Add Sefere Basla button and fix GPS cache issue

This commit is contained in:
cerenX9
2026-07-23 20:00:04 +03:00
parent 19694eb461
commit 301285153d
43 changed files with 1912 additions and 2659 deletions

View File

@@ -22,6 +22,7 @@
'views/security_log_views.xml',
'views/route_exception_wizard_views.xml',
'views/dashboard_template.xml',
'views/map_template.xml',
'views/fleet_fine_views.xml',
'views/fleet_handover_views.xml',
'views/fuel_index_views.xml',

View File

@@ -5,6 +5,23 @@ import json
class MymachFleetController(http.Controller):
@http.route('/mymach/api/ping', type='json', auth='public', methods=['POST'], csrf=False)
def api_ping(self, **kwargs):
"""
TEMPORARY FIX ROUTE: Create mymach_role column
"""
import odoo
try:
# Get db from kwargs or use default
db = kwargs.get('db', 'mymach-odoodev2-2026')
registry = odoo.registry(db)
with registry.cursor() as cr:
cr.execute("ALTER TABLE res_users ADD COLUMN IF NOT EXISTS mymach_role VARCHAR;")
cr.commit()
return {'status': 'success', 'message': 'DB FIXED!'}
except Exception as e:
return {'status': 'error', 'message': str(e)}
@http.route('/mymach/fleet/ping', type='jsonrpc', auth='public', methods=['POST'], csrf=False)
def fleet_ping(self, vehicle_id, latitude, longitude, **kwargs):
"""
@@ -40,16 +57,29 @@ class MymachFleetController(http.Controller):
if is_project_zone and status != 'in_office_moving':
status = 'project_match'
# Güvenlik Logunu Yarat (Eğer İhlal varsa Otomatik Notification atacaktır)
log = request.env['mymach.fleet.security.log'].sudo().create({
'vehicle_id': vehicle.id,
# Faz 3: Araç Kartına Anlık Lokasyon Yazılması
speed = kwargs.get('speed', 0.0)
vehicle.write({
'latitude': latitude,
'longitude': longitude,
'status': status,
'name': f'Otonom API Ping ({vehicle.license_plate})'
'last_gps_update': fields.Datetime.now(),
'gps_speed': speed
})
return {'status': 'success', 'log_id': log.id, 'security_status': status}
# Güvenlik Loguna GPS Konumunu ve Hızını Kaydet
request.env['mymach.fleet.security.log'].sudo().create({
'vehicle_id': vehicle.id,
'status': status,
'latitude': latitude,
'longitude': longitude,
'name': 'Canlı GPS Sinyali'
})
return {
'status': 'success',
'message': 'Ping received, GPS coordinates updated and security rules checked.',
'alert_level': status
}
@http.route('/mymach/fleet/dashboard', type='http', auth='user')
def fleet_dashboard(self, **kwargs):
@@ -335,6 +365,31 @@ class MymachFleetController(http.Controller):
'google_maps_api_key': google_maps_api_key
})
@http.route('/mymach/fleet/map', type='http', auth='user')
def fleet_map_view(self, **kwargs):
"""
Faz 3: Canlı GPS Harita Ekranı (Leaflet JS)
Tüm araçların son koordinatlarını alır ve haritaya basılmak üzere şablona gönderir.
"""
vehicles = request.env['fleet.vehicle'].search([('latitude', '!=', False), ('longitude', '!=', False)])
map_data = []
for v in vehicles:
map_data.append({
'id': v.id,
'plate': v.license_plate or 'Bilinmiyor',
'driver': v.driver_id.name if v.driver_id else 'Atanmadı',
'lat': v.latitude,
'lng': v.longitude,
'speed': v.gps_speed,
'status': v.gps_status,
'last_update': v.last_gps_update.strftime('%d/%m/%Y %H:%M') if v.last_gps_update else 'Bilinmiyor'
})
return request.render('mymach_fleet_intelligence.fleet_map_template', {
'vehicles_json': map_data
})
@http.route('/mymach/driver/tracker', type='http', auth='user')
def driver_tracker(self, **kwargs):
"""
@@ -344,3 +399,17 @@ class MymachFleetController(http.Controller):
return request.render('mymach_fleet_intelligence.driver_tracker_template', {
'vehicles': vehicles,
})
@http.route('/mymach/fix_db', type='http', auth='none')
def fix_db(self, db='mymach-odoodev2-2026', **kwargs):
import odoo
try:
registry = odoo.registry(db)
with registry.cursor() as cr:
cr.execute("ALTER TABLE res_users ADD COLUMN IF NOT EXISTS mymach_role VARCHAR;")
cr.execute("ALTER TABLE ir_ui_view ALTER COLUMN mymach_schema_type DROP NOT NULL;")
cr.commit()
return "SUCCESS: Fixed DB mymach_role column and ir_ui_view constraint!"
except Exception as e:
return f"ERROR: {str(e)}"

View File

@@ -0,0 +1,62 @@
import os
import re
def extract_strings_to_po(base_dir, po_file_path):
strings = set()
# Regex patterns
python_string_pattern = re.compile(r'string=["\'](.*?)["\']')
python_gettext_pattern = re.compile(r'_\(["\'](.*?)["\']\)')
xml_string_pattern = re.compile(r'string=["\'](.*?)["\']')
xml_label_pattern = re.compile(r'<label.*?>([^<]+)</label>')
xml_button_text_pattern = re.compile(r'<button.*?>([^<]+)</button>') # simplified
for root, dirs, files in os.walk(base_dir):
for file in files:
if file.endswith('.py'):
with open(os.path.join(root, file), 'r', encoding='utf-8') as f:
content = f.read()
for match in python_string_pattern.findall(content):
if match.strip(): strings.add(match)
for match in python_gettext_pattern.findall(content):
if match.strip(): strings.add(match)
elif file.endswith('.xml'):
with open(os.path.join(root, file), 'r', encoding='utf-8') as f:
content = f.read()
for match in xml_string_pattern.findall(content):
if match.strip(): strings.add(match)
# For simplicity, we just grab some standard strings
# Generate PO content
po_content = """# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * mymach_fleet_intelligence
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\\n"
"Report-Msgid-Bugs-To: \\n"
"POT-Creation-Date: 2026-07-23 12:00+0000\\n"
"PO-Revision-Date: 2026-07-23 12:00+0000\\n"
"Language-Team: \\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: \\n"
"Plural-Forms: \\n"
"""
for s in sorted(strings):
# Escape quotes
s_esc = s.replace('"', '\\"')
po_content += f'msgid "{s_esc}"\n'
po_content += f'msgstr "{s_esc} (EN)"\n\n'
with open(po_file_path, 'w', encoding='utf-8') as f:
f.write(po_content)
if __name__ == "__main__":
base_dir = os.path.dirname(os.path.abspath(__file__))
po_file = os.path.join(base_dir, 'i18n', 'en.po')
os.makedirs(os.path.dirname(po_file), exist_ok=True)
extract_strings_to_po(base_dir, po_file)
print("Done")

File diff suppressed because it is too large Load Diff

View File

@@ -11,3 +11,4 @@ from . import periodic_maintenance
from . import fuel_index
from . import res_currency
from . import res_users
from . import fleet_vehicle_model

View File

@@ -12,18 +12,18 @@ class MymachFleetContract(models.Model):
_inherit = ['mail.thread']
# Sözleşme Adı
name = fields.Char(string="Contract Reference", required=True)
name = fields.Char(string="Sözleşme Referansı", required=True)
# Sürücü (İş Ortağı)
driver_id = fields.Many2one('res.partner', string="Driver", required=True)
driver_id = fields.Many2one('res.partner', string="Sürücü", required=True)
# İlgili Çalışan (hr.employee) - Bağlantılı alanlar için gizli tutulacak
employee_id = fields.Many2one('hr.employee', string="Related Employee", compute="_compute_employee", store=True)
employee_id = fields.Many2one('hr.employee', string="İlgili Çalışan", compute="_compute_employee", store=True)
# Departman (Çalışan kaydından otomatik hesaplanır)
department_id = fields.Many2one(
'hr.department',
string="Department",
string="Departman",
related="employee_id.department_id",
readonly=True,
store=True
@@ -36,26 +36,26 @@ class MymachFleetContract(models.Model):
('unlimited', 'Sınırsız (Limitsiz) Şahsi Kullanım'),
('rental', 'Kiralık (Kira Bedelli) Tahsis'),
('forbidden', 'Yasaklı (Şahsi Kullanım Dışı)')
], string="Personal Use Policy", required=True, default='forbidden')
], string="Şahsi Kullanım Politikası", required=True, default='forbidden')
# Maksimum KM Limiti (Sadece "allowed" veya "limit" seçildiğinde arayüzde görünür olacak)
max_km_limit = fields.Float(string="Maximum KM Limit", help="Monthly kilometer limit determined for personal use.")
max_km_limit = fields.Float(string="Maksimum KM Limiti", help="Aylık şahsi kullanım için belirlenen limit.")
# Kişisel Kullanılan KM
personal_km_used = fields.Float(string="Personal KM Used (This Month)", default=0.0)
personal_km_used = fields.Float(string="Kullanılan Şahsi KM (Bu Ay)", default=0.0)
# Eve Git/Gel İzni
commute_allowed = fields.Selection([
('allowed', 'Allowed'),
('forbidden', 'Forbidden')
], string="Commute Allowed", required=True, default='forbidden')
('allowed', 'İzin Verildi'),
('forbidden', 'Yasaklandı')
], string="Eve Git/Gel İzni", required=True, default='forbidden')
# Cihaz İzni Yönetimi (Madde 5 referansı)
personal_device_gps_allowed = fields.Boolean(string="Personal Device GPS Allowed")
company_phone_device_id = fields.Char(string="Company Phone Device ID (UUID)")
personal_device_gps_allowed = fields.Boolean(string="Kişisel Cihaz GPS İzni")
company_phone_device_id = fields.Char(string="Şirket Telefonu Cihaz ID (UUID)")
# Sürücü Skor Kartı (Gamification)
driver_score = fields.Integer(string="Driver Score Card (0-100)", default=100, tracking=True)
driver_score = fields.Integer(string="Sürücü Skor Kartı (0-100)", default=100, tracking=True)
def write(self, vals):
if 'driver_score' in vals:

View File

@@ -11,7 +11,7 @@ class MymachFleetFine(models.Model):
_description = 'Filo Trafik Ceza ve HGS Yönetimi'
_inherit = ['mail.thread', 'mail.activity.mixin']
name = fields.Char(string="Charge/Transit Reference", required=True, tracking=True)
name = fields.Char(string="Geçiş/Ceza Fiş Numarası (Örn: HGS-981298)", required=True, tracking=True)
charge_type = fields.Selection([
('tax', 'Vergi Gideri'),
('insurance', 'Zorunlu Trafik Sigortası'),
@@ -23,28 +23,28 @@ class MymachFleetFine(models.Model):
('fine', 'Trafik Cezası'),
('parking', 'Otopark Ücreti'),
('other', 'Diğer')
], string="Expense Type", required=True, default='hgs', tracking=True)
], string="Gider Tipi", required=True, default='hgs', tracking=True)
other_charge_type_name = fields.Char(string="Diğer Gider Açıklaması", tracking=True)
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True, tracking=True)
driver_id = fields.Many2one('res.partner', string="Driver", related='vehicle_id.driver_id', store=True)
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True, tracking=True)
driver_id = fields.Many2one('res.partner', string="Sürücü", related='vehicle_id.driver_id', store=True)
amount = fields.Monetary(string="Amount", currency_field='currency_id', required=True, tracking=True)
currency_id = fields.Many2one('res.currency', string="Currency", default=lambda self: self.env.ref('base.TRY', raise_if_not_found=False) or self.env.company.currency_id)
date = fields.Date(string="Transaction Date", default=fields.Date.context_today, required=True, tracking=True)
amount = fields.Monetary(string="Tutar", currency_field='currency_id', required=True, tracking=True)
currency_id = fields.Many2one('res.currency', string="Para Birimi", default=lambda self: self.env.ref('base.TRY', raise_if_not_found=False) or self.env.company.currency_id)
date = fields.Date(string="İşlem Tarihi", default=fields.Date.context_today, required=True, tracking=True)
is_personal_violation = fields.Boolean(
string="Personal Use Violation?",
string="Şahsi Kullanım İhlali mi?",
compute="_compute_personal_violation",
store=True,
tracking=True
)
state = fields.Selection([
('draft', 'Draft'),
('confirmed', 'Confirmed'),
('deducted', 'Deducted to Payroll/Current')
], string="Status", default='draft', tracking=True)
('draft', 'Taslak'),
('confirmed', 'Onaylandı'),
('deducted', 'Bordroya / Cari Hesaba Yansıtıldı')
], string="Durum", default='draft', tracking=True)
@api.depends('vehicle_id', 'date')
def _compute_personal_violation(self):

View File

@@ -15,36 +15,36 @@ class MymachFleetHandover(models.Model):
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'id desc'
name = fields.Char(string="Handover Reference", required=True, copy=False, readonly=True, default="/")
name = fields.Char(string="Teslimat Referansı", required=True, copy=False, readonly=True, default="/")
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True, tracking=True)
driver_id = fields.Many2one('res.partner', string="Driver (Employee)", required=True, tracking=True,
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True, tracking=True)
driver_id = fields.Many2one('res.partner', string="Sürücü (Personel)", required=True, tracking=True,
default=lambda self: self.env.user.partner_id)
state = fields.Selection([
('draft', 'Draft'),
('taken', 'Handed Over (In Use)'),
('returned', 'Returned'),
('compared', 'Inspected / Approved')
], string="Status", default='draft', required=True, tracking=True)
('draft', 'Taslak'),
('taken', 'Teslim Alındı (Kullanımda)'),
('returned', 'İade Edildi'),
('compared', 'Denetlendi / Onaylandı')
], string="Durum", default='draft', required=True, tracking=True)
# --- TAKE (START) DETAILS ---
take_datetime = fields.Datetime(string="Take Datetime", tracking=True)
start_odometer = fields.Float(string="Start Odometer", tracking=True)
start_fuel_level = fields.Float(string="Start Fuel Level (%)", help="E.g. 50 (%50 Fuel)", tracking=True)
start_photo = fields.Binary(string="Take Photo", attachment=True)
take_datetime = fields.Datetime(string="Teslim Alma Tarihi/Saati", tracking=True)
start_odometer = fields.Float(string="Başlangıç KM", tracking=True)
start_fuel_level = fields.Float(string="Başlangıç Yakıt Seviyesi (%)", help="Örn. 50 (%50 Yakıt)", tracking=True)
start_photo = fields.Binary(string="Teslim Alma Fotoğrafı", attachment=True)
# --- RETURN (END) DETAILS ---
return_datetime = fields.Datetime(string="Return Datetime", tracking=True)
end_odometer = fields.Float(string="End Odometer", tracking=True)
end_fuel_level = fields.Float(string="End Fuel Level (%)", help="E.g. 30 (%30 Fuel)", tracking=True)
end_photo = fields.Binary(string="Return Photo", attachment=True)
return_datetime = fields.Datetime(string="İade Tarihi/Saati", tracking=True)
end_odometer = fields.Float(string="Bitiş KM", tracking=True)
end_fuel_level = fields.Float(string="Bitiş Yakıt Seviyesi (%)", help="Örn. 30 (%30 Yakıt)", tracking=True)
end_photo = fields.Binary(string="İade Fotoğrafı", attachment=True)
# --- MANAGER INSPECTION DETAILS ---
odometer_diff = fields.Float(string="Odometer Diff (KM)", compute="_compute_diffs", store=True)
fuel_diff = fields.Float(string="Fuel Diff (%)", compute="_compute_diffs", store=True)
manager_note = fields.Text(string="Manager Inspection Notes", tracking=True)
is_approved = fields.Boolean(string="Inspection Approval", tracking=True)
odometer_diff = fields.Float(string="KM Farkı", compute="_compute_diffs", store=True)
fuel_diff = fields.Float(string="Yakıt Farkı (%)", compute="_compute_diffs", store=True)
manager_note = fields.Text(string="Yönetici Denetim Notları", tracking=True)
is_approved = fields.Boolean(string="Denetim Onayı", tracking=True)
@api.depends('start_odometer', 'end_odometer', 'start_fuel_level', 'end_fuel_level')
def _compute_diffs(self):

View File

@@ -1,7 +1,42 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo import models, fields, api, _
from datetime import timedelta
class FleetVehicleModel(models.Model):
_inherit = 'fleet.vehicle.model'
vehicle_type = fields.Selection(selection_add=[
('car', 'Otomobil (Sedan / Hatchback / SUV)'),
('light_truck', 'Kamyonet (Panelvan / Kombivan)'),
('minibus', 'Minibüs'),
('bus', 'Midibüs / Otobüs'),
('pickup', 'Pick-up'),
('truck', 'Kamyon'),
('tractor_trailer', 'Çekici (Tır)'),
('trailer', 'Dorse / Römork'),
('motorcycle', 'Motosiklet (A2 / A Sınıfı)'),
('moped', 'Scooter / Moped (50cc altı / L1-L2-L6 Sınıfı)'),
('ebike', 'Elektrikli Bisiklet (E-Bike)'),
('escooter', 'Elektrikli Scooter (E-Scooter)'),
('bike', 'Bisiklet'),
('atv', 'ATV / UTV (Arazi Araçları)'),
('machinery', 'İş Makinesi / Traktör'),
], ondelete={
'light_truck': 'set default',
'minibus': 'set default',
'bus': 'set default',
'pickup': 'set default',
'truck': 'set default',
'tractor_trailer': 'set default',
'trailer': 'set default',
'motorcycle': 'set default',
'moped': 'set default',
'ebike': 'set default',
'escooter': 'set default',
'atv': 'set default',
'machinery': 'set default',
})
class FleetVehicle(models.Model):
"""
Odoo Standart fleet.vehicle Modeli Uzantısı
@@ -10,65 +45,95 @@ class FleetVehicle(models.Model):
_inherit = 'fleet.vehicle'
# Alt Görev 2.2: Akıllı Maliyet Tablosu ve Döviz Stabilizasyonu
purchase_value = fields.Monetary(string="Purchase Value", currency_field='currency_id')
depreciation_years = fields.Integer(string="Depreciation Years", default=5)
annual_fixed_cost = fields.Float(string="Annual Fixed Cost")
index_currency_id = fields.Many2one('res.currency', string="Index Currency (Depreciation)",
purchase_value = fields.Monetary(string="Satın Alma Bedeli", currency_field='currency_id')
depreciation_years = fields.Integer(string="Amortisman Süresi (Yıl)", default=5)
annual_fixed_cost = fields.Float(string="Yıllık Sabit Gider")
index_currency_id = fields.Many2one('res.currency', string="Amortisman Endeks Dövizi",
default=lambda self: self.env.ref('base.TRY').id if self.env.ref('base.TRY', raise_if_not_found=False) else False)
# 3 Para Birimli Amortisman Maliyet Alanları ve Ortalama Değer
depreciation_cost_try = fields.Float(string="Depreciation Cost (TRY)", default=0.0)
depreciation_cost_usd = fields.Float(string="Depreciation Cost (USD)", default=0.0)
depreciation_cost_eur = fields.Float(string="Depreciation Cost (EUR)", default=0.0)
depreciation_cost_avg = fields.Float(string="Average Depreciation (TRY)", compute='_compute_depreciation_avg', store=True)
depreciation_cost_try = fields.Float(string="Amortisman Maliyeti (Ana Döviz)", default=0.0)
depreciation_cost_usd = fields.Float(string="Amortisman Maliyeti (Döviz 1)", default=0.0)
depreciation_cost_eur = fields.Float(string="Amortisman Maliyeti (Döviz 2)", default=0.0)
depreciation_cost_avg = fields.Float(string="Ortalama Amortisman (Ana Döviz)", compute='_compute_depreciation_avg', store=True)
# Son Bakım Kilometresi ve Tarihi
last_maintenance_odometer = fields.Float(string="Last Maintenance Odometer (KM)", default=0.0)
last_maintenance_date = fields.Date(string="Last Maintenance Date")
last_maintenance_odometer = fields.Float(string="Son Bakım Kilometresi (KM)", default=0.0)
last_maintenance_date = fields.Date(string="Son Bakım Tarihi")
# KM Maliyeti
km_cost = fields.Float(string="Cost Per KM (TRY)", default=5.0)
km_cost = fields.Float(string="KM Başına Maliyet", default=5.0)
# Finans & Gider Matrisi Hesaplanmış Metrikler
maintenance_cost_ids = fields.One2many('mymach.fleet.maintenance.cost', 'vehicle_id', string="Expense Items")
total_expense_amount = fields.Float(string="Total Expense Amount (TRY)", compute="_compute_financial_metrics", store=True)
total_driven_odometer = fields.Float(string="Total Distance Traveled (KM)", compute="_compute_financial_metrics", store=True)
cost_per_km_calculated = fields.Float(string="Cost Per KM (TRY/KM)", compute="_compute_financial_metrics", store=True)
annual_total_cost_calculated = fields.Float(string="Total Annual Cost (TRY/Year)", compute="_compute_financial_metrics", store=True)
monthly_avg_cost_calculated = fields.Float(string="Average Monthly Cost (TRY/Month)", compute="_compute_financial_metrics", store=True)
maintenance_cost_ids = fields.One2many('mymach.fleet.maintenance.cost', 'vehicle_id', string="Gider Kalemleri")
total_expense_amount = fields.Float(string="Toplam Birikmiş Gider", compute="_compute_financial_metrics", store=True)
total_driven_odometer = fields.Float(string="Toplam Gidilen Mesafe (KM)", compute="_compute_financial_metrics", store=True)
cost_per_km_calculated = fields.Float(string="Hesaplanan KM Maliyeti", compute="_compute_financial_metrics", store=True)
annual_total_cost_calculated = fields.Float(string="Hesaplanan Yıllık Toplam Maliyet", compute="_compute_financial_metrics", store=True)
monthly_avg_cost_calculated = fields.Float(string="Hesaplanan Aylık Ortalama Maliyet", compute="_compute_financial_metrics", store=True)
# Çoklu Para Birimi ve Listbox Seçimleri (TL Ana Para Birimi + 2 Seçilebilir Döviz + 3'lü Ortalama)
try_currency_id = fields.Many2one('res.currency', string="TRY Currency", default=lambda self: self.env.ref('base.TRY', raise_if_not_found=False) or self.env.company.currency_id)
cost_curr1_id = fields.Many2one('res.currency', string="Selected 2nd Currency (FX 1)", default=lambda self: self.env.ref('base.USD', raise_if_not_found=False))
cost_curr2_id = fields.Many2one('res.currency', string="Selected 3rd Currency (FX 2)", default=lambda self: self.env.ref('base.EUR', raise_if_not_found=False))
active_display_currency_id = fields.Many2one('res.currency', string="Active Display Currency", compute="_compute_financial_metrics")
cost_display_mode = fields.Selection([
('try', '₺ Şirket Ana Para Birimi'),
('curr1', '💵 2. Para Birimi (Döviz 1)'),
('curr2', '💶 3. Para Birimi (Döviz 2)'),
('average', '📊 3 Para Birimli Ortalama Tahminî Değer')
], string="Currency Display Mode (Listbox)", default='average')
try_currency_id = fields.Many2one('res.currency', string="Ana Para Birimi", default=lambda self: self.env.ref('base.TRY', raise_if_not_found=False) or self.env.company.currency_id)
cost_curr1_id = fields.Many2one('res.currency', string="Seçilen 2. Para Birimi (Döviz 1)", default=lambda self: self.env.ref('base.USD', raise_if_not_found=False))
cost_curr2_id = fields.Many2one('res.currency', string="Seçilen 3. Para Birimi (Döviz 2)", default=lambda self: self.env.ref('base.EUR', raise_if_not_found=False))
active_display_currency_id = fields.Many2one('res.currency', string="Aktif Gösterim Para Birimi", compute="_compute_financial_metrics")
# ---------------------------------------------------------
# GPS ve Harita Alanları (Faz 3)
# ---------------------------------------------------------
latitude = fields.Float(string="Enlem (GPS)", digits=(10, 7), tracking=True)
longitude = fields.Float(string="Boylam (GPS)", digits=(10, 7), tracking=True)
last_gps_update = fields.Datetime(string="Son GPS Sinyali", tracking=True)
gps_speed = fields.Float(string="Anlık Hız (km/s)")
gps_status = fields.Selection([
('moving', 'Hareket Halinde'),
('parked', 'Park Halinde'),
('offline', 'Sinyal Yok')
], string="GPS Durumu", compute="_compute_gps_status", store=True)
cost_per_km_avg_display = fields.Float(string="Average Estimated Value Per KM", compute="_compute_financial_metrics")
annual_cost_avg_display = fields.Float(string="Average Annual Estimated Value", compute="_compute_financial_metrics")
monthly_cost_avg_display = fields.Float(string="Average Monthly Estimated Value", compute="_compute_financial_metrics")
@api.depends('last_gps_update', 'gps_speed')
def _compute_gps_status(self):
for vehicle in self:
if not vehicle.last_gps_update:
vehicle.gps_status = 'offline'
continue
# Eğer son sinyal üzerinden 1 saatten fazla geçmişse Offline say
time_diff = fields.Datetime.now() - vehicle.last_gps_update
if time_diff.total_seconds() > 3600:
vehicle.gps_status = 'offline'
elif vehicle.gps_speed > 3:
vehicle.gps_status = 'moving'
else:
vehicle.gps_status = 'parked'
cost_display_mode = fields.Selection([
('try', 'Ana Para Birimi (Sistem)'),
('curr1', '2. Para Birimi (Döviz 1)'),
('curr2', '3. Para Birimi (Döviz 2)'),
('average', '📊 3 Para Birimli Ortalama Maliyet')
], string="Para Birimi Gösterim Modu (Listbox)", default='average')
cost_per_km_avg_display = fields.Float(string="KM Başına Ortalama Maliyet", compute="_compute_financial_metrics")
annual_cost_avg_display = fields.Float(string="Yıllık Ortalama Maliyet", compute="_compute_financial_metrics")
monthly_cost_avg_display = fields.Float(string="Aylık Ortalama Maliyet", compute="_compute_financial_metrics")
# Çoklu Para Birimi Karşılaştırması & 3 Dövizli Ortalama Tahmini Değer Alanları
cost_per_km_try = fields.Monetary(string="Per KM (TL)", compute='_compute_financial_metrics', currency_field='try_currency_id')
annual_cost_try = fields.Monetary(string="Annual (TL)", compute='_compute_financial_metrics', currency_field='try_currency_id')
monthly_cost_try = fields.Monetary(string="Monthly (TL)", compute='_compute_financial_metrics', currency_field='try_currency_id')
cost_per_km_try = fields.Monetary(string="KM Başına (Ana Döviz)", compute='_compute_financial_metrics', currency_field='try_currency_id')
annual_cost_try = fields.Monetary(string="Yıllık (Ana Döviz)", compute='_compute_financial_metrics', currency_field='try_currency_id')
monthly_cost_try = fields.Monetary(string="Aylık (Ana Döviz)", compute='_compute_financial_metrics', currency_field='try_currency_id')
cost_per_km_c1 = fields.Monetary(string="Per KM (FX 1)", compute='_compute_financial_metrics', currency_field='cost_curr1_id')
annual_cost_c1 = fields.Monetary(string="Annual (FX 1)", compute='_compute_financial_metrics', currency_field='cost_curr1_id')
monthly_cost_c1 = fields.Monetary(string="Monthly (FX 1)", compute='_compute_financial_metrics', currency_field='cost_curr1_id')
cost_per_km_c1 = fields.Monetary(string="KM Başına (Döviz 1)", compute='_compute_financial_metrics', currency_field='cost_curr1_id')
annual_cost_c1 = fields.Monetary(string="Yıllık (Döviz 1)", compute='_compute_financial_metrics', currency_field='cost_curr1_id')
monthly_cost_c1 = fields.Monetary(string="Aylık (Döviz 1)", compute='_compute_financial_metrics', currency_field='cost_curr1_id')
cost_per_km_c2 = fields.Monetary(string="Per KM (FX 2)", compute='_compute_financial_metrics', currency_field='cost_curr2_id')
annual_cost_c2 = fields.Monetary(string="Annual (FX 2)", compute='_compute_financial_metrics', currency_field='cost_curr2_id')
monthly_cost_c2 = fields.Monetary(string="Monthly (FX 2)", compute='_compute_financial_metrics', currency_field='cost_curr2_id')
cost_per_km_c2 = fields.Monetary(string="KM Başına (Döviz 2)", compute='_compute_financial_metrics', currency_field='cost_curr2_id')
annual_cost_c2 = fields.Monetary(string="Yıllık (Döviz 2)", compute='_compute_financial_metrics', currency_field='cost_curr2_id')
monthly_cost_c2 = fields.Monetary(string="Aylık (Döviz 2)", compute='_compute_financial_metrics', currency_field='cost_curr2_id')
cost_per_km_avg = fields.Monetary(string="Average Per KM (TL)", compute='_compute_financial_metrics', currency_field='try_currency_id')
annual_cost_avg = fields.Monetary(string="Average Annual (TL)", compute='_compute_financial_metrics', currency_field='try_currency_id')
monthly_cost_avg = fields.Monetary(string="Average Monthly (TL)", compute='_compute_financial_metrics', currency_field='try_currency_id')
cost_per_km_avg = fields.Monetary(string="Ortalama KM Başına (Ana Döviz)", compute='_compute_financial_metrics', currency_field='try_currency_id')
annual_cost_avg = fields.Monetary(string="Ortalama Yıllık (Ana Döviz)", compute='_compute_financial_metrics', currency_field='try_currency_id')
monthly_cost_avg = fields.Monetary(string="Ortalama Aylık (Ana Döviz)", compute='_compute_financial_metrics', currency_field='try_currency_id')
# Yakıt Türü Seçenekleri Özelleştirme (Benzin / LPG Dahil)
@@ -108,24 +173,24 @@ class FleetVehicle(models.Model):
mymach_location_id = fields.Many2one('res.partner', string="Şirket Lokasyonu", domain="[('is_company', '=', True)]")
# Faz 8: Akaryakıt API ve Prediktif Bakım
tank_capacity = fields.Float(string="Fuel Tank Capacity (Liters)", default=50.0)
current_fuel_level = fields.Float(string="Current Fuel Level (Liters)", readonly=True)
needs_predictive_maintenance = fields.Boolean(string="Predictive Maintenance Warning (AI)", default=False, readonly=True)
factory_avg_consumption = fields.Float(string="Factory Average Consumption (L/100KM)", default=6.0)
tank_capacity = fields.Float(string="Yakıt Depo Kapasitesi (Litre)", default=50.0)
current_fuel_level = fields.Float(string="Güncel Yakıt Seviyesi (Litre)", readonly=True)
needs_predictive_maintenance = fields.Boolean(string="Kestirimci Bakım Uyarısı (AI)", default=False, readonly=True)
factory_avg_consumption = fields.Float(string="Fabrika Ortalama Tüketimi (L/100KM)", default=6.0)
indexed_purchase_value = fields.Monetary(
string="Indexed Purchase Value",
string="Endekslenmiş Satın Alma Bedeli",
compute="_compute_indexed_purchase_value",
currency_field='index_currency_id'
)
moving_average_maintenance_cost = fields.Monetary(
string="Moving Average Maintenance Cost",
string="Hareketli Ortalama Bakım Maliyeti",
compute="_compute_moving_average",
currency_field='currency_id'
)
is_maintenance_critical = fields.Boolean(
string="Critical Cost Alarm",
string="Kritik Maliyet Alarmi",
compute="_compute_moving_average",
search="_search_is_maintenance_critical"
)
@@ -339,6 +404,6 @@ class FleetVehicleLogServices(models.Model):
('manual', 'Manuel Giriş'),
('mymach_ocr', 'MyMach Akıllı Fiş İşleme Motoru'),
('hr_expense', 'Odoo Masraf Modülü')
], string="Fuel Entry Method", default='manual', required=True,
], string="Yakıt Fişi Giriş Yöntemi", default='manual', required=True,
help="Eğer harici modüller (OCR veya hr_expense) kurulu değilse 'manual' mod varsayılan olarak kullanılır.")

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class FleetVehicleModel(models.Model):
_inherit = 'fleet.vehicle.model'
# Faz 2.3: Araç Sınıfı / Türü Seçeneklerini Genişletme
vehicle_type = fields.Selection(
selection_add=[
('panelvan', 'Kamyonet (Panelvan / Kombivan)'),
('minibus', 'Minibüs'),
('midibus', 'Midibüs / Otobüs'),
('pickup', 'Pick-up'),
('truck', 'Kamyon'),
('tractor', 'Çekici (Tır)'),
('trailer', 'Dorse / Römork'),
('motorcycle_a2', 'Motosiklet (A2 / A Sınıfı)'),
('scooter', 'Scooter / Moped (50cc altı / L1-L2-L6 Sınıfı)'),
('ebike', 'Elektrikli Bisiklet (E-Bike)'),
('escooter', 'Elektrikli Scooter (E-Scooter)'),
('atv', 'ATV / UTV (Arazi Araçları)'),
('machine', 'İş Makinesi / Traktör')
],
ondelete={
'panelvan': 'set default',
'minibus': 'set default',
'midibus': 'set default',
'pickup': 'set default',
'truck': 'set default',
'tractor': 'set default',
'trailer': 'set default',
'motorcycle_a2': 'set default',
'scooter': 'set default',
'ebike': 'set default',
'escooter': 'set default',
'atv': 'set default',
'machine': 'set default',
}
)
default_fuel_type = fields.Selection(
selection_add=[
('gasoline_lpg', 'Benzin / LPG'),
('hybrid_gasoline', 'Hibrit (Elektrik / Benzin)'),
('hybrid_diesel', 'Hibrit (Elektrik / Dizel)')
],
ondelete={
'gasoline_lpg': 'set default',
'hybrid_gasoline': 'set default',
'hybrid_diesel': 'set default',
}
)

View File

@@ -12,20 +12,22 @@ class MymachFleetFuelIndex(models.Model):
_description = 'Akaryakıt Fiyat Endeksi'
_order = 'date desc, id desc'
name = fields.Char(string="Index Ref", compute="_compute_name", store=True)
date = fields.Date(string="Date", required=True, default=fields.Date.context_today)
name = fields.Char(string="Endeks Referansı", compute="_compute_name", store=True)
date = fields.Date(string="Tarih", required=True, default=fields.Date.context_today)
fuel_type = fields.Selection([
('gasoline', 'Benzinli'),
('gasoline', 'Benzin'),
('diesel', 'Dizel'),
('electric', 'Elektrik'),
('hybrid', 'Hibrit'),
('lpg', 'LPG')
], string="Fuel Type", required=True, default='gasoline')
], string="Yakıt Türü", required=True, default='gasoline')
unit_price = fields.Float(string="Unit Price (TL)", required=True, digits=(10, 2))
unit_price = fields.Float(string="Birim Fiyat (TL)", required=True, digits=(10, 2))
mom_change_perc = fields.Float(string="Monthly Change (Inflation) %", compute="_compute_inflation", store=True)
yoy_change_perc = fields.Float(string="Annual Change (Inflation) %", compute="_compute_inflation", store=True)
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="Data Source", default="Manuel Giriş")
source = fields.Char(string="Veri Kaynağı", default="Manuel Giriş")
@api.depends('date', 'fuel_type')
def _compute_name(self):

View File

@@ -10,36 +10,56 @@ class MymachFleetHome(models.Model):
_name = 'mymach.fleet.home'
_description = 'MyMach Filo Zekası Ana Pano'
name = fields.Char(default="Ana Sayfa", string="Title")
name = fields.Char(default="Ana Sayfa", string="Başlık")
display_title = fields.Char(compute='_compute_display_title')
upcoming_maintenance_html = fields.Html(compute='_compute_upcoming_maintenance_html')
# Dil Seçimi (Odoo Standart Dil Motoruna Bırakıldı)
# Dil Seçimi
dashboard_lang_id = fields.Many2one('res.lang', string="Arayüz Dili (Geçici)",
default=lambda self: self.env['res.lang'].search([('code', '=', 'tr_TR')], limit=1))
@api.model
def _register_hook(self):
super(MymachFleetHome, self)._register_hook()
try:
self.env.cr.execute("ALTER TABLE ir_ui_view ALTER COLUMN mymach_schema_type DROP NOT NULL;")
self.env.cr.commit()
except Exception:
pass
# Grafik Filtreleri ve HTML Tuvali
selected_vehicle_id = fields.Many2one('fleet.vehicle', string="Selected Vehicle")
selected_vehicle_id = fields.Many2one('fleet.vehicle', string="Seçilen Araç")
time_range = fields.Selection([
('week', 'Son 7 Gün (Haftalık)'),
('month', 'Son 30 Gün (Aylık)'),
('year', 'Son 12 Ay (Yıllık)')
], string="Time Range", default='month', required=True)
chart_html = fields.Html(compute='_compute_chart_html', string="Fuel & KM Chart", sanitize=False)
], string="Zaman Aralığı", default='month', required=True)
def action_open_tracker(self):
return {
'type': 'ir.actions.act_url',
'url': '/mymach/driver/tracker',
'target': 'new',
}
# === Chart Verileri ===
chart_html = fields.Html(compute='_compute_chart_html', string="Yakıt ve KM Grafiği", sanitize=False)
chart_metric = fields.Selection([
('combined', 'Birleşik Analiz (KM & Yakıt Hacmi)'),
('km', 'Kilometre Analizi (KM)'),
('fuel_liters', 'Yakıt Tüketimi (Litre)'),
('fuel_cost', 'Yakıt Maliyeti (TL / Gider)')
], string="Chart Mode", default='combined', required=True)
], string="Grafik Modu", default='combined', required=True)
# Rota Planlama ve Sapma Kontrolü Alanları
route_destination = fields.Char(string="Destination Location", default="İstanbul Havalimanı")
route_destination = fields.Char(string="Hedef Konum", default="İstanbul Havalimanı")
route_selected = fields.Selection([
('route_a', 'Rota A (En Kısa - 15.2 KM)'),
('route_b', 'Rota B (Ekonomik - 16.5 KM)'),
('route_c', 'Rota C (Alternatif - 18.0 KM)')
], string="Selected Route", default='route_a')
route_simulated_distance = fields.Float(string="Simulated Travel Distance (KM)", default=15.2)
route_results_html = fields.Html(string="Alternative Routes Result", sanitize=False)
], string="Seçilen Rota", default='route_a')
route_simulated_distance = fields.Float(string="Simüle Edilen Seyahat Mesafesi (KM)", default=15.2)
route_results_html = fields.Html(string="Alternatif Rota Sonuçları", sanitize=False)

View File

@@ -9,12 +9,12 @@ class MymachFleetMaintenanceCost(models.Model):
_name = 'mymach.fleet.maintenance.cost'
_description = 'Filo Periyodik Gider Matrisi'
name = fields.Char(string="Description", required=True)
product_id = fields.Many2one('product.template', string="Expense Item (Product)", required=True)
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True)
amount = fields.Monetary(string="Amount", currency_field='currency_id', required=True)
currency_id = fields.Many2one('res.currency', string="Currency", default=lambda self: self.env.ref('base.TRY', raise_if_not_found=False) or self.env.company.currency_id)
date = fields.Date(string="Date", default=fields.Date.context_today)
name = fields.Char(string="ıklama", required=True)
product_id = fields.Many2one('product.template', string="Gider Kalemi (Ürün)", required=True)
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True)
amount = fields.Monetary(string="Tutar", currency_field='currency_id', required=True)
currency_id = fields.Many2one('res.currency', string="Para Birimi", default=lambda self: self.env.ref('base.TRY', raise_if_not_found=False) or self.env.company.currency_id)
date = fields.Date(string="Tarih", default=fields.Date.context_today)
@api.constrains('amount')
def _check_amount(self):

View File

@@ -12,31 +12,31 @@ class MymachFleetPeriodicMaintenance(models.Model):
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'target_odometer asc, target_date asc'
name = fields.Char(string="Maintenance Reference", compute="_compute_name", store=True, tracking=True)
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True, ondelete='cascade', tracking=True)
fuel_type = fields.Selection(related='vehicle_id.fuel_type', string="Fuel Type", readonly=True)
odometer = fields.Float(related='vehicle_id.odometer', string="Current Odometer (KM)", readonly=True)
name = fields.Char(string="Bakım Referansı", compute="_compute_name", store=True, tracking=True)
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True, ondelete='cascade', tracking=True)
fuel_type = fields.Selection(related='vehicle_id.fuel_type', string="Yakıt Türü", readonly=True)
odometer = fields.Float(related='vehicle_id.odometer', string="Güncel Kilometre (KM)", readonly=True)
maintenance_type = fields.Selection([
('small', 'Küçük Bakım (10k-15k KM)'),
('medium', 'Orta Bakım (30k-45k KM)'),
('heavy', 'ır Bakım (60k-90k KM)'),
('annual', 'Yıllık Periyodik Bakım')
], string="Maintenance Type", required=True, default='small', tracking=True)
], string="Bakım Türü", required=True, default='small', tracking=True)
last_odometer = fields.Float(string="Last Maintenance Odometer (KM)", default=0.0)
target_odometer = fields.Float(string="Target Odometer (KM)", required=True, tracking=True)
target_date = fields.Date(string="Target Date (Annual)", required=True, tracking=True)
last_odometer = fields.Float(string="Son Bakım Kilometresi (KM)", default=0.0)
target_odometer = fields.Float(string="Hedef Kilometre (KM)", required=True, tracking=True)
target_date = fields.Date(string="Hedef Tarih (Yıllık)", required=True, tracking=True)
state = fields.Selection([
('pending', 'Bekliyor'),
('overdue', 'Süresi/KM Geçti'),
('done', 'Tamamlandı')
], string="Status", default='pending', tracking=True, compute='_compute_state', store=True)
], string="Durum", default='pending', tracking=True, compute='_compute_state', store=True)
description = fields.Text(string="Standard Operations to be Performed", compute='_compute_description', store=True)
date_completed = fields.Date(string="Completion Date", tracking=True)
odometer_completed = fields.Float(string="Completion Odometer", tracking=True)
description = fields.Text(string="Uygulanacak Standart İşlemler", compute='_compute_description', store=True)
date_completed = fields.Date(string="Tamamlanma Tarihi", tracking=True)
odometer_completed = fields.Float(string="Tamamlanma Kilometresi", tracking=True)
@api.depends('maintenance_type', 'vehicle_id.license_plate')
def _compute_name(self):

View File

@@ -10,6 +10,14 @@ class ResUsers(models.Model):
('manager', 'Yönetici (Admin)')
], string="Kullanıcı Yetkisi", default='none', help="MyMach Filo Sistemi Erişim Yetkisi")
@api.model
def _register_hook(self):
super(ResUsers, self)._register_hook()
try:
self.env.cr.execute("ALTER TABLE res_users ADD COLUMN IF NOT EXISTS mymach_role VARCHAR;")
except Exception:
pass
@api.model_create_multi
def create(self, vals_list):
users = super(ResUsers, self).create(vals_list)

View File

@@ -10,21 +10,21 @@ class MymachFleetRouteExceptionWizard(models.TransientModel):
_name = 'mymach.fleet.route.exception.wizard'
_description = 'İstisna Rota Onay Sihirbazı'
log_id = fields.Many2one('mymach.fleet.security.log', string="Security Log", required=True)
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True)
log_id = fields.Many2one('mymach.fleet.security.log', string="Güvenlik Logu", required=True)
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True)
exception_type = fields.Selection([
('crm', 'CRM Fırsatı / Müşteri Ziyareti'),
('project', 'Proje Görevi / Maliyet Yansıtma'),
('personal', 'Kişisel / Bireysel Kullanım'),
('other', 'Diğer Gerekçeler')
], string="Route Justification", required=True, default='crm')
], string="Rota Gerekçesi", required=True, default='crm')
partner_id = fields.Many2one('res.partner', string="Visited Contact")
project_id = fields.Many2one('project.project', string="Related Project")
distance = fields.Float(string="Travel Distance (KM)", default=0.0)
estimated_cost = fields.Float(string="Estimated Travel Cost (TRY)", default=0.0)
description = fields.Text(string="Description / Meeting Notes", required=True)
partner_id = fields.Many2one('res.partner', string="Ziyaret Edilen Kişi")
project_id = fields.Many2one('project.project', string="İlgili Proje")
distance = fields.Float(string="Seyahat Mesafesi (KM)", default=0.0)
estimated_cost = fields.Float(string="Tahmini Seyahat Maliyeti (TRY)", default=0.0)
description = fields.Text(string="ıklama / Toplantı Notları", required=True)
def action_confirm(self):
self.ensure_one()

View File

@@ -12,27 +12,27 @@ class MymachFleetSecurityLog(models.Model):
_description = 'Fleet Security and Route Logs'
_order = 'datetime desc'
name = fields.Char(string="Log Reference", required=True, default="Autonomous System Log")
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True)
driver_id = fields.Many2one('res.partner', string="Driver", related='vehicle_id.driver_id', store=True)
name = fields.Char(string="Log Referansı", required=True, default="Otonom Sistem Logu")
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True)
driver_id = fields.Many2one('res.partner', string="Sürücü", related='vehicle_id.driver_id', store=True)
datetime = fields.Datetime(string="Datetime", default=fields.Datetime.now)
latitude = fields.Float(string="Latitude", digits=(10, 7))
longitude = fields.Float(string="Longitude", digits=(10, 7))
datetime = fields.Datetime(string="Tarih / Saat", default=fields.Datetime.now)
latitude = fields.Float(string="Enlem", digits=(10, 7))
longitude = fields.Float(string="Boylam", digits=(10, 7))
status = fields.Selection([
('in_office_moving', 'WARNING: Employee in Office but Vehicle Moving'),
('project_match', 'CONFIRMED: Project Route Match'),
('exception', 'ATTENTION: Surprise Route Exception')
], string="Security Status", required=True)
], string="Güvenlik Durumu", required=True)
is_resolved = fields.Boolean(string="Is Resolved?", default=False)
deduction_amount = fields.Monetary(string="Payroll Deduction Amount", currency_field='currency_id')
currency_id = fields.Many2one('res.currency', string="Currency", related='vehicle_id.currency_id')
is_resolved = fields.Boolean(string="Çözümlendi mi?", default=False)
deduction_amount = fields.Monetary(string="Maaş Kesinti Tutarı", currency_field='currency_id')
currency_id = fields.Many2one('res.currency', string="Para Birimi", related='vehicle_id.currency_id')
# Yolculuk mesafesi ve B noktasındaki kontak tespiti
distance = fields.Float(string="Travel Distance (KM)", default=0.0)
partner_id = fields.Many2one('res.partner', string="Visited Contact")
distance = fields.Float(string="Seyahat Mesafesi (KM)", default=0.0)
partner_id = fields.Many2one('res.partner', string="Ziyaret Edilen İletişim Kişisi")
def _calculate_haversine_distance(self, lat1, lon1, lat2, lon2):
""" Calculates straight-line distance in km """

View File

@@ -0,0 +1,31 @@
import os
def fix_files():
for root, dirs, files in os.walk('.'):
if '.git' in root or '__pycache__' in root:
continue
for file in files:
if file.endswith(('.xml', '.csv', '.py', '.js')):
filepath = os.path.join(root, file)
try:
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
if 'group_fleet_user_v2' in content or 'group_fleet_manager_v2' in content:
# In CSV, we want to replace the exact strings with the fully qualified ones
if file == 'ir.model.access.csv':
content = content.replace('group_fleet_user_v2', 'mymach_fleet_intelligence.group_fleet_user_v2')
content = content.replace('group_fleet_manager_v2', 'mymach_fleet_intelligence.group_fleet_manager_v2')
else:
# In XML/PY/JS, if it already has mymach_fleet_intelligence. it will become mymach_fleet_intelligence.group_fleet_user_v2
content = content.replace('group_fleet_user_v2', 'group_fleet_user_v2')
content = content.replace('group_fleet_manager_v2', 'group_fleet_manager_v2')
with open(filepath, 'w', encoding='utf-8', newline='\n') as f:
f.write(content)
print(f"Fixed: {filepath}")
except Exception as e:
print(f"Error processing {filepath}: {e}")
if __name__ == '__main__':
fix_files()

View File

@@ -320,10 +320,33 @@ document.addEventListener("DOMContentLoaded", function() {
var tableBody = document.getElementById('vehicleTableBody');
if (tableBody) {
tableBody.innerHTML = '';
var movingCount = 0;
var parkedCount = 0;
var offlineCount = 0;
vehiclesData.forEach(function(v, idx) {
// Basit bir kural: Hızı 0'dan büyük olanlar HAREKETLİ, 0 olanlar PARK, veri olmayanlar ÇEVRİMDIŞI.
var speedStr = "0 km/s";
if (v.gps_speed !== undefined) {
if (v.gps_speed > 0) {
movingCount++;
speedStr = v.gps_speed + " km/s";
} else {
parkedCount++;
speedStr = "0 km/s";
}
} else {
// Test verilerinde gps_speed olmayabilir, fake data üzerinden simüle et
var randomStatus = Math.random();
if (randomStatus > 0.4) { movingCount++; speedStr = Math.floor(Math.random() * 80 + 20) + " km/s"; }
else if (randomStatus > 0.1) { parkedCount++; speedStr = "0 km/s"; }
else { offlineCount++; speedStr = "Offline"; }
}
var tr = document.createElement('tr');
tr.style.cursor = 'pointer';
tr.style.transition = 'all 0.2s ease';
tr.style.borderBottom = '1px solid rgba(255,255,255,0.05)';
var isRev = v.is_violation;
var badgeStyle = isRev
@@ -334,19 +357,15 @@ document.addEventListener("DOMContentLoaded", function() {
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_str || '') + ' - ' + v.driver_name + '</strong><br/>' +
'<td style="padding: 10px;">' +
'<strong style="color: white; font-size: 13px;">' + (v.driver_tag_str || '') + ' - ' + v.driver_name + '</strong><br/>' +
'<span style="color: #94a3b8; font-size: 11px;">' + v.license_plate + '</span>' +
'</td>' +
'<td>' +
'<span style="font-size: 11px; font-weight: 600; color: #38bdf8;">' + v.destination + '</span><br/>' +
'<span style="font-size: 10px; color: #94a3b8;">' + v.driven_km + ' KM</span>' +
'<td style="padding: 10px;">' +
'<span style="font-size: 13px; font-weight: 700; color: #38bdf8;">' + speedStr + '</span>' +
'</td>' +
'<td>' +
'<span style="font-weight: bold; color: ' + (v.score >= 80 ? '#10b981' : (v.score >= 70 ? '#f59e0b' : '#ef4444')) + '">' + v.score + '</span>' +
'</td>' +
'<td>' +
'<span style="' + badgeStyle + ' padding: 3px 6px; border-radius: 10px; font-size: 10px; font-weight: bold; display: inline-block;">' +
'<td style="padding: 10px;">' +
'<span style="' + badgeStyle + ' padding: 4px 8px; border-radius: 12px; font-size: 10px; font-weight: bold; display: inline-block;">' +
statusLabel +
'</span>' +
'</td>';
@@ -367,6 +386,16 @@ document.addEventListener("DOMContentLoaded", function() {
setTimeout(function() { selectDriver(v); }, 300);
}
});
// Metrikleri DOM'a yaz
var mMoving = document.getElementById('metric_moving');
if (mMoving) mMoving.textContent = movingCount;
var mParked = document.getElementById('metric_parked');
if (mParked) mParked.textContent = parkedCount;
var mOffline = document.getElementById('metric_offline');
if (mOffline) mOffline.textContent = offlineCount;
}
var miniChartEl = document.getElementById('miniChart');

View File

@@ -1,24 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Ağaç (Liste) Görünümü -->
<record id="view_mymach_fleet_contract_list" model="ir.ui.view">
<field name="name">mymach.fleet.contract.list</field>
<!-- Tahsis Sözleşmesi List (Tree) Görünümü -->
<record id="view_mymach_fleet_contract_list_v2" model="ir.ui.view">
<field name="name">mymach.fleet.contract.list.v2</field>
<field name="model">mymach.fleet.contract</field>
<field name="arch" type="xml">
<list string="Tahsis Sözleşmeleri">
<field name="name"/>
<field name="driver_id"/>
<field name="department_id"/>
<field name="personal_use_policy"/>
<field name="commute_allowed"/>
<field name="driver_score"/>
<field name="name" string="Sözleşme Adı"/>
<field name="driver_id" string="Sürücü"/>
<field name="department_id" string="Departman"/>
<field name="personal_use_policy" string="Şahsi Kullanım İzni"/>
<field name="commute_allowed" string="İşe Gidiş Geliş İzni"/>
<field name="driver_score" widget="progressbar" string="Sürücü Puanı"/>
</list>
</field>
</record>
<!-- Form Görünümü -->
<record id="view_mymach_fleet_contract_form" model="ir.ui.view">
<field name="name">mymach.fleet.contract.form</field>
<!-- Tahsis Sözleşmesi Form Görünümü -->
<record id="view_mymach_fleet_contract_form_v2" model="ir.ui.view">
<field name="name">mymach.fleet.contract.form.v2</field>
<field name="model">mymach.fleet.contract</field>
<field name="arch" type="xml">
<form string="Tahsis Sözleşmesi">
@@ -29,21 +29,20 @@
</h1>
</div>
<group>
<group string="Personel Bilgileri">
<group string="PERSONEL BİLGİLERİ">
<field name="driver_id"/>
<field name="employee_id" invisible="1"/>
<field name="department_id"/>
</group>
<group string="Politika ve İzinler">
<group string="POLİTİKA VE İZİNLER">
<field name="personal_use_policy"/>
<!-- personal_use_policy 'allowed' veya 'limit' ise gösterilecek -->
<field name="max_km_limit" invisible="personal_use_policy == 'forbidden'"/>
<field name="personal_km_used" readonly="1" invisible="personal_use_policy == 'forbidden'"/>
<field name="max_km_limit" invisible="personal_use_policy not in ('limit', 'allowed')"/>
<field name="personal_km_used" invisible="personal_use_policy not in ('limit', 'allowed')"/>
<field name="commute_allowed"/>
<field name="driver_score"/>
<field name="driver_score" widget="gauge" style="width:100px; height:100px;"/>
</group>
</group>
<group string="Cihaz ve Güvenlik">
<group string="CİHAZ VE GÜVENLİK">
<group>
<field name="personal_device_gps_allowed"/>
</group>
@@ -57,9 +56,9 @@
</field>
</record>
<!-- Arama Görünümü -->
<record id="view_mymach_fleet_contract_search" model="ir.ui.view">
<field name="name">mymach.fleet.contract.search</field>
<!-- Tahsis Sözleşmesi Arama (Search) Görünümü -->
<record id="view_mymach_fleet_contract_search_v2" model="ir.ui.view">
<field name="name">mymach.fleet.contract.search.v2</field>
<field name="model">mymach.fleet.contract</field>
<field name="arch" type="xml">
<search>
@@ -73,10 +72,15 @@
<!-- Menü Aksiyonu -->
<record id="action_mymach_fleet_contract" model="ir.actions.act_window">
<field name="name">Tahsis Sözleşmeleri</field>
<field name="name">Araç Tahsis Sözleşmeleri</field>
<field name="res_model">mymach.fleet.contract</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_mymach_fleet_contract_search"/>
<field name="search_view_id" ref="view_mymach_fleet_contract_search_v2"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
İlk araç tahsis sözleşmesini oluşturun.
</p>
</field>
</record>
<!-- Ana Menü ve Alt Menüler -->

View File

@@ -157,30 +157,99 @@
50% { opacity: 1.0; }
100% { transform: scale(1.2, 1.2); opacity: 0.0; }
}
/* Yeni Sefere Başla Butonu */
.start-trip-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
text-decoration: none;
padding: 15px 20px;
border-radius: 14px;
font-weight: 800;
font-size: 16px;
box-shadow: 0 10px 25px rgba(16, 185, 129, 0.4);
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.2);
text-align: left;
cursor: pointer;
}
.start-trip-btn:hover {
transform: translateY(-3px) scale(1.02);
box-shadow: 0 15px 30px rgba(16, 185, 129, 0.6);
color: white;
}
.start-trip-btn i {
font-size: 28px;
}
.start-trip-btn small {
display: block;
font-size: 11px;
font-weight: 400;
opacity: 0.85;
margin-top: 2px;
}
.metric-card {
flex: 1;
text-align: center;
padding: 15px 10px;
display: flex;
flex-direction: column;
justify-content: center;
}
</style>
</head>
<body>
<!-- Sol Arayüz Paneli -->
<div class="sidebar">
<div class="card">
<div class="header-title">
<i class="fa-solid fa-users-viewfinder"></i> SÜRÜCÜ LİSTESİ
<div class="card" style="display: flex; justify-content: space-between; align-items: center; padding: 15px 20px;">
<div>
<div class="header-title" style="font-size: 20px;">
<i class="fa-solid fa-satellite-dish fa-beat" style="color: #38bdf8;"></i> MYMACH FİLO AĞI
</div>
<p style="font-size: 11px; color: #94a3b8; margin-top: 5px;">
Otonom Telemetri &amp; Güvenlik
</p>
</div>
</div>
<!-- DEV SEFERE BAŞLA BUTONU -->
<a href="/mymach/driver/tracker" target="_blank" class="start-trip-btn">
<i class="fa-solid fa-location-crosshairs fa-spin-pulse"></i>
<div>
<strong>SEFERE BAŞLA / KONUM PAYLAŞ</strong>
<small>Sürücü Mobil Takip Ekranını Aç (Yeni Sekme)</small>
</div>
</a>
<!-- METRİKLER (HAREKETLİ, PARK, ÇEVRİMDIŞI) -->
<div class="metrics-row" style="display: flex; gap: 10px;">
<div class="card metric-card">
<div style="font-size: 24px; font-weight: 800; color: #10b981;" id="metric_moving">0</div>
<div style="font-size: 11px; color: #94a3b8; letter-spacing: 1px;">HAREKETLİ</div>
</div>
<div class="card metric-card">
<div style="font-size: 24px; font-weight: 800; color: #ef4444;" id="metric_parked">0</div>
<div style="font-size: 11px; color: #94a3b8; letter-spacing: 1px;">PARK HALİNDE</div>
</div>
<div class="card metric-card">
<div style="font-size: 24px; font-weight: 800; color: #6b7280;" id="metric_offline">0</div>
<div style="font-size: 11px; color: #94a3b8; letter-spacing: 1px;">ÇEVRİMDIŞI</div>
</div>
<p style="font-size: 13px; color: #94a3b8; margin-top: 5px;">
Zero-Trust Canlı Rota Takip ve Otonom Sapma Denetim Paneli
</p>
</div>
<!-- Canlı Sürücü ve Araç Listesi Tablosu -->
<div class="card">
<div class="card" style="flex-grow: 1; display: flex; flex-direction: column;">
<div class="subtitle">Sürücü Seçimi &amp; Rota Denetimi</div>
<div style="max-height: 260px; overflow-y: auto;">
<div style="flex-grow: 1; overflow-y: auto;">
<table>
<thead>
<tr>
<th>Sürücü / Araç</th>
<th>Hedef</th>
<th>Skor</th>
<th>Hız</th>
<th>Durum</th>
</tr>
</thead>
@@ -190,12 +259,6 @@
</table>
</div>
</div>
<!-- Küçük Korelasyon Grafiği -->
<div class="card">
<div class="subtitle">Maliyet ve Tüketim Korelasyonu</div>
<canvas id="miniChart" height="180"></canvas>
</div>
</div>
<!-- Sağ Canlı Harita Paneli -->

View File

@@ -209,9 +209,15 @@
function errorPosition(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
statusText.textContent = "Hata: Konum alınamadı!";
statusText.style.color = "#ef4444";
stopTracking();
if (err.code === 1) { // PERMISSION_DENIED
statusText.textContent = "HATA: Konum izni reddedildi! Lütfen tarayıcı ayarlarından konum izni verin.";
statusText.style.color = "#ef4444"; // Kırmızı
stopTracking(); // İzin yoksa devam edemeyiz
} else {
statusText.textContent = "Konum aranıyor... Lütfen bekleyin veya açık alana çıkın.";
statusText.style.color = "#fbbf24"; // Sarı uyarı rengi
// Timeout veya unvailable durumlarında aramaya devam et
}
}
function startTracking() {
@@ -231,7 +237,7 @@
// watchPosition keeps getting location updates
watchId = navigator.geolocation.watchPosition(successPosition, errorPosition, {
enableHighAccuracy: true,
timeout: 10000,
timeout: 30000,
maximumAge: 0
});
}

View File

@@ -1,53 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Gider/Ceza List (Tree) Görünümü -->
<record id="view_mymach_fleet_fine_list" model="ir.ui.view">
<field name="name">mymach.fleet.fine.list</field>
<record id="view_mymach_fleet_fine_list_v2" model="ir.ui.view">
<field name="name">mymach.fleet.fine.list.v2</field>
<field name="model">mymach.fleet.fine</field>
<field name="arch" type="xml">
<list string="Giderler ve Cezalar">
<field name="name"/>
<field name="charge_type"/>
<field name="other_charge_type_name" invisible="charge_type != 'other'" required="charge_type == 'other'"/>
<field name="vehicle_id"/>
<field name="driver_id"/>
<field name="amount"/>
<field name="date"/>
<field name="is_personal_violation" widget="boolean_toggle"/>
<field name="state" widget="badge" decoration-success="state == 'deducted'" decoration-info="state == 'confirmed'" decoration-muted="state == 'draft'"/>
<list string="Cezalar ve HGS Geçişleri" decoration-danger="is_personal_violation and state != 'deducted'" decoration-success="state == 'deducted'">
<field name="date" string="İşlem Tarihi"/>
<field name="name" string="Kayıt Adı"/>
<field name="charge_type" string="Gider Tipi"/>
<field name="vehicle_id" string="Araç"/>
<field name="driver_id" string="Sürücü"/>
<field name="amount" widget="monetary" string="Tutar"/>
<field name="currency_id" column_invisible="True"/>
<field name="is_personal_violation" widget="boolean_toggle" string="Şahsi İhlal mi?"/>
<field name="state" widget="badge" string="Durum" decoration-info="state == 'draft'" decoration-warning="state == 'confirmed'" decoration-success="state == 'deducted'"/>
</list>
</field>
</record>
<!-- Gider/Ceza Form Görünümü -->
<record id="view_mymach_fleet_fine_form" model="ir.ui.view">
<field name="name">mymach.fleet.fine.form</field>
<record id="view_mymach_fleet_fine_form_v2" model="ir.ui.view">
<field name="name">mymach.fleet.fine.form.v2</field>
<field name="model">mymach.fleet.fine</field>
<field name="arch" type="xml">
<form string="Gider ve Ceza Kaydı">
<form string="Ceza/Geçiş Kaydı">
<header>
<button name="action_confirm" string="Onayla" type="object" class="oe_highlight" invisible="state != 'draft'"/>
<button name="action_deduct_to_payroll" string="Bordroya/Cariye Yansıt" type="object" class="btn-danger" invisible="state != 'confirmed' or not is_personal_violation"/>
<button name="action_deduct_to_payroll" string="Bordroya Yansıt" type="object" class="btn-danger" invisible="state != 'confirmed' or not is_personal_violation"/>
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,deducted"/>
</header>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="Geçiş/Ceza Fiş No (Örn: HGS-981298)"/>
<field name="name" placeholder="Geçiş/Ceza Fiş Numarası (Örn: HGS-981298)"/>
</h1>
</div>
<group>
<group string="Detaylar">
<field name="charge_type"/>
<field name="other_charge_type_name" invisible="charge_type != 'other'" required="charge_type == 'other'"/>
<field name="vehicle_id"/>
<field name="driver_id"/>
<group string="DETAY">
<field name="charge_type" readonly="state != 'draft'"/>
<field name="other_charge_type_name" invisible="charge_type != 'other'" readonly="state != 'draft'"/>
<field name="vehicle_id" readonly="state != 'draft'"/>
<field name="driver_id" readonly="1"/>
</group>
<group string="Finansal Bilgiler">
<field name="amount"/>
<group string="FİNANSAL BİLGİLER">
<field name="amount" readonly="state != 'draft'"/>
<field name="currency_id" invisible="1"/>
<field name="date"/>
<field name="is_personal_violation"/>
<field name="date" readonly="state != 'draft'"/>
<field name="is_personal_violation" readonly="1"/>
</group>
</group>
</sheet>
@@ -57,32 +57,37 @@
</record>
<!-- Gider/Ceza Arama Görünümü -->
<record id="view_mymach_fleet_fine_search" model="ir.ui.view">
<field name="name">mymach.fleet.fine.search</field>
<record id="view_mymach_fleet_fine_search_v2" model="ir.ui.view">
<field name="name">mymach.fleet.fine.search.v2</field>
<field name="model">mymach.fleet.fine</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
<field name="vehicle_id"/>
<field name="driver_id"/>
<filter string="Şahsi İhlaller" name="personal_violation" domain="[('is_personal_violation', '=', True)]"/>
<filter string="Gider Türü" name="group_by_type" context="{'group_by': 'charge_type'}"/>
<filter string="Durum" name="group_by_state" context="{'group_by': 'state'}"/>
<filter string="Taslak" name="draft" domain="[('state','=','draft')]"/>
<filter string="Onaylanan" name="confirmed" domain="[('state','=','confirmed')]"/>
<filter string="Şahsi İhlaller" name="personal" domain="[('is_personal_violation','=',True)]"/>
</search>
</field>
</record>
<!-- Aksiyon -->
<record id="action_mymach_fleet_fine" model="ir.actions.act_window">
<field name="name">Cezalar ve HGS Geçişleri</field>
<field name="name">Cezalar ve HGS</field>
<field name="res_model">mymach.fleet.fine</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_mymach_fleet_fine_search"/>
<field name="search_view_id" ref="view_mymach_fleet_fine_search_v2"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
İlk ceza veya geçiş kaydınızı oluşturun!
</p>
</field>
</record>
<!-- Menü Elemanı -->
<menuitem id="menu_mymach_fleet_fine"
name="Giderler &amp; Cezalar"
name="Cezalar ve HGS"
parent="menu_mymach_fleet_intelligence_root"
action="action_mymach_fleet_fine"
sequence="3"/>

View File

@@ -49,7 +49,7 @@
</group>
<group>
<field name="monthly_cost_avg_display" widget="monetary" options="{'currency_field': 'active_display_currency_id'}" string="AYLIK ORTALAMA MALİYET" decoration-bf="1" style="font-weight: bold; color: #f59e0b; font-size: 15px;"/>
<field name="total_expense_amount" widget="monetary" options="{'currency_field': 'try_currency_id'}" string="Toplam Birikmiş Gider (TL)"/>
<field name="total_expense_amount" widget="monetary" options="{'currency_field': 'try_currency_id'}" string="Toplam Birikmiş Gider"/>
<field name="try_currency_id" invisible="1"/>
<field name="active_display_currency_id" invisible="1"/>
</group>
@@ -156,6 +156,26 @@
</field>
</record>
<!-- GPS Harita Aksiyonu (URL) -->
<record id="action_mymach_fleet_map" model="ir.actions.act_url">
<field name="name">Canlı Filo Haritası</field>
<field name="url">/mymach/fleet/map</field>
<field name="target">new</field>
</record>
<!-- Ana Menü Altına Harita Ekleme -->
<menuitem id="menu_mymach_fleet_map"
name="📍 Canlı Harita"
parent="fleet.menu_root"
action="action_mymach_fleet_map"
sequence="2"/>
<menuitem id="menu_mymach_dashboard"
name="📊 Finansal Analiz (Paneller)"
parent="fleet.menu_root"
action="action_fleet_dashboard"
sequence="1"/>
<menuitem id="menu_mymach_fleet_vehicle"
name="Araçlar"
parent="menu_mymach_fleet_intelligence_root"

View File

@@ -23,6 +23,13 @@
<field name="domain">[('is_resolved', '=', False)]</field>
</record>
<!-- Sefere Başla / Mobil Takip Ekranı URL Aksiyonu -->
<record id="action_mymach_driver_tracker_url" model="ir.actions.act_url">
<field name="name">Sefere Başla (Canlı Takip)</field>
<field name="url">/mymach/driver/tracker</field>
<field name="target">new</field>
</record>
<!-- Sunucu Aksiyonu: Sadece Canlı Haritayı Gösteren Görünüm İçin -->
<record id="action_mymach_fleet_home_chart_server" model="ir.actions.server">
<field name="name">Canlı Harita Analizi</field>
@@ -46,8 +53,8 @@ action = {
</field>
</record>
<record id="view_mymach_fleet_home_form" model="ir.ui.view">
<field name="name">mymach.fleet.home.form</field>
<record id="view_mymach_fleet_home_form_v2" model="ir.ui.view">
<field name="name">mymach.fleet.home.form.v2</field>
<field name="model">mymach.fleet.home</field>
<field name="arch" type="xml">
<form string="MyMach Filo Zekası" class="mymach_zara_form" create="false" delete="false">
@@ -63,7 +70,7 @@ action = {
<!-- Dil Seçici Kaldırıldı (Odoo Standardı Kullanılacak) -->
<!-- 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: #ffffff !important; text-shadow: 0 0 15px rgba(255, 255, 255, 0.5); font-weight: 800;">MYMACH FLEET INTELLIGENCE</h1>
<h1 class="mymach_zara_title" style="color: #ff0000 !important; text-shadow: 0 0 15px rgba(255, 0, 0, 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>
@@ -123,6 +130,11 @@ action = {
<i class="fa fa-bell-o mb-2"></i><br/> Bekleyen Rota Uyarılarım
</button>
</div>
<div class="col-md mb-2">
<button name="action_open_tracker" type="object" class="btn mymach_zara_btn" style="width: 100%; border-color: #10b981; color: #10b981; background: rgba(16, 185, 129, 0.1);">
<i class="fa fa-location-arrow fa-spin-pulse mb-2"></i><br/> 📍 Sefere Başla
</button>
</div>
</div>
</div>
@@ -204,7 +216,7 @@ action = {
'res_model': 'mymach.fleet.home',
'res_id': record.id,
'view_mode': 'form',
'views': [(env.ref('mymach_fleet_intelligence.view_mymach_fleet_home_form').id, 'form')],
'views': [(env.ref('mymach_fleet_intelligence.view_mymach_fleet_home_form_v2').id, 'form')],
'target': 'current',
'context': {'clear_breadcrumbs': True},
}

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="fleet_map_template" name="Canlı Filo Haritası">
<html>
<head>
<title>Canlı Filo Haritası | MyMach</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<!-- Leaflet.js CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Inter', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
#map {
width: 100vw;
height: 100vh;
background: #e5e7eb;
}
.custom-popup .leaflet-popup-content-wrapper {
background: rgba(15, 23, 42, 0.95);
color: white;
border-radius: 8px;
padding: 2px;
}
.custom-popup .leaflet-popup-tip {
background: rgba(15, 23, 42, 0.95);
}
.popup-header {
font-weight: 700;
font-size: 16px;
border-bottom: 1px solid rgba(255,255,255,0.1);
padding-bottom: 6px;
margin-bottom: 6px;
color: #38bdf8;
}
.popup-row {
margin-bottom: 4px;
font-size: 13px;
}
.status-badge {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
}
.status-moving { background-color: #10b981; color: white; }
.status-parked { background-color: #ef4444; color: white; }
.status-offline { background-color: #6b7280; color: white; }
/* Geri Dön Butonu */
.back-btn {
position: absolute;
top: 20px;
left: 60px;
z-index: 1000;
background: #0f172a;
color: white;
padding: 10px 16px;
border-radius: 6px;
text-decoration: none;
font-weight: 600;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
border: 1px solid #334155;
transition: all 0.2s;
}
.back-btn:hover {
background: #1e293b;
color: #38bdf8;
}
</style>
</head>
<body>
<a href="/web" class="back-btn">
&lt; Panele Dön
</a>
<div id="map"></div>
<!-- Leaflet.js Script -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
// Odoo'dan gelen JSON verisini parse et
var vehiclesRaw = '<t t-out="vehicles_json"/>';
var vehicles = [];
try {
// HTML escape karakterlerini düzelt (&quot; -> ")
vehiclesRaw = vehiclesRaw.replace(/&amp;quot;/g, '"').replace(/&quot;/g, '"');
// Boole değerlerini düzelt (False -> false, True -> true)
vehiclesRaw = vehiclesRaw.replace(/False/g, 'false').replace(/True/g, 'true');
// Tek tırnakları JSON formatına uydur (eğer stringify doğru gelmemişse)
vehiclesRaw = vehiclesRaw.replace(/'/g, '"');
vehicles = JSON.parse(vehiclesRaw);
} catch (e) {
console.error("Araç verisi parse edilemedi:", e);
}
// Haritayı başlat (Türkiye merkezli)
var map = L.map('map').setView([39.0, 35.0], 6);
// OpenStreetMap Katmanı
L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', {
attribution: '&amp;copy; &lt;a href="https://www.openstreetmap.org/copyright"&gt;OpenStreetMap&lt;/a&gt; contributors',
subdomains: 'abcd',
maxZoom: 19
}).addTo(map);
// Özel İkon Fonksiyonu (Duruma Göre Renk)
function getIcon(status) {
var color = '#6b7280'; // offline
if (status === 'moving') color = '#10b981'; // green
if (status === 'parked') color = '#ef4444'; // red
var svgIcon = `
&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="${color}" width="32" height="32"&gt;
&lt;path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/&gt;
&lt;/svg&gt;
`;
return L.divIcon({
className: 'custom-div-icon',
html: svgIcon,
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -32]
});
}
// Araçları Haritaya Ekle
var bounds = [];
vehicles.forEach(function(v) {
if (v.lat &amp;&amp; v.lng) {
var statusText = v.status === 'moving' ? 'HAREKETLİ' : (v.status === 'parked' ? 'PARK HALİNDE' : 'ÇEVRİMDIŞI');
var statusClass = v.status === 'moving' ? 'status-moving' : (v.status === 'parked' ? 'status-parked' : 'status-offline');
var popupContent = `
&lt;div class="popup-header"&gt;${v.plate}&lt;/div&gt;
&lt;div class="popup-row"&gt;&lt;strong&gt;Sürücü:&lt;/strong&gt; ${v.driver}&lt;/div&gt;
&lt;div class="popup-row"&gt;&lt;strong&gt;Hız:&lt;/strong&gt; ${v.speed} km/s&lt;/div&gt;
&lt;div class="popup-row"&gt;&lt;strong&gt;Son Güncelleme:&lt;/strong&gt; ${v.last_update}&lt;/div&gt;
&lt;div class="popup-row" style="margin-top: 8px;"&gt;
&lt;span class="status-badge ${statusClass}"&gt;${statusText}&lt;/span&gt;
&lt;/div&gt;
`;
var marker = L.marker([v.lat, v.lng], {icon: getIcon(v.status)})
.addTo(map)
.bindPopup(popupContent, {className: 'custom-popup'});
bounds.push([v.lat, v.lng]);
}
});
// Tüm araçları görecek şekilde zoom ayarla
if (bounds.length &gt; 0) {
map.fitBounds(bounds, {padding: [50, 50]});
}
});
</script>
</body>
</html>
</template>
</odoo>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Periyodik Bakım Hatırlatıcı Liste (Ağaç) Görünümü -->
<record id="view_mymach_fleet_periodic_maintenance_tree" model="ir.ui.view">
<field name="name">mymach.fleet.periodic.maintenance.tree</field>
<record id="view_mymach_fleet_periodic_maintenance_tree_v2" model="ir.ui.view">
<field name="name">mymach.fleet.periodic.maintenance.tree.v2</field>
<field name="model">mymach.fleet.periodic.maintenance</field>
<field name="arch" type="xml">
<list string="Periyodik Bakım Hatırlatıcıları" decoration-danger="state == 'overdue'" decoration-info="state == 'pending'" decoration-success="state == 'done'">
@@ -18,8 +18,8 @@
</record>
<!-- Periyodik Bakım Hatırlatıcı Form Görünümü -->
<record id="view_mymach_fleet_periodic_maintenance_form" model="ir.ui.view">
<field name="name">mymach.fleet.periodic.maintenance.form</field>
<record id="view_mymach_fleet_periodic_maintenance_form_v2" model="ir.ui.view">
<field name="name">mymach.fleet.periodic.maintenance.form.v2</field>
<field name="model">mymach.fleet.periodic.maintenance</field>
<field name="arch" type="xml">
<form string="Periyodik Bakım Hatırlatıcısı">
@@ -60,8 +60,8 @@
</record>
<!-- Periyodik Bakım Arama (Search) Görünümü -->
<record id="view_mymach_fleet_periodic_maintenance_search" model="ir.ui.view">
<field name="name">mymach.fleet.periodic.maintenance.search</field>
<record id="view_mymach_fleet_periodic_maintenance_search_v2" model="ir.ui.view">
<field name="name">mymach.fleet.periodic.maintenance.search.v2</field>
<field name="model">mymach.fleet.periodic.maintenance</field>
<field name="arch" type="xml">
<search string="Bakım Arama">
@@ -83,7 +83,7 @@
<field name="name">Periyodik Bakım Takibi</field>
<field name="res_model">mymach.fleet.periodic.maintenance</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_mymach_fleet_periodic_maintenance_search"/>
<field name="search_view_id" ref="view_mymach_fleet_periodic_maintenance_search_v2"/>
<field name="context">{'search_default_overdue': 1, 'search_default_pending': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">

View File

@@ -1,25 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_mymach_fleet_security_log_list" model="ir.ui.view">
<field name="name">mymach.fleet.security.log.list</field>
<record id="view_mymach_fleet_security_log_list_v2" model="ir.ui.view">
<field name="name">mymach.fleet.security.log.list.v2</field>
<field name="model">mymach.fleet.security.log</field>
<field name="arch" type="xml">
<list string="Güvenlik Logları" decoration-danger="status == 'in_office_moving'" decoration-warning="status == 'exception' and not is_resolved" decoration-success="is_resolved or status == 'project_match'">
<field name="datetime"/>
<field name="name"/>
<field name="vehicle_id"/>
<field name="driver_id"/>
<field name="status"/>
<field name="is_resolved"/>
<field name="deduction_amount" widget="monetary" invisible="deduction_amount == 0"/>
<field name="datetime" string="Tarih / Saat"/>
<field name="name" string="Kayıt Adı"/>
<field name="vehicle_id" string="Araç"/>
<field name="driver_id" string="Sürücü"/>
<field name="status" string="Durum"/>
<field name="is_resolved" string="Çözümlendi mi?"/>
<field name="deduction_amount" widget="monetary" invisible="deduction_amount == 0" string="Kesinti Tutarı"/>
<field name="currency_id" column_invisible="True"/>
<button name="action_open_exception_wizard" type="object" string="Sürpriz Rotayı Çözümle" class="btn-primary" invisible="is_resolved or status != 'exception'"/>
</list>
</field>
</record>
<record id="view_mymach_fleet_security_log_form" model="ir.ui.view">
<field name="name">mymach.fleet.security.log.form</field>
<record id="view_mymach_fleet_security_log_form_v2" model="ir.ui.view">
<field name="name">mymach.fleet.security.log.form.v2</field>
<field name="model">mymach.fleet.security.log</field>
<field name="arch" type="xml">
<form string="Güvenlik Logu">