Add Sefere Basla button and fix GPS cache issue
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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)}"
|
||||
|
||||
|
||||
62
addons/mymach_fleet_intelligence/extract_po.py
Normal file
62
addons/mymach_fleet_intelligence/extract_po.py
Normal 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")
|
||||
0
addons/mymach_fleet_intelligence/force_translate.py
Normal file
0
addons/mymach_fleet_intelligence/force_translate.py
Normal file
File diff suppressed because it is too large
Load Diff
0
addons/mymach_fleet_intelligence/load_en_db.py
Normal file
0
addons/mymach_fleet_intelligence/load_en_db.py
Normal 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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.")
|
||||
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
)
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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="Açı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):
|
||||
|
||||
@@ -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', 'Ağı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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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="Açıklama / Toplantı Notları", required=True)
|
||||
|
||||
def action_confirm(self):
|
||||
self.ensure_one()
|
||||
|
||||
@@ -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 """
|
||||
|
||||
31
addons/mymach_fleet_intelligence/scratch_fix_groups.py
Normal file
31
addons/mymach_fleet_intelligence/scratch_fix_groups.py
Normal 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()
|
||||
0
addons/mymach_fleet_intelligence/setup_en_gb.py
Normal file
0
addons/mymach_fleet_intelligence/setup_en_gb.py
Normal 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');
|
||||
|
||||
0
addons/mymach_fleet_intelligence/translate_en.py
Normal file
0
addons/mymach_fleet_intelligence/translate_en.py
Normal 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 -->
|
||||
|
||||
@@ -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 & 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 & 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 -->
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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 & Cezalar"
|
||||
name="Cezalar ve HGS"
|
||||
parent="menu_mymach_fleet_intelligence_root"
|
||||
action="action_mymach_fleet_fine"
|
||||
sequence="3"/>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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},
|
||||
}
|
||||
|
||||
172
addons/mymach_fleet_intelligence/views/map_template.xml
Normal file
172
addons/mymach_fleet_intelligence/views/map_template.xml
Normal 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">
|
||||
< 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 (" -> ")
|
||||
vehiclesRaw = vehiclesRaw.replace(/&quot;/g, '"').replace(/"/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: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> 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 = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="${color}" width="32" height="32">
|
||||
<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"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
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 && 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 = `
|
||||
<div class="popup-header">${v.plate}</div>
|
||||
<div class="popup-row"><strong>Sürücü:</strong> ${v.driver}</div>
|
||||
<div class="popup-row"><strong>Hız:</strong> ${v.speed} km/s</div>
|
||||
<div class="popup-row"><strong>Son Güncelleme:</strong> ${v.last_update}</div>
|
||||
<div class="popup-row" style="margin-top: 8px;">
|
||||
<span class="status-badge ${statusClass}">${statusText}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 > 0) {
|
||||
map.fitBounds(bounds, {padding: [50, 50]});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user