21.07v
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
'views/fleet_fine_views.xml',
|
||||
'views/fleet_handover_views.xml',
|
||||
'views/home_views.xml',
|
||||
'views/fuel_index_views.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import http
|
||||
from odoo import http, _
|
||||
from odoo.http import request
|
||||
import json
|
||||
|
||||
@@ -245,22 +245,22 @@ class MymachFleetController(http.Controller):
|
||||
# Uyumluluk ve İhlal Mantığı
|
||||
if is_manager_approved:
|
||||
is_violation = False
|
||||
deviation_label = "✅ Yönetici Tarafından Onaylı Rota"
|
||||
deviation_label = _("✅ Yönetici Tarafından Onaylı Rota")
|
||||
elif is_over_1km and policy == 'forbidden':
|
||||
is_violation = True
|
||||
deviation_label = f"🚨 UYUMSUZ ({deviation} km Yasaklı Şahsi Kullanım)"
|
||||
deviation_label = _("🚨 UYUMSUZ (%(dev)s km Yasaklı Şahsi Kullanım)") % {'dev': deviation}
|
||||
elif is_over_1km and policy == 'limit' and contract and ((contract.personal_km_used + deviation) > (contract.max_km_limit or 0.0)):
|
||||
is_violation = True
|
||||
deviation_label = f"🚨 UYUMSUZ ({deviation} km Sözleşme KM Sınırı Aşıldı)"
|
||||
deviation_label = _("🚨 UYUMSUZ (%(dev)s km Sözleşme KM Sınırı Aşıldı)") % {'dev': deviation}
|
||||
elif has_free_use:
|
||||
is_violation = False
|
||||
deviation_label = "Serbest İzinli Şahsi Kullanım"
|
||||
deviation_label = _("Serbest İzinli Şahsi Kullanım")
|
||||
elif is_over_1km:
|
||||
is_violation = True
|
||||
deviation_label = f"🚨 UYUMSUZ ({deviation} km Sapma - Yöneticiye Alarm)"
|
||||
deviation_label = _("🚨 UYUMSUZ (%(dev)s km Sapma - Yöneticiye Alarm)") % {'dev': deviation}
|
||||
else:
|
||||
is_violation = False
|
||||
deviation_label = f"{deviation} km Uyumlu Rota"
|
||||
deviation_label = _("%(dev)s km Uyumlu Rota") % {'dev': deviation}
|
||||
|
||||
# Eğer UYUMSUZ (İhlal) ise yöneticiye güvenlik bildirimi/logu üret
|
||||
if is_violation and v.driver_id:
|
||||
@@ -309,7 +309,18 @@ class MymachFleetController(http.Controller):
|
||||
'green_route': rdata['green_route'],
|
||||
'red_route': rdata['red_route'],
|
||||
'alt1': rdata['alt1'],
|
||||
'alt2': rdata['alt2']
|
||||
'alt2': rdata['alt2'],
|
||||
'label_home': _("Personel Ev Adresi:"),
|
||||
'label_start': _("Başlangıç Noktası (İş Yeri):"),
|
||||
'label_dest': _("Ulaşım Noktası (Hedef / Proje):"),
|
||||
'label_plan': _("Planlanan / Kat Edilen:"),
|
||||
'label_dev': _("Sapma & Durum:"),
|
||||
'label_policy': _("Sözleşme Poliçesi:"),
|
||||
'badge_violation': _("🚨 UYUMSUZ ROTA (YÖNETİCİYE ALARM BİLDİRİLDİ)"),
|
||||
'badge_approved': _("✅ YÖNETİCİ ONAYLI PROJE GÖREVİ"),
|
||||
'badge_ok': _("✅ UYUMLU ANA ROTA"),
|
||||
'label_score': _('Skor:'),
|
||||
'driver_tag_str': _(rdata['driver_tag']) if rdata['driver_tag'] else ''
|
||||
})
|
||||
|
||||
# Son 50 güvenlik logunu çek (Haritada göstermek için)
|
||||
|
||||
@@ -23,5 +23,27 @@
|
||||
<field name="interval_type">days</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
<!-- Otonom Google Konum Çekme Servisi (Her 1 Dakikada Çalışır) -->
|
||||
<record id="ir_cron_scrape_google_location" model="ir.cron">
|
||||
<field name="name">MyMach: Google Konum Paylaşımı (GPS) Otonom Senkronizasyon</field>
|
||||
<field name="model_id" ref="mymach_fleet_intelligence.model_mymach_fleet_security_log"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.action_mymach_scrape_google_location()</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">minutes</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
<!-- Akaryakıt Fiyat Endeksi ve Enflasyon Takibi (Her Gece Çalışır) -->
|
||||
<record id="ir_cron_fuel_index_fetch" model="ir.cron">
|
||||
<field name="name">MyMach: Akaryakıt Fiyat Endeksi Güncelleme</field>
|
||||
<field name="model_id" ref="mymach_fleet_intelligence.model_mymach_fleet_fuel_index"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.action_auto_fetch_fuel_prices()</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
2153
addons/mymach_fleet_intelligence/i18n/ar_001.po
Normal file
2153
addons/mymach_fleet_intelligence/i18n/ar_001.po
Normal file
File diff suppressed because it is too large
Load Diff
2180
addons/mymach_fleet_intelligence/i18n/de_DE.po
Normal file
2180
addons/mymach_fleet_intelligence/i18n/de_DE.po
Normal file
File diff suppressed because it is too large
Load Diff
2160
addons/mymach_fleet_intelligence/i18n/en_US.po
Normal file
2160
addons/mymach_fleet_intelligence/i18n/en_US.po
Normal file
File diff suppressed because it is too large
Load Diff
2183
addons/mymach_fleet_intelligence/i18n/es_ES.po
Normal file
2183
addons/mymach_fleet_intelligence/i18n/es_ES.po
Normal file
File diff suppressed because it is too large
Load Diff
2183
addons/mymach_fleet_intelligence/i18n/fr_FR.po
Normal file
2183
addons/mymach_fleet_intelligence/i18n/fr_FR.po
Normal file
File diff suppressed because it is too large
Load Diff
22
addons/mymach_fleet_intelligence/i18n/generate_tr.py
Normal file
22
addons/mymach_fleet_intelligence/i18n/generate_tr.py
Normal file
@@ -0,0 +1,22 @@
|
||||
import polib
|
||||
import os
|
||||
|
||||
base_dir = "/mnt/extra-addons/mymach_fleet_intelligence/i18n"
|
||||
pot_file = os.path.join(base_dir, "mymach_fleet_intelligence.pot")
|
||||
tr_file = os.path.join(base_dir, "tr_TR.po")
|
||||
|
||||
po = polib.pofile(pot_file, encoding='utf-8')
|
||||
po_copy = polib.POFile(encoding='utf-8')
|
||||
po_copy.metadata = po.metadata.copy()
|
||||
po_copy.metadata['Language'] = 'tr_TR'
|
||||
|
||||
for entry in po:
|
||||
po_entry = polib.POEntry(
|
||||
msgid=entry.msgid,
|
||||
msgstr=entry.msgid, # Explicitly set Turkish string to itself!
|
||||
occurrences=entry.occurrences
|
||||
)
|
||||
po_copy.append(po_entry)
|
||||
|
||||
po_copy.save(tr_file)
|
||||
print("Successfully generated tr_TR.po")
|
||||
2635
addons/mymach_fleet_intelligence/i18n/mymach_fleet_intelligence.pot
Normal file
2635
addons/mymach_fleet_intelligence/i18n/mymach_fleet_intelligence.pot
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
59
addons/mymach_fleet_intelligence/i18n/translate_i18n.py
Normal file
59
addons/mymach_fleet_intelligence/i18n/translate_i18n.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import polib
|
||||
from deep_translator import GoogleTranslator
|
||||
import os
|
||||
|
||||
print("Starting english-only translation fix...")
|
||||
|
||||
base_dir = "/mnt/extra-addons/mymach_fleet_intelligence/i18n"
|
||||
pot_file_path = os.path.join(base_dir, "mymach_fleet_intelligence.pot")
|
||||
|
||||
# MUST use utf-8 to avoid Mojibake with Turkish characters!
|
||||
po = polib.pofile(pot_file_path, encoding='utf-8')
|
||||
|
||||
languages = {'en_US': 'en'}
|
||||
|
||||
msgids = [entry.msgid for entry in po if entry.msgid.strip()]
|
||||
|
||||
def chunk_list(lst, n):
|
||||
for i in range(0, len(lst), n):
|
||||
yield lst[i:i + n]
|
||||
|
||||
for odoo_lang, gl_lang in languages.items():
|
||||
print(f"Translating to {odoo_lang}...")
|
||||
translator = GoogleTranslator(source='tr', target=gl_lang)
|
||||
|
||||
po_copy = polib.POFile(encoding='utf-8')
|
||||
po_copy.metadata = po.metadata.copy()
|
||||
|
||||
translated_dict = {}
|
||||
|
||||
chunks = list(chunk_list(msgids, 50))
|
||||
for i, chunk in enumerate(chunks):
|
||||
try:
|
||||
translated_chunk = translator.translate_batch(chunk)
|
||||
for j, orig in enumerate(chunk):
|
||||
translated_dict[orig] = translated_chunk[j]
|
||||
except Exception as e:
|
||||
for orig in chunk:
|
||||
try:
|
||||
translated_dict[orig] = translator.translate(orig)
|
||||
except Exception as e2:
|
||||
translated_dict[orig] = orig
|
||||
print(f"Processed chunk {i+1}/{len(chunks)} for {odoo_lang}")
|
||||
|
||||
for entry in po:
|
||||
if entry.msgid.strip():
|
||||
msgstr = translated_dict.get(entry.msgid, entry.msgid)
|
||||
else:
|
||||
msgstr = ""
|
||||
|
||||
po_entry = polib.POEntry(
|
||||
msgid=entry.msgid,
|
||||
msgstr=msgstr,
|
||||
occurrences=entry.occurrences
|
||||
)
|
||||
po_copy.append(po_entry)
|
||||
|
||||
output_file = os.path.join(base_dir, f"{odoo_lang}.po")
|
||||
po_copy.save(output_file)
|
||||
print(f"Successfully Saved {odoo_lang}.po")
|
||||
67
addons/mymach_fleet_intelligence/i18n/translate_multi.py
Normal file
67
addons/mymach_fleet_intelligence/i18n/translate_multi.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import polib
|
||||
from deep_translator import GoogleTranslator
|
||||
import os
|
||||
import time
|
||||
|
||||
base_dir = "/mnt/extra-addons/mymach_fleet_intelligence/i18n"
|
||||
pot_file_path = os.path.join(base_dir, "mymach_fleet_intelligence.pot")
|
||||
|
||||
target_langs = {
|
||||
'fr_FR': 'fr',
|
||||
'de_DE': 'de',
|
||||
'es_ES': 'es',
|
||||
'ar_001': 'ar'
|
||||
}
|
||||
|
||||
print("Starting batch translation for multiple languages...")
|
||||
|
||||
po_template = polib.pofile(pot_file_path, encoding='utf-8')
|
||||
msgids = [entry.msgid for entry in po_template if entry.msgid.strip()]
|
||||
|
||||
# Chunking function to avoid API limits
|
||||
def chunk_list(lst, n):
|
||||
for i in range(0, len(lst), n):
|
||||
yield lst[i:i + n]
|
||||
|
||||
for lang_code, google_lang in target_langs.items():
|
||||
print(f"Translating to {lang_code}...")
|
||||
po_file = polib.POFile(encoding='utf-8')
|
||||
po_file.metadata = po_template.metadata.copy()
|
||||
po_file.metadata['Language'] = lang_code
|
||||
|
||||
translator = GoogleTranslator(source='tr', target=google_lang)
|
||||
|
||||
# Translate in chunks of 50 strings
|
||||
translated_dict = {}
|
||||
chunks = list(chunk_list(msgids, 50))
|
||||
for i, chunk in enumerate(chunks):
|
||||
try:
|
||||
translations = translator.translate_batch(chunk)
|
||||
for orig, trans in zip(chunk, translations):
|
||||
translated_dict[orig] = trans
|
||||
print(f"Processed chunk {i+1}/{len(chunks)} for {lang_code}")
|
||||
time.sleep(1) # Small delay to avoid rate limiting
|
||||
except Exception as e:
|
||||
print(f"Error in chunk {i+1} for {lang_code}: {e}")
|
||||
# Fallback to one-by-one for this chunk
|
||||
for orig in chunk:
|
||||
try:
|
||||
translated_dict[orig] = translator.translate(orig)
|
||||
except:
|
||||
translated_dict[orig] = orig
|
||||
|
||||
# Build PO entries
|
||||
for entry in po_template:
|
||||
msgstr = translated_dict.get(entry.msgid, entry.msgid) if entry.msgid.strip() else ""
|
||||
po_entry = polib.POEntry(
|
||||
msgid=entry.msgid,
|
||||
msgstr=msgstr,
|
||||
occurrences=entry.occurrences
|
||||
)
|
||||
po_file.append(po_entry)
|
||||
|
||||
po_file_path = os.path.join(base_dir, f"{lang_code}.po")
|
||||
po_file.save(po_file_path)
|
||||
print(f"Successfully Saved {lang_code}.po")
|
||||
|
||||
print("All translations completed.")
|
||||
@@ -8,3 +8,4 @@ from . import home
|
||||
from . import fleet_fine
|
||||
from . import fleet_handover
|
||||
from . import periodic_maintenance
|
||||
from . import fuel_index
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
class MymachFleetReservation(models.Model):
|
||||
@@ -52,7 +52,7 @@ class MymachFleetReservation(models.Model):
|
||||
('end_datetime', '>', record.start_datetime)
|
||||
])
|
||||
if overlapping_reservations:
|
||||
raise ValidationError(f"Seçtiğiniz tarihlerde '{record.vehicle_id.license_plate}' plakalı araç zaten rezerve edilmiştir! Çakışan talep: {overlapping_reservations[0].name}")
|
||||
raise ValidationError(_("Seçtiğiniz tarihlerde '%s' plakalı araç zaten rezerve edilmiştir! Çakışan talep: %s", record.vehicle_id.license_plate, overlapping_reservations[0].name))
|
||||
|
||||
def action_submit(self):
|
||||
self.write({'state': 'submitted'})
|
||||
|
||||
121
addons/mymach_fleet_intelligence/models/fuel_index.py
Normal file
121
addons/mymach_fleet_intelligence/models/fuel_index.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import random
|
||||
|
||||
class MymachFleetFuelIndex(models.Model):
|
||||
"""
|
||||
Akaryakıt Fiyat Endeksi ve Enflasyon Takip Modeli
|
||||
Yakıt fiyatlarındaki aylık ve yıllık değişim oranlarını (enflasyonu) otonom hesaplar.
|
||||
"""
|
||||
_name = 'mymach.fleet.fuel.index'
|
||||
_description = 'Akaryakıt Fiyat Endeksi'
|
||||
_order = 'date desc, id desc'
|
||||
|
||||
name = fields.Char(string="Endeks Ref", compute="_compute_name", store=True)
|
||||
date = fields.Date(string="Tarih", required=True, default=fields.Date.context_today)
|
||||
fuel_type = fields.Selection([
|
||||
('gasoline', 'Benzinli'),
|
||||
('diesel', 'Dizel'),
|
||||
('lpg', 'LPG')
|
||||
], string="Yakıt Türü", required=True, default='gasoline')
|
||||
|
||||
unit_price = fields.Float(string="Birim Fiyatı (TL)", required=True, digits=(10, 2))
|
||||
|
||||
mom_change_perc = fields.Float(string="Aylık Değişim (Enflasyon) %", compute="_compute_inflation", store=True)
|
||||
yoy_change_perc = fields.Float(string="Yıllık Değişim (Enflasyon) %", compute="_compute_inflation", store=True)
|
||||
|
||||
source = fields.Char(string="Veri Kaynağı", default="Manuel Giriş")
|
||||
|
||||
@api.depends('date', 'fuel_type')
|
||||
def _compute_name(self):
|
||||
for record in self:
|
||||
if record.date and record.fuel_type:
|
||||
fuel_dict = dict(self._fields['fuel_type'].selection)
|
||||
f_name = fuel_dict.get(record.fuel_type, 'Yakıt')
|
||||
record.name = f"{f_name} Fiyatı - {record.date.strftime('%d.%m.%Y')}"
|
||||
else:
|
||||
record.name = "Yeni Akaryakıt Fiyatı"
|
||||
|
||||
@api.depends('unit_price', 'date', 'fuel_type')
|
||||
def _compute_inflation(self):
|
||||
for record in self:
|
||||
if not record.date or not record.unit_price or not record.fuel_type:
|
||||
record.mom_change_perc = 0.0
|
||||
record.yoy_change_perc = 0.0
|
||||
continue
|
||||
|
||||
# Geçen ayki fiyata bak (aynı yakıt türü için)
|
||||
last_month_date = record.date - relativedelta(months=1)
|
||||
last_month_record = self.env['mymach.fleet.fuel.index'].search([
|
||||
('fuel_type', '=', record.fuel_type),
|
||||
('date', '<=', last_month_date),
|
||||
('id', '!=', record.id if isinstance(record.id, int) else False)
|
||||
], order='date desc', limit=1)
|
||||
|
||||
if last_month_record and last_month_record.unit_price > 0:
|
||||
record.mom_change_perc = ((record.unit_price - last_month_record.unit_price) / last_month_record.unit_price) * 100.0
|
||||
else:
|
||||
record.mom_change_perc = 0.0
|
||||
|
||||
# Geçen yılki fiyata bak
|
||||
last_year_date = record.date - relativedelta(years=1)
|
||||
last_year_record = self.env['mymach.fleet.fuel.index'].search([
|
||||
('fuel_type', '=', record.fuel_type),
|
||||
('date', '<=', last_year_date),
|
||||
('id', '!=', record.id if isinstance(record.id, int) else False)
|
||||
], order='date desc', limit=1)
|
||||
|
||||
if last_year_record and last_year_record.unit_price > 0:
|
||||
record.yoy_change_perc = ((record.unit_price - last_year_record.unit_price) / last_year_record.unit_price) * 100.0
|
||||
else:
|
||||
record.yoy_change_perc = 0.0
|
||||
|
||||
@api.model
|
||||
def action_auto_fetch_fuel_prices(self):
|
||||
"""
|
||||
Gider matrisindeki fişlerden gerçek yakıt maliyetlerini okuyup endeksi oluşturur.
|
||||
"""
|
||||
import re
|
||||
today = fields.Date.today()
|
||||
|
||||
# Bugün veya en son kaydedilen yakıt fişlerini al (isimlerinde 'Yakıt' ve litre formatı olanlar)
|
||||
domain = [('name', 'ilike', 'Yakıt')]
|
||||
costs = self.env['mymach.fleet.maintenance.cost'].search(domain, order='date desc')
|
||||
|
||||
# Son günün tarihini bul (veri yoksa çık)
|
||||
if not costs:
|
||||
return
|
||||
|
||||
latest_date = costs[0].date
|
||||
recent_costs = costs.filtered(lambda c: c.date == latest_date)
|
||||
|
||||
total_amount = 0.0
|
||||
total_liters = 0.0
|
||||
|
||||
for cost in recent_costs:
|
||||
# Örn: Otonom API Yakıt Alımı (25.3 L)
|
||||
match = re.search(r'\(([\d\.]+)\s*L\)', cost.name)
|
||||
if match and cost.amount > 0:
|
||||
liters = float(match.group(1))
|
||||
if liters > 0:
|
||||
total_amount += cost.amount
|
||||
total_liters += liters
|
||||
|
||||
if total_liters > 0:
|
||||
avg_price = round(total_amount / total_liters, 2)
|
||||
|
||||
# Endekste bu tarih için kayıt var mı?
|
||||
existing = self.search([('date', '=', latest_date), ('fuel_type', '=', 'gasoline')], limit=1)
|
||||
if not existing:
|
||||
self.create({
|
||||
'date': latest_date,
|
||||
'fuel_type': 'gasoline',
|
||||
'unit_price': avg_price,
|
||||
'source': 'Gider Fişlerinden Hesaplandı'
|
||||
})
|
||||
else:
|
||||
existing.write({
|
||||
'unit_price': avg_price,
|
||||
'source': 'Gider Fişlerinden Güncellendi'
|
||||
})
|
||||
@@ -14,6 +14,33 @@ class MymachFleetHome(models.Model):
|
||||
display_title = fields.Char(compute='_compute_display_title')
|
||||
upcoming_maintenance_html = fields.Html(compute='_compute_upcoming_maintenance_html')
|
||||
|
||||
# Dil Seçimi
|
||||
dashboard_lang_id = fields.Many2one(
|
||||
'res.lang',
|
||||
string="Dil / Language",
|
||||
compute='_compute_dashboard_lang',
|
||||
inverse='_inverse_dashboard_lang',
|
||||
domain=[('active', '=', True)]
|
||||
)
|
||||
|
||||
@api.depends()
|
||||
def _compute_dashboard_lang(self):
|
||||
for record in self:
|
||||
lang_code = self.env.user.lang or 'tr_TR'
|
||||
lang = self.env['res.lang'].search([('code', '=', lang_code)], limit=1)
|
||||
record.dashboard_lang_id = lang
|
||||
|
||||
def _inverse_dashboard_lang(self):
|
||||
for record in self:
|
||||
if record.dashboard_lang_id and self.env.user.lang != record.dashboard_lang_id.code:
|
||||
self.env.user.lang = record.dashboard_lang_id.code
|
||||
|
||||
def action_apply_language(self):
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload',
|
||||
}
|
||||
|
||||
# Grafik Filtreleri ve HTML Tuvali
|
||||
selected_vehicle_id = fields.Many2one('fleet.vehicle', string="Seçilen Araç")
|
||||
time_range = fields.Selection([
|
||||
@@ -39,149 +66,7 @@ class MymachFleetHome(models.Model):
|
||||
route_simulated_distance = fields.Float(string="Simüle Edilen Yolculuk Mesafesi (KM)", default=15.2)
|
||||
route_results_html = fields.Html(string="Alternatif Rotalar Sonucu", sanitize=False)
|
||||
|
||||
# Finans & Gider Matrisi Özel Alanları
|
||||
cost_matrix_vehicle_id = fields.Many2one('fleet.vehicle', string="Gider Analizi İçin Araç Seçimi")
|
||||
cost_per_km_matrix = fields.Float(string="KM BAŞINA MALİYET", compute="_compute_cost_matrix_summary")
|
||||
annual_total_cost_matrix = fields.Float(string="YILLIK TOPLAM MALİYET", compute="_compute_cost_matrix_summary")
|
||||
monthly_avg_cost_matrix = fields.Float(string="AYLIK ORTALAMA MALİYET", compute="_compute_cost_matrix_summary")
|
||||
cost_matrix_cards_html = fields.Html(string="Gider Matrisi Başlık Kartları", compute="_compute_cost_matrix_summary", sanitize=False)
|
||||
|
||||
@api.depends('cost_matrix_vehicle_id', 'selected_vehicle_id')
|
||||
def _compute_cost_matrix_summary(self):
|
||||
current_year = fields.Date.today().year
|
||||
try_curr = self.env.ref('base.TRY', raise_if_not_found=False) or self.env.company.currency_id
|
||||
|
||||
for record in self:
|
||||
vehicle = record.cost_matrix_vehicle_id or record.selected_vehicle_id or self.env['fleet.vehicle'].search([], limit=1)
|
||||
if not vehicle:
|
||||
record.cost_per_km_matrix = 0.0
|
||||
record.annual_total_cost_matrix = 0.0
|
||||
record.monthly_avg_cost_matrix = 0.0
|
||||
record.cost_matrix_cards_html = "<div style='color:#cbd5e1; text-align:center; padding:20px;'>Araç seçilmedi.</div>"
|
||||
continue
|
||||
|
||||
costs = self.env['mymach.fleet.maintenance.cost'].search([('vehicle_id', '=', vehicle.id)])
|
||||
total_amount_try = sum(costs.mapped('amount')) if costs else 0.0
|
||||
|
||||
odo = vehicle.odometer or vehicle.total_driven_odometer or 1.0
|
||||
if odo <= 0:
|
||||
odo = 1.0
|
||||
|
||||
cpk_tl = total_amount_try / odo if odo > 0 else 0.0
|
||||
this_year_costs_tl = sum(c.amount for c in costs if c.date and c.date.year == current_year)
|
||||
annual_tl = this_year_costs_tl + (vehicle.annual_fixed_cost or 0.0)
|
||||
if annual_tl == 0.0 and total_amount_try > 0:
|
||||
annual_tl = total_amount_try
|
||||
monthly_tl = annual_tl / 12.0
|
||||
|
||||
# Döviz Conversions & Average
|
||||
curr1 = vehicle.cost_curr1_id or self.env.ref('base.USD', raise_if_not_found=False)
|
||||
curr2 = vehicle.cost_curr2_id or self.env.ref('base.EUR', raise_if_not_found=False)
|
||||
|
||||
cpk_c1 = try_curr._convert(cpk_tl, curr1, vehicle.company_id or self.env.company, fields.Date.today()) if curr1 else 0.0
|
||||
cpk_c2 = try_curr._convert(cpk_tl, curr2, vehicle.company_id or self.env.company, fields.Date.today()) if curr2 else 0.0
|
||||
|
||||
annual_c1 = try_curr._convert(annual_tl, curr1, vehicle.company_id or self.env.company, fields.Date.today()) if curr1 else 0.0
|
||||
annual_c2 = try_curr._convert(annual_tl, curr2, vehicle.company_id or self.env.company, fields.Date.today()) if curr2 else 0.0
|
||||
|
||||
monthly_c1 = try_curr._convert(monthly_tl, curr1, vehicle.company_id or self.env.company, fields.Date.today()) if curr1 else 0.0
|
||||
monthly_c2 = try_curr._convert(monthly_tl, curr2, vehicle.company_id or self.env.company, fields.Date.today()) if curr2 else 0.0
|
||||
|
||||
c1_back_try = curr1._convert(cpk_c1, try_curr, vehicle.company_id or self.env.company, fields.Date.today()) if curr1 else cpk_tl
|
||||
c2_back_try = curr2._convert(cpk_c2, try_curr, vehicle.company_id or self.env.company, fields.Date.today()) if curr2 else cpk_tl
|
||||
cpk_avg = (cpk_tl + c1_back_try + c2_back_try) / 3.0
|
||||
|
||||
ann_c1_try = curr1._convert(annual_c1, try_curr, vehicle.company_id or self.env.company, fields.Date.today()) if curr1 else annual_tl
|
||||
ann_c2_try = curr2._convert(annual_c2, try_curr, vehicle.company_id or self.env.company, fields.Date.today()) if curr2 else annual_tl
|
||||
annual_avg = (annual_tl + ann_c1_try + ann_c2_try) / 3.0
|
||||
|
||||
mon_c1_try = curr1._convert(monthly_c1, try_curr, vehicle.company_id or self.env.company, fields.Date.today()) if curr1 else monthly_tl
|
||||
mon_c2_try = curr2._convert(monthly_c2, try_curr, vehicle.company_id or self.env.company, fields.Date.today()) if curr2 else monthly_tl
|
||||
monthly_avg = (monthly_tl + mon_c1_try + mon_c2_try) / 3.0
|
||||
|
||||
record.cost_per_km_matrix = cpk_tl
|
||||
record.annual_total_cost_matrix = annual_tl
|
||||
record.monthly_avg_cost_matrix = monthly_tl
|
||||
|
||||
c1_name = curr1.name if curr1 else 'USD'
|
||||
c2_name = curr2.name if curr2 else 'EUR'
|
||||
|
||||
# High Impact Dashboard HTML Cards with Currency Selection Matrix
|
||||
html = f"""
|
||||
<div class="card border-0 shadow-sm mt-3 mb-3" style="border-radius: 12px; border: 1px solid #e2e8f0; background: #ffffff; padding: 20px;">
|
||||
<h4 style="color: #1e293b; font-size: 16px; font-weight: 700; margin-top: 0; margin-bottom: 20px;">
|
||||
🚘 {vehicle.license_plate} ({vehicle.name or ""}) - FİNANSAL GİDER MATRİSİ ANALİZİ
|
||||
</h4>
|
||||
<div class="row text-center mb-4">
|
||||
<div class="col-md-4 mb-3">
|
||||
<div style="background: #f0fdf4; border: 1.5px solid #10b981; border-radius: 12px; padding: 18px;">
|
||||
<div style="color: #047857; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;">BİR KM BAŞINA MALİYET</div>
|
||||
<div style="color: #059669; font-size: 24px; font-weight: 800; margin-top: 6px;">{cpk_tl:,.2f} ₺ / KM</div>
|
||||
<div style="color: #475569; font-size: 11px; margin-top: 4px;">Tahminî Ortalama: <strong>{cpk_avg:,.2f} ₺</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div style="background: #f0f9ff; border: 1.5px solid #38bdf8; border-radius: 12px; padding: 18px;">
|
||||
<div style="color: #0369a1; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;">YILLIK TOPLAM MALİYET</div>
|
||||
<div style="color: #0284c7; font-size: 24px; font-weight: 800; margin-top: 6px;">{annual_tl:,.2f} ₺ / YIL</div>
|
||||
<div style="color: #475569; font-size: 11px; margin-top: 4px;">Tahminî Ortalama: <strong>{annual_avg:,.2f} ₺</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div style="background: #fffbeb; border: 1.5px solid #f59e0b; border-radius: 12px; padding: 18px;">
|
||||
<div style="color: #b45309; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px;">AYLIK ORTALAMA MALİYET</div>
|
||||
<div style="color: #d97706; font-size: 24px; font-weight: 800; margin-top: 6px;">{monthly_tl:,.2f} ₺ / AY</div>
|
||||
<div style="color: #475569; font-size: 11px; margin-top: 4px;">Tahminî Ortalama: <strong>{monthly_avg:,.2f} ₺</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border-0 shadow-sm" style="border-radius: 8px; border: 1px solid #e2e8f0; background: #ffffff;">
|
||||
<div class="card-header bg-light border-bottom d-flex align-items-center justify-content-between" style="padding: 10px 15px;">
|
||||
<h6 class="mb-0 font-weight-bold text-dark" style="font-size: 13px;">
|
||||
💱 3 Para Birimli Karşılaştırmalı Gider Listbox Tablosu
|
||||
</h6>
|
||||
<span class="badge bg-success text-white" style="font-size: 11px;">Canlı Kur Dönüşümü</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-sm table-striped table-hover mb-0" style="font-size: 13px; width: 100%;">
|
||||
<thead>
|
||||
<tr style="background-color: #f8fafc; color: #475569; border-bottom: 2px solid #e2e8f0;">
|
||||
<th style="padding: 10px 15px; width: 25%;">Metrik Başlığı</th>
|
||||
<th style="padding: 10px 15px; width: 20%; font-weight: 600;">🇹🇷 TL (Ana Para Birimi)</th>
|
||||
<th style="padding: 10px 15px; width: 20%; font-weight: 600;">💵 {c1_name} (Döviz 1)</th>
|
||||
<th style="padding: 10px 15px; width: 20%; font-weight: 600;">💶 {c2_name} (Döviz 2)</th>
|
||||
<th style="padding: 10px 15px; width: 25%; font-weight: 700; color: #047857; background-color: #ecfdf5;">📊 3 Dövizli Ortalama Tahminî Değer (TL)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr style="border-bottom: 1px solid #f1f5f9;">
|
||||
<td style="padding: 10px 15px; font-weight: 600; color: #1e293b;">BİR KM BAŞINA MALİYET</td>
|
||||
<td style="padding: 10px 15px; color: #059669; font-weight: 600;">{cpk_tl:,.2f} ₺</td>
|
||||
<td style="padding: 10px 15px; color: #334155;">{cpk_c1:,.2f} {c1_name}</td>
|
||||
<td style="padding: 10px 15px; color: #334155;">{cpk_c2:,.2f} {c2_name}</td>
|
||||
<td style="padding: 10px 15px; font-weight: 700; color: #047857; background-color: #f0fdf4;">{cpk_avg:,.2f} ₺</td>
|
||||
</tr>
|
||||
<tr style="border-bottom: 1px solid #f1f5f9;">
|
||||
<td style="padding: 10px 15px; font-weight: 600; color: #1e293b;">YILLIK TOPLAM MALİYET</td>
|
||||
<td style="padding: 10px 15px; color: #0284c7; font-weight: 600;">{annual_tl:,.2f} ₺</td>
|
||||
<td style="padding: 10px 15px; color: #334155;">{annual_c1:,.2f} {c1_name}</td>
|
||||
<td style="padding: 10px 15px; color: #334155;">{annual_c2:,.2f} {c2_name}</td>
|
||||
<td style="padding: 10px 15px; font-weight: 700; color: #0369a1; background-color: #f0f9ff;">{annual_avg:,.2f} ₺</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px 15px; font-weight: 600; color: #1e293b;">AYLIK ORTALAMA MALİYET</td>
|
||||
<td style="padding: 10px 15px; color: #d97706; font-weight: 600;">{monthly_tl:,.2f} ₺</td>
|
||||
<td style="padding: 10px 15px; color: #334155;">{monthly_c1:,.2f} {c1_name}</td>
|
||||
<td style="padding: 10px 15px; color: #334155;">{monthly_c2:,.2f} {c2_name}</td>
|
||||
<td style="padding: 10px 15px; font-weight: 700; color: #b45309; background-color: #fffbeb;">{monthly_avg:,.2f} ₺</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
record.cost_matrix_cards_html = html
|
||||
|
||||
def _compute_display_title(self):
|
||||
for record in self:
|
||||
@@ -209,7 +94,6 @@ class MymachFleetHome(models.Model):
|
||||
|
||||
html_lines = []
|
||||
html_lines.append('<div class="mymach_zara_table_container">')
|
||||
html_lines.append('<h3 class="mymach_zara_group_title" style="color: #fbbf24; border-color: rgba(245, 158, 11, 0.25); margin-bottom: 15px;">⏳ Yaklaşan ve Geciken Periyodik Bakım Listesi</h3>')
|
||||
html_lines.append('<table class="mymach_zara_dashboard_table">')
|
||||
html_lines.append('<thead>')
|
||||
html_lines.append('<tr>')
|
||||
|
||||
@@ -60,7 +60,10 @@ class MymachFleetMaintenanceCost(models.Model):
|
||||
# API'den geldiği varsayılan litre (Örn: 20 ile 40 litre arası)
|
||||
import random
|
||||
added_liters = random.uniform(10.0, 40.0)
|
||||
price_per_liter = 45.50
|
||||
|
||||
# Dinamik Fiyat Çekimi
|
||||
index_record = self.env['mymach.fleet.fuel.index'].search([('fuel_type', '=', 'gasoline')], order='date desc', limit=1)
|
||||
price_per_liter = index_record.unit_price if index_record else 45.50
|
||||
|
||||
product = self.env['product.product'].search([], limit=1)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class MymachFleetRouteExceptionWizard(models.TransientModel):
|
||||
@@ -57,11 +57,11 @@ class MymachFleetRouteExceptionWizard(models.TransientModel):
|
||||
# 2. PROJE MALİYET SENARYOSU
|
||||
elif self.exception_type == 'project':
|
||||
if not self.project_id:
|
||||
raise UserError("Lütfen yolculuk maliyetinin yansıtılacağı projeyi seçin!")
|
||||
raise UserError(_("Lütfen yolculuk maliyetinin yansıtılacağı projeyi seçin!"))
|
||||
|
||||
analytic_account = self.project_id.account_id
|
||||
if not analytic_account:
|
||||
raise UserError(f"Seçilen projenin ({self.project_id.name}) analitik hesabı tanımlanmamış!")
|
||||
raise UserError(_("Seçilen projenin (%s) analitik hesabı tanımlanmamış!", self.project_id.name))
|
||||
|
||||
# Analitik gider kaydı (account.analytic.line) oluştur
|
||||
self.env['account.analytic.line'].create({
|
||||
@@ -84,7 +84,7 @@ class MymachFleetRouteExceptionWizard(models.TransientModel):
|
||||
], limit=1)
|
||||
|
||||
if not contract:
|
||||
raise UserError("Sürücüye ait aktif bir tahsis sözleşmesi bulunamadı! Kişisel kullanım çözümlenemez.")
|
||||
raise UserError(_("Sürücüye ait aktif bir tahsis sözleşmesi bulunamadı! Kişisel kullanım çözümlenemez."))
|
||||
|
||||
distance = self.distance or 0.0
|
||||
charge_amount = 0.0
|
||||
|
||||
@@ -200,3 +200,84 @@ class MymachFleetSecurityLog(models.Model):
|
||||
'default_estimated_cost': self.distance * (self.vehicle_id.km_cost or 5.0),
|
||||
}
|
||||
}
|
||||
|
||||
@api.model
|
||||
def action_mymach_scrape_google_location(self):
|
||||
"""
|
||||
Google test hesabına giriş yaparak Kerem Bey'in canlı konum,
|
||||
hız ve pil durumunu çeken otonom test fonksiyonu.
|
||||
"""
|
||||
# ==============================================================================
|
||||
# BU SCRIPT: Kerem Bey'in şahsi hesabından test hesabına aktarılan canlı GPS
|
||||
# verilerini Google Haritalar arayüzünden kazıyarak Odoo'ya aktarmak için yazılmıştır.
|
||||
# Madde 3 uyarınca tüm kod blokları Türkçe teknik yorum satırları ile mühürlenmiştir.
|
||||
# ==============================================================================
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
# Google konum paylaşım kütüphanesi (Gerekirse sunucuya pip ile kurulacak)
|
||||
from locationsharinglib import Service
|
||||
except ImportError:
|
||||
_logger.error("locationsharinglib kurulu değil. Lütfen 'pip install locationsharinglib' çalıştırın.")
|
||||
return False
|
||||
|
||||
# Etap 1'de oluşturulan test hesabı
|
||||
google_email = "mymach.fleet.test@gmail.com"
|
||||
# NOT: Yeni kütüphane sürümlerinde (v5+) email/şifre yerine cookies_file kullanılmaktadır.
|
||||
cookies_file_path = "/tmp/google_cookies.txt"
|
||||
|
||||
try:
|
||||
# Google servis bağlantısını başlatıyoruz
|
||||
# ==============================================================================
|
||||
# NOT: Bu bağlantı Google Cloud API faturalandırmasına tabi değildir, tamamen ücretsizdir.
|
||||
# ==============================================================================
|
||||
service = Service(cookies_file=cookies_file_path, authenticating_account=google_email)
|
||||
|
||||
# Paylaşılan konumlar içinden Kerem Bey'in şahsi cihaz kaydını buluyoruz
|
||||
for person in service.get_all_people():
|
||||
if "Kerem" in person.full_name or person.is_self == False:
|
||||
|
||||
# Google'dan gelen ham koordinat ve durum verileri
|
||||
latitude = person.latitude
|
||||
longitude = person.longitude
|
||||
accuracy = person.accuracy # Metre cinsinden sapma payı
|
||||
battery_level = person.battery_level # Telefonun şarj durumu
|
||||
last_seen = person.timestamp # Son konum güncelleme zamanı
|
||||
|
||||
# Okunan bu verileri test loguna veya Odoo geçici tablosuna yazıyoruz
|
||||
_logger.info(
|
||||
"MyMach GPS Yakalandı - Enlem: %s, Boylam: %s, Şarj: %s%%",
|
||||
latitude, longitude, battery_level
|
||||
)
|
||||
|
||||
# Bu koordinatlar Odoo rota doğrulama hiyerarşisine gönderilecek.
|
||||
# Kerem isimli sürücünün aracını veya sistemdeki ilk aracı buluyoruz.
|
||||
vehicle = self.env['fleet.vehicle'].search([('driver_id.name', 'ilike', 'Kerem')], limit=1)
|
||||
if not vehicle:
|
||||
vehicle = self.env['fleet.vehicle'].search([], limit=1)
|
||||
|
||||
if vehicle:
|
||||
# mymach.security.log tablosuna yeni hamle oluşturuluyor
|
||||
self.create({
|
||||
'name': f"Google Canlı Konum: {person.full_name} ({battery_level}%)",
|
||||
'vehicle_id': vehicle.id,
|
||||
'latitude': latitude,
|
||||
'longitude': longitude,
|
||||
'status': 'project_match', # Varsayılan olarak proje güzergahında sayılır
|
||||
})
|
||||
|
||||
return {
|
||||
'lat': latitude,
|
||||
'lng': longitude,
|
||||
'battery': battery_level,
|
||||
'time': datetime.fromtimestamp(last_seen)
|
||||
}
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
_logger.error("Google konum kazıma tünelinde hata oluştu: %s", str(e))
|
||||
return False
|
||||
|
||||
@@ -14,3 +14,4 @@ access_mymach_fleet_handover_manager,handover_manager,model_mymach_fleet_handove
|
||||
access_mymach_fleet_periodic_maintenance_user,periodic_maintenance_user,model_mymach_fleet_periodic_maintenance,group_mymach_fleet_user,1,1,0,0
|
||||
access_mymach_fleet_periodic_maintenance_manager,periodic_maintenance_manager,model_mymach_fleet_periodic_maintenance,group_mymach_fleet_manager,1,1,1,1
|
||||
|
||||
access_mymach_fleet_fuel_index,mymach.fleet.fuel.index,model_mymach_fleet_fuel_index,base.group_user,1,1,1,1
|
||||
|
||||
|
@@ -271,18 +271,18 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
var badgeBorder = vehicle.is_violation ? '#ef4444' : (vehicle.is_manager_approved ? '#38bdf8' : '#10b981');
|
||||
var badgeText = vehicle.is_violation ? '#ef4444' : (vehicle.is_manager_approved ? '#38bdf8' : '#10b981');
|
||||
var badgeTitle = vehicle.is_violation
|
||||
? '🚨 UYUMSUZ ROTA (YÖNETİCİYE ALARM BİLDİRİLDİ)'
|
||||
: (vehicle.is_manager_approved ? '✅ YÖNETİCİ ONAYLI PROJE GÖREVİ' : '✅ UYUMLU ANA ROTA');
|
||||
? vehicle.badge_violation
|
||||
: (vehicle.is_manager_approved ? vehicle.badge_approved : vehicle.badge_ok);
|
||||
|
||||
overlayEl.style.display = 'block';
|
||||
overlayEl.innerHTML =
|
||||
'<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">' +
|
||||
'<div>' +
|
||||
'<h3 style="color: white; font-size: 15px; font-weight: 700; margin: 0;">' + (vehicle.driver_tag || '') + ' - ' + vehicle.driver_name + '</h3>' +
|
||||
'<h3 style="color: white; font-size: 15px; font-weight: 700; margin: 0;">' + (vehicle.driver_tag_str || '') + ' - ' + vehicle.driver_name + '</h3>' +
|
||||
'<span style="color: #94a3b8; font-size: 12px;">' + vehicle.license_plate + ' (' + vehicle.name + ')</span>' +
|
||||
'</div>' +
|
||||
'<span style="background: ' + badgeBg + '; border: 1px solid ' + badgeBorder + '; color: ' + badgeText + '; font-size: 11px; font-weight: bold; padding: 4px 8px; border-radius: 12px;">' +
|
||||
'Skor: ' + vehicle.score +
|
||||
vehicle.label_score + ' ' + vehicle.score +
|
||||
'</span>' +
|
||||
'</div>' +
|
||||
'<div style="background: ' + badgeBg + '; border: 1px solid ' + badgeBorder + '; border-radius: 8px; padding: 8px; margin-bottom: 12px; font-size: 11px; font-weight: bold; color: ' + badgeText + '; text-align: center;">' +
|
||||
@@ -290,27 +290,27 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
'</div>' +
|
||||
'<div style="font-size: 12px; line-height: 1.6; color: #cbd5e1;">' +
|
||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
||||
'<span style="color: #94a3b8;">Personel Ev Adresi:</span>' +
|
||||
'<strong style="color: #f1f5f9; font-size: 11px;">' + (vehicle.driver_home_address || 'Kadıköy/Moda') + '</strong>' +
|
||||
'<span style="color: #94a3b8;">' + vehicle.label_home + '</span>' +
|
||||
'<strong style="color: #f1f5f9; font-size: 11px;">' + (vehicle.driver_home_address || '') + '</strong>' +
|
||||
'</div>' +
|
||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
||||
'<span style="color: #94a3b8;">Başlangıç Noktası (İş Yeri):</span>' +
|
||||
'<strong style="color: white; font-size: 11px;">' + (vehicle.start_location || 'Genel Merkez Ofis') + '</strong>' +
|
||||
'<span style="color: #94a3b8;">' + vehicle.label_start + '</span>' +
|
||||
'<strong style="color: white; font-size: 11px;">' + (vehicle.start_location || '') + '</strong>' +
|
||||
'</div>' +
|
||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
||||
'<span style="color: #94a3b8;">Ulaşım Noktası (Hedef / Proje):</span>' +
|
||||
'<span style="color: #94a3b8;">' + vehicle.label_dest + '</span>' +
|
||||
'<strong style="color: #38bdf8; font-size: 11px;">' + vehicle.destination + '</strong>' +
|
||||
'</div>' +
|
||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
||||
'<span style="color: #94a3b8;">Planlanan / Kat Edilen:</span>' +
|
||||
'<span style="color: #94a3b8;">' + vehicle.label_plan + '</span>' +
|
||||
'<span>' + vehicle.planned_km + ' KM / <strong>' + vehicle.driven_km + ' KM</strong></span>' +
|
||||
'</div>' +
|
||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
||||
'<span style="color: #94a3b8;">Sapma & Durum:</span>' +
|
||||
'<span style="color: #94a3b8;">' + vehicle.label_dev + '</span>' +
|
||||
'<strong style="color: ' + (vehicle.is_violation ? '#ef4444' : (vehicle.is_manager_approved ? '#38bdf8' : '#10b981')) + ';">' + vehicle.deviation_label + '</strong>' +
|
||||
'</div>' +
|
||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
||||
'<span style="color: #94a3b8;">Sözleşme Poliçesi:</span>' +
|
||||
'<span style="color: #94a3b8;">' + vehicle.label_policy + '</span>' +
|
||||
'<span style="color: ' + (vehicle.has_free_use ? '#38bdf8' : '#f59e0b') + '; font-weight: 600;">' + vehicle.policy_desc + '</span>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
@@ -331,11 +331,11 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
: (v.is_manager_approved
|
||||
? 'background: rgba(56, 189, 248, 0.2); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.4);'
|
||||
: 'background: rgba(16, 185, 129, 0.2); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.4);');
|
||||
var statusLabel = isRev ? ('🚨 UYUMSUZ (' + v.deviation + ' km)') : (v.is_manager_approved ? '✅ Onaylı Rota' : '✅ Uyumlu');
|
||||
var statusLabel = isRev ? (v.badge_violation) : (v.is_manager_approved ? v.badge_approved : v.badge_ok);
|
||||
|
||||
tr.innerHTML =
|
||||
'<td>' +
|
||||
'<strong style="color: white;">' + (v.driver_tag || '') + ' - ' + v.driver_name + '</strong><br/>' +
|
||||
'<strong style="color: white;">' + (v.driver_tag_str || '') + ' - ' + v.driver_name + '</strong><br/>' +
|
||||
'<span style="color: #94a3b8; font-size: 11px;">' + v.license_plate + '</span>' +
|
||||
'</td>' +
|
||||
'<td>' +
|
||||
|
||||
68
addons/mymach_fleet_intelligence/views/fuel_index_views.xml
Normal file
68
addons/mymach_fleet_intelligence/views/fuel_index_views.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_fuel_index_tree" model="ir.ui.view">
|
||||
<field name="name">mymach.fleet.fuel.index.tree</field>
|
||||
<field name="model">mymach.fleet.fuel.index</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Akaryakıt Enflasyon Endeksi">
|
||||
<field name="date" />
|
||||
<field name="fuel_type" />
|
||||
<field name="unit_price" widget="monetary" />
|
||||
<field name="mom_change_perc" widget="percentage" decoration-danger="mom_change_perc > 0" decoration-success="mom_change_perc <= 0" />
|
||||
<field name="yoy_change_perc" widget="percentage" decoration-danger="yoy_change_perc > 0" decoration-success="yoy_change_perc <= 0" />
|
||||
<field name="source" />
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_fuel_index_form" model="ir.ui.view">
|
||||
<field name="name">mymach.fleet.fuel.index.form</field>
|
||||
<field name="model">mymach.fleet.fuel.index</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Akaryakıt Fiyat Girişi">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" readonly="1" />
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="date" />
|
||||
<field name="fuel_type" />
|
||||
<field name="unit_price" widget="monetary" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="mom_change_perc" widget="percentage" />
|
||||
<field name="yoy_change_perc" widget="percentage" />
|
||||
<field name="source" />
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_fuel_index" model="ir.actions.act_window">
|
||||
<field name="name">Akaryakıt Enflasyon Endeksi</field>
|
||||
<field name="res_model">mymach.fleet.fuel.index</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Sistem henüz bir akaryakıt endeksi kaydetmemiş.
|
||||
</p>
|
||||
<p>
|
||||
Günlük yakıt fiyatları buradan takip edilir ve enflasyon oranları otomatik hesaplanır.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Ana Pano Configuration Menüsüne veya Ekonomi Alt Menüsüne Eklenebilir -->
|
||||
<menuitem id="menu_mymach_fleet_fuel_index"
|
||||
name="Ekonomi & Enflasyon"
|
||||
parent="menu_mymach_fleet_intelligence_root"
|
||||
action="action_fuel_index"
|
||||
sequence="90"/>
|
||||
|
||||
</odoo>
|
||||
@@ -53,10 +53,17 @@ action = {
|
||||
<div class="mymach_zara_overlay"></div>
|
||||
|
||||
<!-- Floating Content Wrapper -->
|
||||
<div class="mymach_zara_content">
|
||||
<div class="mymach_zara_content" style="position: relative;">
|
||||
|
||||
<!-- Dil Seçici (Top Right) -->
|
||||
<div style="position: absolute; top: 10px; right: 15px; width: 140px; z-index: 100;">
|
||||
<field name="dashboard_lang_id" widget="selection" style="background: rgba(15, 23, 42, 0.6) !important; color: white !important; border: 1px solid rgba(255, 255, 255, 0.15) !important; border-radius: 6px; padding: 4px; width: 100%; display: block; margin-bottom: 5px;"/>
|
||||
<button name="action_apply_language" type="object" string="🌐 Dili Değiştir" class="btn btn-sm btn-outline-light" style="width: 100%; font-size: 11px; padding: 3px;"/>
|
||||
</div>
|
||||
|
||||
<!-- Zara-Style Minimalist Editorial Header -->
|
||||
<div style="text-align: center; margin-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.08); padding-bottom: 25px;">
|
||||
<h1 class="mymach_zara_title" style="color: #ef4444 !important; text-shadow: 0 0 15px rgba(239, 68, 68, 0.5); font-weight: 800;">MYMACH FLEET INTELLIGENCE</h1>
|
||||
<h1 class="mymach_zara_title" style="color: #ffffff !important; text-shadow: 0 0 15px rgba(255, 255, 255, 0.5); font-weight: 800;">MYMACH FLEET INTELLIGENCE</h1>
|
||||
<field name="display_title" readonly="1" class="mymach_zara_subtitle" style="border: none; background: transparent; color: #cbd5e1 !important; text-align: center; display: block; width: 100%; pointer-events: none; text-shadow: 0 1px 5px rgba(0,0,0,0.5);"/>
|
||||
</div>
|
||||
|
||||
@@ -130,7 +137,7 @@ action = {
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<button name="%(mymach_fleet_intelligence.action_mymach_fleet_maintenance_cost)d" type="action" class="btn mymach_zara_btn" style="width: 100%;">
|
||||
<i class="fa fa-money mb-2"></i><br/> Gider Matrisi (Endeks)
|
||||
<i class="fa fa-money mb-2"></i><br/> Akaryakıt Giderleri
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
@@ -138,32 +145,15 @@ action = {
|
||||
<i class="fa fa-line-chart mb-2"></i><br/> Kilometre & Yakıt Grafiği
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<button name="%(mymach_fleet_intelligence.action_fuel_index)d" type="action" class="btn mymach_zara_btn" style="width: 100%;">
|
||||
<i class="fa fa-percent mb-2"></i><br/> Akaryakıt Endeksi & Enflasyon
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Finans Gider Matrisi & Araç Maliyet Analitiği -->
|
||||
<div style="margin-top: 20px; background: rgba(15, 23, 42, 0.4); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; padding: 20px;" groups="mymach_fleet_intelligence.group_mymach_fleet_manager">
|
||||
<h3 style="color: #10b981; font-size: 15px; font-weight: 600; margin-top: 0; margin-bottom: 15px; display: flex; align-items: center; gap: 8px;">
|
||||
💰 FİNANS GİDER MATRİSİ VE ARAÇ MALİYET ANALİTİĞİ
|
||||
</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="cost_matrix_vehicle_id" style="color: #cbd5e1; font-weight: 500; margin-bottom: 5px; display: block;">Maliyeti Hesaplanacak Aracı Seçin:</label>
|
||||
<field name="cost_matrix_vehicle_id" options="{'no_create': True, 'no_open': True}" style="background: rgba(15, 23, 42, 0.6) !important; color: white !important; border: 1px solid rgba(255, 255, 255, 0.15) !important; border-radius: 6px; padding: 6px; width: 100%; display: block;"/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3" style="display: flex; align-items: flex-end;">
|
||||
<button name="action_save_settings" string="Seçilen Araç İçin Hesapla ve Güncelle" type="object" class="btn btn-success" style="width: 100%; background: #10b981; color: white; border: none; font-weight: bold; padding: 8px;"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top: 10px;">
|
||||
<field name="cost_matrix_cards_html" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Periyodik Bakım Canlı İzleme Tablosu -->
|
||||
<div style="margin-top: 20px;">
|
||||
<field name="upcoming_maintenance_html" readonly="1"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</sheet>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
<!-- Gider Matrisi Aksiyonu -->
|
||||
<record id="action_mymach_fleet_maintenance_cost" model="ir.actions.act_window">
|
||||
<field name="name">Gider Matrisi (Araç Bazlı Analiz)</field>
|
||||
<field name="name">Akaryakıt Giderleri (Araç Bazlı Analiz)</field>
|
||||
<field name="res_model">mymach.fleet.maintenance.cost</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
<field name="context">{'search_default_group_by_vehicle': 1}</field>
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
<!-- Alt Menü -->
|
||||
<menuitem id="menu_mymach_fleet_maintenance_cost"
|
||||
name="Finans & Gider Matrisi"
|
||||
name="Akaryakıt Giderleri"
|
||||
parent="menu_mymach_fleet_intelligence_root"
|
||||
action="action_mymach_fleet_maintenance_cost"
|
||||
sequence="2"
|
||||
|
||||
Reference in New Issue
Block a user