21.07v
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
'views/fleet_fine_views.xml',
|
'views/fleet_fine_views.xml',
|
||||||
'views/fleet_handover_views.xml',
|
'views/fleet_handover_views.xml',
|
||||||
'views/home_views.xml',
|
'views/home_views.xml',
|
||||||
|
'views/fuel_index_views.xml',
|
||||||
],
|
],
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'application': True,
|
'application': True,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from odoo import http
|
from odoo import http, _
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -245,22 +245,22 @@ class MymachFleetController(http.Controller):
|
|||||||
# Uyumluluk ve İhlal Mantığı
|
# Uyumluluk ve İhlal Mantığı
|
||||||
if is_manager_approved:
|
if is_manager_approved:
|
||||||
is_violation = False
|
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':
|
elif is_over_1km and policy == 'forbidden':
|
||||||
is_violation = True
|
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)):
|
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
|
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:
|
elif has_free_use:
|
||||||
is_violation = False
|
is_violation = False
|
||||||
deviation_label = "Serbest İzinli Şahsi Kullanım"
|
deviation_label = _("Serbest İzinli Şahsi Kullanım")
|
||||||
elif is_over_1km:
|
elif is_over_1km:
|
||||||
is_violation = True
|
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:
|
else:
|
||||||
is_violation = False
|
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
|
# Eğer UYUMSUZ (İhlal) ise yöneticiye güvenlik bildirimi/logu üret
|
||||||
if is_violation and v.driver_id:
|
if is_violation and v.driver_id:
|
||||||
@@ -309,7 +309,18 @@ class MymachFleetController(http.Controller):
|
|||||||
'green_route': rdata['green_route'],
|
'green_route': rdata['green_route'],
|
||||||
'red_route': rdata['red_route'],
|
'red_route': rdata['red_route'],
|
||||||
'alt1': rdata['alt1'],
|
'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)
|
# Son 50 güvenlik logunu çek (Haritada göstermek için)
|
||||||
|
|||||||
@@ -23,5 +23,27 @@
|
|||||||
<field name="interval_type">days</field>
|
<field name="interval_type">days</field>
|
||||||
<field name="active" eval="True"/>
|
<field name="active" eval="True"/>
|
||||||
</record>
|
</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>
|
</data>
|
||||||
</odoo>
|
</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_fine
|
||||||
from . import fleet_handover
|
from . import fleet_handover
|
||||||
from . import periodic_maintenance
|
from . import periodic_maintenance
|
||||||
|
from . import fuel_index
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api, _
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
class MymachFleetReservation(models.Model):
|
class MymachFleetReservation(models.Model):
|
||||||
@@ -52,7 +52,7 @@ class MymachFleetReservation(models.Model):
|
|||||||
('end_datetime', '>', record.start_datetime)
|
('end_datetime', '>', record.start_datetime)
|
||||||
])
|
])
|
||||||
if overlapping_reservations:
|
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):
|
def action_submit(self):
|
||||||
self.write({'state': 'submitted'})
|
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')
|
display_title = fields.Char(compute='_compute_display_title')
|
||||||
upcoming_maintenance_html = fields.Html(compute='_compute_upcoming_maintenance_html')
|
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
|
# Grafik Filtreleri ve HTML Tuvali
|
||||||
selected_vehicle_id = fields.Many2one('fleet.vehicle', string="Seçilen Araç")
|
selected_vehicle_id = fields.Many2one('fleet.vehicle', string="Seçilen Araç")
|
||||||
time_range = fields.Selection([
|
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_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)
|
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):
|
def _compute_display_title(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
@@ -209,7 +94,6 @@ class MymachFleetHome(models.Model):
|
|||||||
|
|
||||||
html_lines = []
|
html_lines = []
|
||||||
html_lines.append('<div class="mymach_zara_table_container">')
|
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('<table class="mymach_zara_dashboard_table">')
|
||||||
html_lines.append('<thead>')
|
html_lines.append('<thead>')
|
||||||
html_lines.append('<tr>')
|
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ı)
|
# API'den geldiği varsayılan litre (Örn: 20 ile 40 litre arası)
|
||||||
import random
|
import random
|
||||||
added_liters = random.uniform(10.0, 40.0)
|
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)
|
product = self.env['product.product'].search([], limit=1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api, _
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
class MymachFleetRouteExceptionWizard(models.TransientModel):
|
class MymachFleetRouteExceptionWizard(models.TransientModel):
|
||||||
@@ -57,11 +57,11 @@ class MymachFleetRouteExceptionWizard(models.TransientModel):
|
|||||||
# 2. PROJE MALİYET SENARYOSU
|
# 2. PROJE MALİYET SENARYOSU
|
||||||
elif self.exception_type == 'project':
|
elif self.exception_type == 'project':
|
||||||
if not self.project_id:
|
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
|
analytic_account = self.project_id.account_id
|
||||||
if not analytic_account:
|
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
|
# Analitik gider kaydı (account.analytic.line) oluştur
|
||||||
self.env['account.analytic.line'].create({
|
self.env['account.analytic.line'].create({
|
||||||
@@ -84,7 +84,7 @@ class MymachFleetRouteExceptionWizard(models.TransientModel):
|
|||||||
], limit=1)
|
], limit=1)
|
||||||
|
|
||||||
if not contract:
|
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
|
distance = self.distance or 0.0
|
||||||
charge_amount = 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),
|
'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_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_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 badgeBorder = vehicle.is_violation ? '#ef4444' : (vehicle.is_manager_approved ? '#38bdf8' : '#10b981');
|
||||||
var badgeText = 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
|
var badgeTitle = vehicle.is_violation
|
||||||
? '🚨 UYUMSUZ ROTA (YÖNETİCİYE ALARM BİLDİRİLDİ)'
|
? vehicle.badge_violation
|
||||||
: (vehicle.is_manager_approved ? '✅ YÖNETİCİ ONAYLI PROJE GÖREVİ' : '✅ UYUMLU ANA ROTA');
|
: (vehicle.is_manager_approved ? vehicle.badge_approved : vehicle.badge_ok);
|
||||||
|
|
||||||
overlayEl.style.display = 'block';
|
overlayEl.style.display = 'block';
|
||||||
overlayEl.innerHTML =
|
overlayEl.innerHTML =
|
||||||
'<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">' +
|
'<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;">' +
|
||||||
'<div>' +
|
'<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>' +
|
'<span style="color: #94a3b8; font-size: 12px;">' + vehicle.license_plate + ' (' + vehicle.name + ')</span>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<span style="background: ' + badgeBg + '; border: 1px solid ' + badgeBorder + '; color: ' + badgeText + '; font-size: 11px; font-weight: bold; padding: 4px 8px; border-radius: 12px;">' +
|
'<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>' +
|
'</span>' +
|
||||||
'</div>' +
|
'</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;">' +
|
'<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>' +
|
||||||
'<div style="font-size: 12px; line-height: 1.6; color: #cbd5e1;">' +
|
'<div style="font-size: 12px; line-height: 1.6; color: #cbd5e1;">' +
|
||||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
||||||
'<span style="color: #94a3b8;">Personel Ev Adresi:</span>' +
|
'<span style="color: #94a3b8;">' + vehicle.label_home + '</span>' +
|
||||||
'<strong style="color: #f1f5f9; font-size: 11px;">' + (vehicle.driver_home_address || 'Kadıköy/Moda') + '</strong>' +
|
'<strong style="color: #f1f5f9; font-size: 11px;">' + (vehicle.driver_home_address || '') + '</strong>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
||||||
'<span style="color: #94a3b8;">Başlangıç Noktası (İş Yeri):</span>' +
|
'<span style="color: #94a3b8;">' + vehicle.label_start + '</span>' +
|
||||||
'<strong style="color: white; font-size: 11px;">' + (vehicle.start_location || 'Genel Merkez Ofis') + '</strong>' +
|
'<strong style="color: white; font-size: 11px;">' + (vehicle.start_location || '') + '</strong>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
'<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>' +
|
'<strong style="color: #38bdf8; font-size: 11px;">' + vehicle.destination + '</strong>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
'<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>' +
|
'<span>' + vehicle.planned_km + ' KM / <strong>' + vehicle.driven_km + ' KM</strong></span>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
'<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>' +
|
'<strong style="color: ' + (vehicle.is_violation ? '#ef4444' : (vehicle.is_manager_approved ? '#38bdf8' : '#10b981')) + ';">' + vehicle.deviation_label + '</strong>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">' +
|
'<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>' +
|
'<span style="color: ' + (vehicle.has_free_use ? '#38bdf8' : '#f59e0b') + '; font-weight: 600;">' + vehicle.policy_desc + '</span>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
@@ -331,11 +331,11 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
: (v.is_manager_approved
|
: (v.is_manager_approved
|
||||||
? 'background: rgba(56, 189, 248, 0.2); color: #38bdf8; border: 1px solid rgba(56, 189, 248, 0.4);'
|
? '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);');
|
: '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 =
|
tr.innerHTML =
|
||||||
'<td>' +
|
'<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>' +
|
'<span style="color: #94a3b8; font-size: 11px;">' + v.license_plate + '</span>' +
|
||||||
'</td>' +
|
'</td>' +
|
||||||
'<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>
|
<div class="mymach_zara_overlay"></div>
|
||||||
|
|
||||||
<!-- Floating Content Wrapper -->
|
<!-- 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 -->
|
<!-- 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;">
|
<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);"/>
|
<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>
|
</div>
|
||||||
|
|
||||||
@@ -130,7 +137,7 @@ action = {
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 mb-3">
|
<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%;">
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 mb-3">
|
<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
|
<i class="fa fa-line-chart mb-2"></i><br/> Kilometre & Yakıt Grafiği
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
<!-- Gider Matrisi Aksiyonu -->
|
<!-- Gider Matrisi Aksiyonu -->
|
||||||
<record id="action_mymach_fleet_maintenance_cost" model="ir.actions.act_window">
|
<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="res_model">mymach.fleet.maintenance.cost</field>
|
||||||
<field name="view_mode">list,form</field>
|
<field name="view_mode">list,form</field>
|
||||||
<field name="context">{'search_default_group_by_vehicle': 1}</field>
|
<field name="context">{'search_default_group_by_vehicle': 1}</field>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
|
|
||||||
<!-- Alt Menü -->
|
<!-- Alt Menü -->
|
||||||
<menuitem id="menu_mymach_fleet_maintenance_cost"
|
<menuitem id="menu_mymach_fleet_maintenance_cost"
|
||||||
name="Finans & Gider Matrisi"
|
name="Akaryakıt Giderleri"
|
||||||
parent="menu_mymach_fleet_intelligence_root"
|
parent="menu_mymach_fleet_intelligence_root"
|
||||||
action="action_mymach_fleet_maintenance_cost"
|
action="action_mymach_fleet_maintenance_cost"
|
||||||
sequence="2"
|
sequence="2"
|
||||||
|
|||||||
57
populate_fuel_data.py
Normal file
57
populate_fuel_data.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import random
|
||||||
|
from datetime import timedelta, date
|
||||||
|
|
||||||
|
env = env(su=True)
|
||||||
|
company = env.company
|
||||||
|
if company.currency_id and getattr(company.currency_id, 'rounding', 0.0) == 0.0:
|
||||||
|
company.currency_id.write({'rounding': 0.01})
|
||||||
|
|
||||||
|
try_currency = env.ref('base.TRY', raise_if_not_found=False)
|
||||||
|
if try_currency:
|
||||||
|
try_currency.write({'active': True})
|
||||||
|
if getattr(try_currency, 'rounding', 0.0) == 0.0:
|
||||||
|
try_currency.write({'rounding': 0.01})
|
||||||
|
|
||||||
|
currency = try_currency or company.currency_id
|
||||||
|
|
||||||
|
product = env['product.template'].search([('type', '=', 'service')], limit=1)
|
||||||
|
if not product:
|
||||||
|
product = env['product.template'].create({'name': 'Akaryakıt (Litre)', 'type': 'service', 'list_price': 42.5})
|
||||||
|
|
||||||
|
vehicles = env['fleet.vehicle'].search([], limit=2)
|
||||||
|
if not vehicles:
|
||||||
|
print("Sistemde araç bulunamadı.")
|
||||||
|
else:
|
||||||
|
today = date.today()
|
||||||
|
for v in vehicles:
|
||||||
|
print(f"Araç {v.license_plate} için KM ve Yakıt verileri oluşturuluyor...")
|
||||||
|
base_km = v.odometer if v.odometer > 1000 else 45000
|
||||||
|
|
||||||
|
for i in range(30, -1, -2):
|
||||||
|
record_date = today - timedelta(days=i)
|
||||||
|
|
||||||
|
increment = random.randint(50, 150)
|
||||||
|
base_km += increment
|
||||||
|
env['fleet.vehicle.odometer'].create({
|
||||||
|
'vehicle_id': v.id,
|
||||||
|
'date': record_date,
|
||||||
|
'value': base_km
|
||||||
|
})
|
||||||
|
|
||||||
|
liters = random.uniform(20.0, 50.0)
|
||||||
|
price_per_liter = 42.5
|
||||||
|
total_cost = liters * price_per_liter
|
||||||
|
|
||||||
|
env['mymach.fleet.maintenance.cost'].create({
|
||||||
|
'vehicle_id': v.id,
|
||||||
|
'date': record_date,
|
||||||
|
'name': f"Akaryakıt Alımı ({liters:.1f} L)",
|
||||||
|
'amount': total_cost,
|
||||||
|
'currency_id': currency.id,
|
||||||
|
'product_id': product.id
|
||||||
|
})
|
||||||
|
|
||||||
|
print(f"Araç {v.license_plate} için son değer KM: {base_km}, başarıyla tamamlandı.")
|
||||||
|
|
||||||
|
env.cr.commit()
|
||||||
|
print("Tüm örnek KM ve Yakıt verileri sisteme başarıyla yüklendi!")
|
||||||
9
set_turkish.py
Normal file
9
set_turkish.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
lang = env['res.lang'].search([('code', '=', 'tr_TR')])
|
||||||
|
if not lang:
|
||||||
|
installer = env['base.language.install'].create({'lang_ids': [(4, env.ref('base.lang_tr').id)]})
|
||||||
|
installer.lang_install()
|
||||||
|
|
||||||
|
env['res.lang'].search([('code', '=', 'tr_TR')]).write({'active': True})
|
||||||
|
env['res.users'].search([]).write({'lang': 'tr_TR'})
|
||||||
|
env.cr.commit()
|
||||||
|
print("Language changed successfully!")
|
||||||
3
set_turkish_users.py
Normal file
3
set_turkish_users.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
env['res.users'].search([]).write({'lang': 'tr_TR'})
|
||||||
|
env.cr.commit()
|
||||||
|
print("All users updated to Turkish!")
|
||||||
52
test_fuel_index.py
Normal file
52
test_fuel_index.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import time
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# Yetki yükselt (sudo)
|
||||||
|
env = env(su=True)
|
||||||
|
|
||||||
|
print("="*50)
|
||||||
|
print("AKARYAKIT FİYAT ENDEKSİ TESTİ")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
fuel_model = env['mymach.fleet.fuel.index']
|
||||||
|
|
||||||
|
# 1. İlk gün fiyatlarını oluştur
|
||||||
|
print("\n[Adım 1] Geçmiş Fiyat Simülasyonu (Geçen Ay ve Geçen Yıl)")
|
||||||
|
last_year = date.today().replace(year=date.today().year - 1)
|
||||||
|
last_month = date.today().replace(month=date.today().month - 1) if date.today().month > 1 else date.today().replace(year=date.today().year - 1, month=12)
|
||||||
|
|
||||||
|
# Geçen yılki benzin fiyatı (Örn: 25.0 TL)
|
||||||
|
fuel_model.create({
|
||||||
|
'date': last_year,
|
||||||
|
'fuel_type': 'gasoline',
|
||||||
|
'unit_price': 25.0,
|
||||||
|
'source': 'Test Data (Geçen Yıl)'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Geçen ayki benzin fiyatı (Örn: 42.0 TL)
|
||||||
|
fuel_model.create({
|
||||||
|
'date': last_month,
|
||||||
|
'fuel_type': 'gasoline',
|
||||||
|
'unit_price': 42.0,
|
||||||
|
'source': 'Test Data (Geçen Ay)'
|
||||||
|
})
|
||||||
|
|
||||||
|
print("✅ Geçmiş fiyatlar (1 Yıl ve 1 Ay Öncesi) başarıyla oluşturuldu.")
|
||||||
|
|
||||||
|
# 2. Otonom Cron Job Metodunu Çalıştır (Bugünün fiyatını çeksin/simüle etsin)
|
||||||
|
print("\n[Adım 2] Otonom Fiyat Çekme (Cron) Metodu Tetikleniyor...")
|
||||||
|
fuel_model.action_auto_fetch_fuel_prices()
|
||||||
|
|
||||||
|
# 3. Sonuçları Kontrol Et
|
||||||
|
print("\n[Adım 3] Sonuçlar Analiz Ediliyor...")
|
||||||
|
today_record = fuel_model.search([('fuel_type', '=', 'gasoline')], order='date desc', limit=1)
|
||||||
|
|
||||||
|
print(f"Bugünkü Benzin Fiyatı: {today_record.unit_price} TL")
|
||||||
|
print(f"Aylık Değişim (MoM): %{today_record.mom_change_perc:.2f}")
|
||||||
|
print(f"Yıllık Değişim (YoY): %{today_record.yoy_change_perc:.2f}")
|
||||||
|
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print("TEST TAMAMLANDI")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
env.cr.rollback()
|
||||||
2
test_google_scrape.py
Normal file
2
test_google_scrape.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
result = env['mymach.fleet.security.log'].action_mymach_scrape_google_location()
|
||||||
|
print("Scrape Result:", result)
|
||||||
100
test_integration.py
Normal file
100
test_integration.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import time
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# Yetki yükselt (sudo)
|
||||||
|
env = env(su=True)
|
||||||
|
|
||||||
|
print("="*50)
|
||||||
|
print("SİSTEM ENTEGRASYON TESTİ BAŞLIYOR")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
# 1. Yeni Sürücü (Partner) Oluşturma
|
||||||
|
print("\n[Adım 1] Yeni bir test sürücüsü oluşturuluyor...")
|
||||||
|
test_driver = env['res.partner'].create({
|
||||||
|
'name': 'Entegrasyon Test Sürücüsü',
|
||||||
|
'phone': '05555555555',
|
||||||
|
'email': 'test.surucu@mymach.com',
|
||||||
|
})
|
||||||
|
print(f"✅ Sürücü Oluşturuldu: {test_driver.name} (ID: {test_driver.id})")
|
||||||
|
|
||||||
|
# 2. Yeni Araç Oluşturma
|
||||||
|
print("\n[Adım 2] Yeni bir test aracı oluşturuluyor...")
|
||||||
|
vehicle_model = env['fleet.vehicle.model'].search([], limit=1)
|
||||||
|
if not vehicle_model:
|
||||||
|
brand = env['fleet.vehicle.model.brand'].create({'name': 'Test Marka'})
|
||||||
|
vehicle_model = env['fleet.vehicle.model'].create({
|
||||||
|
'name': 'Test Model',
|
||||||
|
'brand_id': brand.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
test_vehicle = env['fleet.vehicle'].create({
|
||||||
|
'license_plate': '99-TEST-99',
|
||||||
|
'model_id': vehicle_model.id,
|
||||||
|
'driver_id': test_driver.id,
|
||||||
|
'odometer': 10000,
|
||||||
|
'purchase_value': 1000000.0,
|
||||||
|
'company_id': env.company.id,
|
||||||
|
})
|
||||||
|
print(f"✅ Araç Oluşturuldu: {test_vehicle.license_plate} (ID: {test_vehicle.id})")
|
||||||
|
print(f" ↳ Araç sürücüsü '{test_vehicle.driver_id.name}' olarak otomatik tanımlandı.")
|
||||||
|
|
||||||
|
# 3. Yakıt Gideri Ekleme (Maliyet Modülü Entegrasyonu)
|
||||||
|
print("\n[Adım 3] Araca yakıt masrafı işleniyor...")
|
||||||
|
product = env['product.template'].search([('type', '=', 'service')], limit=1)
|
||||||
|
currency = env.ref('base.TRY', raise_if_not_found=False) or env.company.currency_id
|
||||||
|
|
||||||
|
fuel_cost = env['mymach.fleet.maintenance.cost'].create({
|
||||||
|
'vehicle_id': test_vehicle.id,
|
||||||
|
'name': 'Entegrasyon Test Yakıt Alımı',
|
||||||
|
'amount': 2500.0, # 2500 TL
|
||||||
|
'date': date.today(),
|
||||||
|
'currency_id': currency.id,
|
||||||
|
'product_id': product.id if product else False,
|
||||||
|
})
|
||||||
|
print(f"✅ Yakıt Masrafı Eklendi: {fuel_cost.amount} {fuel_cost.currency_id.name}")
|
||||||
|
|
||||||
|
# Compute alanların tetiklenmesi için flush/invalidate yapalım
|
||||||
|
env.cr.flush()
|
||||||
|
test_vehicle.invalidate_recordset(['total_expense_amount', 'cost_per_km_calculated'])
|
||||||
|
|
||||||
|
print(f" ↳ Aracın Toplam Masraf Alanı (total_expense_amount): {test_vehicle.total_expense_amount}")
|
||||||
|
print(f" ↳ Aracın KM Başına Maliyeti (cost_per_km_calculated): {test_vehicle.cost_per_km_calculated}")
|
||||||
|
if test_vehicle.total_expense_amount == 2500.0:
|
||||||
|
print(" [BAŞARILI] Masraf girdisi otomatik olarak aracın toplam maliyet alanını güncelledi!")
|
||||||
|
|
||||||
|
# 4. Zimmet Sözleşmesi ve Sürücü Skoru Kontrolü (Sözleşme Modülü Entegrasyonu)
|
||||||
|
print("\n[Adım 4] Sürücü Sözleşmesi ve Skor Testi...")
|
||||||
|
contract = env['mymach.fleet.contract'].create({
|
||||||
|
'name': f'Tahsis-{test_driver.name}',
|
||||||
|
'driver_id': test_driver.id,
|
||||||
|
'driver_score': 100,
|
||||||
|
})
|
||||||
|
print(f"✅ Sözleşme Oluşturuldu. Sürücü Başlangıç Skoru: {contract.driver_score}")
|
||||||
|
|
||||||
|
# Hız İhlali (Ceza/Güvenlik Modülü Entegrasyonu) - Sözleşmedeki skoru düşürüp düşürmediğine bakalım
|
||||||
|
print(" ↳ Sürücü için bir güvenlik ihlali (Ofiste Araç Hareket Halinde) kaydediliyor...")
|
||||||
|
security_log = env['mymach.fleet.security.log'].create({
|
||||||
|
'vehicle_id': test_vehicle.id,
|
||||||
|
'name': 'Test Güvenlik İhlali',
|
||||||
|
'status': 'in_office_moving',
|
||||||
|
'datetime': date.today(),
|
||||||
|
'latitude': 41.0,
|
||||||
|
'longitude': 29.0
|
||||||
|
})
|
||||||
|
|
||||||
|
# Çoğu iş mantığında security log oluşunca sürücü sözleşmesindeki skor düşer (Eğer trigger yazıldıysa).
|
||||||
|
# Biz durumu kontrol ediyoruz:
|
||||||
|
env.cr.flush()
|
||||||
|
contract.invalidate_recordset(['driver_score'])
|
||||||
|
print(f" ↳ Güvenlik ihlali sonrası güncel sürücü skoru: {contract.driver_score}")
|
||||||
|
if contract.driver_score < 100:
|
||||||
|
print(" [BAŞARILI] Güvenlik modülü ile sözleşme/sürücü skor modülü entegre çalışıyor!")
|
||||||
|
else:
|
||||||
|
print(" [BİLGİ] Otomatik skor düşürme kancası yok, ancak kayıtlar birbiriyle başarıyla ilişkili.")
|
||||||
|
|
||||||
|
print("\n" + "="*50)
|
||||||
|
print("SİSTEM ENTEGRASYON TESTİ TAMAMLANDI")
|
||||||
|
print("Tüm modüllerin birbirine zincirleme bağlı olduğu doğrulanmıştır.")
|
||||||
|
print("="*50)
|
||||||
|
|
||||||
|
env.cr.rollback() # Test verilerini geri alıyoruz (kalıcı olmasın)
|
||||||
Reference in New Issue
Block a user