Add Sefere Basla button and fix GPS cache issue
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
'views/security_log_views.xml',
|
'views/security_log_views.xml',
|
||||||
'views/route_exception_wizard_views.xml',
|
'views/route_exception_wizard_views.xml',
|
||||||
'views/dashboard_template.xml',
|
'views/dashboard_template.xml',
|
||||||
|
'views/map_template.xml',
|
||||||
'views/fleet_fine_views.xml',
|
'views/fleet_fine_views.xml',
|
||||||
'views/fleet_handover_views.xml',
|
'views/fleet_handover_views.xml',
|
||||||
'views/fuel_index_views.xml',
|
'views/fuel_index_views.xml',
|
||||||
|
|||||||
@@ -5,6 +5,23 @@ import json
|
|||||||
|
|
||||||
class MymachFleetController(http.Controller):
|
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)
|
@http.route('/mymach/fleet/ping', type='jsonrpc', auth='public', methods=['POST'], csrf=False)
|
||||||
def fleet_ping(self, vehicle_id, latitude, longitude, **kwargs):
|
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':
|
if is_project_zone and status != 'in_office_moving':
|
||||||
status = 'project_match'
|
status = 'project_match'
|
||||||
|
|
||||||
# Güvenlik Logunu Yarat (Eğer İhlal varsa Otomatik Notification atacaktır)
|
# Faz 3: Araç Kartına Anlık Lokasyon Yazılması
|
||||||
log = request.env['mymach.fleet.security.log'].sudo().create({
|
speed = kwargs.get('speed', 0.0)
|
||||||
'vehicle_id': vehicle.id,
|
vehicle.write({
|
||||||
'latitude': latitude,
|
'latitude': latitude,
|
||||||
'longitude': longitude,
|
'longitude': longitude,
|
||||||
'status': status,
|
'last_gps_update': fields.Datetime.now(),
|
||||||
'name': f'Otonom API Ping ({vehicle.license_plate})'
|
'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')
|
@http.route('/mymach/fleet/dashboard', type='http', auth='user')
|
||||||
def fleet_dashboard(self, **kwargs):
|
def fleet_dashboard(self, **kwargs):
|
||||||
@@ -335,6 +365,31 @@ class MymachFleetController(http.Controller):
|
|||||||
'google_maps_api_key': google_maps_api_key
|
'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')
|
@http.route('/mymach/driver/tracker', type='http', auth='user')
|
||||||
def driver_tracker(self, **kwargs):
|
def driver_tracker(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -344,3 +399,17 @@ class MymachFleetController(http.Controller):
|
|||||||
return request.render('mymach_fleet_intelligence.driver_tracker_template', {
|
return request.render('mymach_fleet_intelligence.driver_tracker_template', {
|
||||||
'vehicles': vehicles,
|
'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 fuel_index
|
||||||
from . import res_currency
|
from . import res_currency
|
||||||
from . import res_users
|
from . import res_users
|
||||||
|
from . import fleet_vehicle_model
|
||||||
|
|||||||
@@ -12,18 +12,18 @@ class MymachFleetContract(models.Model):
|
|||||||
_inherit = ['mail.thread']
|
_inherit = ['mail.thread']
|
||||||
|
|
||||||
# Sözleşme Adı
|
# 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ğı)
|
# 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
|
# İ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)
|
# Departman (Çalışan kaydından otomatik hesaplanır)
|
||||||
department_id = fields.Many2one(
|
department_id = fields.Many2one(
|
||||||
'hr.department',
|
'hr.department',
|
||||||
string="Department",
|
string="Departman",
|
||||||
related="employee_id.department_id",
|
related="employee_id.department_id",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
store=True
|
store=True
|
||||||
@@ -36,26 +36,26 @@ class MymachFleetContract(models.Model):
|
|||||||
('unlimited', 'Sınırsız (Limitsiz) Şahsi Kullanım'),
|
('unlimited', 'Sınırsız (Limitsiz) Şahsi Kullanım'),
|
||||||
('rental', 'Kiralık (Kira Bedelli) Tahsis'),
|
('rental', 'Kiralık (Kira Bedelli) Tahsis'),
|
||||||
('forbidden', 'Yasaklı (Şahsi Kullanım Dışı)')
|
('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)
|
# 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
|
# 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
|
# Eve Git/Gel İzni
|
||||||
commute_allowed = fields.Selection([
|
commute_allowed = fields.Selection([
|
||||||
('allowed', 'Allowed'),
|
('allowed', 'İzin Verildi'),
|
||||||
('forbidden', 'Forbidden')
|
('forbidden', 'Yasaklandı')
|
||||||
], string="Commute Allowed", required=True, default='forbidden')
|
], string="Eve Git/Gel İzni", required=True, default='forbidden')
|
||||||
|
|
||||||
# Cihaz İzni Yönetimi (Madde 5 referansı)
|
# Cihaz İzni Yönetimi (Madde 5 referansı)
|
||||||
personal_device_gps_allowed = fields.Boolean(string="Personal Device GPS Allowed")
|
personal_device_gps_allowed = fields.Boolean(string="Kişisel Cihaz GPS İzni")
|
||||||
company_phone_device_id = fields.Char(string="Company Phone Device ID (UUID)")
|
company_phone_device_id = fields.Char(string="Şirket Telefonu Cihaz ID (UUID)")
|
||||||
|
|
||||||
# Sürücü Skor Kartı (Gamification)
|
# 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):
|
def write(self, vals):
|
||||||
if 'driver_score' in vals:
|
if 'driver_score' in vals:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class MymachFleetFine(models.Model):
|
|||||||
_description = 'Filo Trafik Ceza ve HGS Yönetimi'
|
_description = 'Filo Trafik Ceza ve HGS Yönetimi'
|
||||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
_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([
|
charge_type = fields.Selection([
|
||||||
('tax', 'Vergi Gideri'),
|
('tax', 'Vergi Gideri'),
|
||||||
('insurance', 'Zorunlu Trafik Sigortası'),
|
('insurance', 'Zorunlu Trafik Sigortası'),
|
||||||
@@ -23,28 +23,28 @@ class MymachFleetFine(models.Model):
|
|||||||
('fine', 'Trafik Cezası'),
|
('fine', 'Trafik Cezası'),
|
||||||
('parking', 'Otopark Ücreti'),
|
('parking', 'Otopark Ücreti'),
|
||||||
('other', 'Diğer')
|
('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)
|
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)
|
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True, tracking=True)
|
||||||
driver_id = fields.Many2one('res.partner', string="Driver", related='vehicle_id.driver_id', store=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)
|
amount = fields.Monetary(string="Tutar", 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)
|
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="Transaction Date", default=fields.Date.context_today, required=True, tracking=True)
|
date = fields.Date(string="İşlem Tarihi", default=fields.Date.context_today, required=True, tracking=True)
|
||||||
|
|
||||||
is_personal_violation = fields.Boolean(
|
is_personal_violation = fields.Boolean(
|
||||||
string="Personal Use Violation?",
|
string="Şahsi Kullanım İhlali mi?",
|
||||||
compute="_compute_personal_violation",
|
compute="_compute_personal_violation",
|
||||||
store=True,
|
store=True,
|
||||||
tracking=True
|
tracking=True
|
||||||
)
|
)
|
||||||
|
|
||||||
state = fields.Selection([
|
state = fields.Selection([
|
||||||
('draft', 'Draft'),
|
('draft', 'Taslak'),
|
||||||
('confirmed', 'Confirmed'),
|
('confirmed', 'Onaylandı'),
|
||||||
('deducted', 'Deducted to Payroll/Current')
|
('deducted', 'Bordroya / Cari Hesaba Yansıtıldı')
|
||||||
], string="Status", default='draft', tracking=True)
|
], string="Durum", default='draft', tracking=True)
|
||||||
|
|
||||||
@api.depends('vehicle_id', 'date')
|
@api.depends('vehicle_id', 'date')
|
||||||
def _compute_personal_violation(self):
|
def _compute_personal_violation(self):
|
||||||
|
|||||||
@@ -15,36 +15,36 @@ class MymachFleetHandover(models.Model):
|
|||||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
_order = 'id desc'
|
_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)
|
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True, tracking=True)
|
||||||
driver_id = fields.Many2one('res.partner', string="Driver (Employee)", 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)
|
default=lambda self: self.env.user.partner_id)
|
||||||
|
|
||||||
state = fields.Selection([
|
state = fields.Selection([
|
||||||
('draft', 'Draft'),
|
('draft', 'Taslak'),
|
||||||
('taken', 'Handed Over (In Use)'),
|
('taken', 'Teslim Alındı (Kullanımda)'),
|
||||||
('returned', 'Returned'),
|
('returned', 'İade Edildi'),
|
||||||
('compared', 'Inspected / Approved')
|
('compared', 'Denetlendi / Onaylandı')
|
||||||
], string="Status", default='draft', required=True, tracking=True)
|
], string="Durum", default='draft', required=True, tracking=True)
|
||||||
|
|
||||||
# --- TAKE (START) DETAILS ---
|
# --- TAKE (START) DETAILS ---
|
||||||
take_datetime = fields.Datetime(string="Take Datetime", tracking=True)
|
take_datetime = fields.Datetime(string="Teslim Alma Tarihi/Saati", tracking=True)
|
||||||
start_odometer = fields.Float(string="Start Odometer", tracking=True)
|
start_odometer = fields.Float(string="Başlangıç KM", tracking=True)
|
||||||
start_fuel_level = fields.Float(string="Start Fuel Level (%)", help="E.g. 50 (%50 Fuel)", 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="Take Photo", attachment=True)
|
start_photo = fields.Binary(string="Teslim Alma Fotoğrafı", attachment=True)
|
||||||
|
|
||||||
# --- RETURN (END) DETAILS ---
|
# --- RETURN (END) DETAILS ---
|
||||||
return_datetime = fields.Datetime(string="Return Datetime", tracking=True)
|
return_datetime = fields.Datetime(string="İade Tarihi/Saati", tracking=True)
|
||||||
end_odometer = fields.Float(string="End Odometer", tracking=True)
|
end_odometer = fields.Float(string="Bitiş KM", tracking=True)
|
||||||
end_fuel_level = fields.Float(string="End Fuel Level (%)", help="E.g. 30 (%30 Fuel)", 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="Return Photo", attachment=True)
|
end_photo = fields.Binary(string="İade Fotoğrafı", attachment=True)
|
||||||
|
|
||||||
# --- MANAGER INSPECTION DETAILS ---
|
# --- MANAGER INSPECTION DETAILS ---
|
||||||
odometer_diff = fields.Float(string="Odometer Diff (KM)", compute="_compute_diffs", store=True)
|
odometer_diff = fields.Float(string="KM Farkı", compute="_compute_diffs", store=True)
|
||||||
fuel_diff = fields.Float(string="Fuel Diff (%)", compute="_compute_diffs", store=True)
|
fuel_diff = fields.Float(string="Yakıt Farkı (%)", compute="_compute_diffs", store=True)
|
||||||
manager_note = fields.Text(string="Manager Inspection Notes", tracking=True)
|
manager_note = fields.Text(string="Yönetici Denetim Notları", tracking=True)
|
||||||
is_approved = fields.Boolean(string="Inspection Approval", tracking=True)
|
is_approved = fields.Boolean(string="Denetim Onayı", tracking=True)
|
||||||
|
|
||||||
@api.depends('start_odometer', 'end_odometer', 'start_fuel_level', 'end_fuel_level')
|
@api.depends('start_odometer', 'end_odometer', 'start_fuel_level', 'end_fuel_level')
|
||||||
def _compute_diffs(self):
|
def _compute_diffs(self):
|
||||||
|
|||||||
@@ -1,7 +1,42 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api, _
|
||||||
from datetime import timedelta
|
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):
|
class FleetVehicle(models.Model):
|
||||||
"""
|
"""
|
||||||
Odoo Standart fleet.vehicle Modeli Uzantısı
|
Odoo Standart fleet.vehicle Modeli Uzantısı
|
||||||
@@ -10,65 +45,95 @@ class FleetVehicle(models.Model):
|
|||||||
_inherit = 'fleet.vehicle'
|
_inherit = 'fleet.vehicle'
|
||||||
|
|
||||||
# Alt Görev 2.2: Akıllı Maliyet Tablosu ve Döviz Stabilizasyonu
|
# Alt Görev 2.2: Akıllı Maliyet Tablosu ve Döviz Stabilizasyonu
|
||||||
purchase_value = fields.Monetary(string="Purchase Value", currency_field='currency_id')
|
purchase_value = fields.Monetary(string="Satın Alma Bedeli", currency_field='currency_id')
|
||||||
depreciation_years = fields.Integer(string="Depreciation Years", default=5)
|
depreciation_years = fields.Integer(string="Amortisman Süresi (Yıl)", default=5)
|
||||||
annual_fixed_cost = fields.Float(string="Annual Fixed Cost")
|
annual_fixed_cost = fields.Float(string="Yıllık Sabit Gider")
|
||||||
index_currency_id = fields.Many2one('res.currency', string="Index Currency (Depreciation)",
|
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)
|
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
|
# 3 Para Birimli Amortisman Maliyet Alanları ve Ortalama Değer
|
||||||
depreciation_cost_try = fields.Float(string="Depreciation Cost (TRY)", default=0.0)
|
depreciation_cost_try = fields.Float(string="Amortisman Maliyeti (Ana Döviz)", default=0.0)
|
||||||
depreciation_cost_usd = fields.Float(string="Depreciation Cost (USD)", default=0.0)
|
depreciation_cost_usd = fields.Float(string="Amortisman Maliyeti (Döviz 1)", default=0.0)
|
||||||
depreciation_cost_eur = fields.Float(string="Depreciation Cost (EUR)", default=0.0)
|
depreciation_cost_eur = fields.Float(string="Amortisman Maliyeti (Döviz 2)", default=0.0)
|
||||||
depreciation_cost_avg = fields.Float(string="Average Depreciation (TRY)", compute='_compute_depreciation_avg', store=True)
|
depreciation_cost_avg = fields.Float(string="Ortalama Amortisman (Ana Döviz)", compute='_compute_depreciation_avg', store=True)
|
||||||
|
|
||||||
# Son Bakım Kilometresi ve Tarihi
|
# Son Bakım Kilometresi ve Tarihi
|
||||||
last_maintenance_odometer = fields.Float(string="Last Maintenance Odometer (KM)", default=0.0)
|
last_maintenance_odometer = fields.Float(string="Son Bakım Kilometresi (KM)", default=0.0)
|
||||||
last_maintenance_date = fields.Date(string="Last Maintenance Date")
|
last_maintenance_date = fields.Date(string="Son Bakım Tarihi")
|
||||||
|
|
||||||
# KM Maliyeti
|
# 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
|
# Finans & Gider Matrisi Hesaplanmış Metrikler
|
||||||
maintenance_cost_ids = fields.One2many('mymach.fleet.maintenance.cost', 'vehicle_id', string="Expense Items")
|
maintenance_cost_ids = fields.One2many('mymach.fleet.maintenance.cost', 'vehicle_id', string="Gider Kalemleri")
|
||||||
total_expense_amount = fields.Float(string="Total Expense Amount (TRY)", compute="_compute_financial_metrics", store=True)
|
total_expense_amount = fields.Float(string="Toplam Birikmiş Gider", compute="_compute_financial_metrics", store=True)
|
||||||
total_driven_odometer = fields.Float(string="Total Distance Traveled (KM)", 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="Cost Per KM (TRY/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="Total Annual Cost (TRY/Year)", 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="Average Monthly Cost (TRY/Month)", 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)
|
# Ç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)
|
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="Selected 2nd Currency (FX 1)", default=lambda self: self.env.ref('base.USD', raise_if_not_found=False))
|
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="Selected 3rd Currency (FX 2)", default=lambda self: self.env.ref('base.EUR', 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="Active Display Currency", compute="_compute_financial_metrics")
|
active_display_currency_id = fields.Many2one('res.currency', string="Aktif Gösterim Para Birimi", compute="_compute_financial_metrics")
|
||||||
cost_display_mode = fields.Selection([
|
|
||||||
('try', '₺ Şirket Ana Para Birimi'),
|
# ---------------------------------------------------------
|
||||||
('curr1', '💵 2. Para Birimi (Döviz 1)'),
|
# GPS ve Harita Alanları (Faz 3)
|
||||||
('curr2', '💶 3. Para Birimi (Döviz 2)'),
|
# ---------------------------------------------------------
|
||||||
('average', '📊 3 Para Birimli Ortalama Tahminî Değer')
|
latitude = fields.Float(string="Enlem (GPS)", digits=(10, 7), tracking=True)
|
||||||
], string="Currency Display Mode (Listbox)", default='average')
|
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")
|
@api.depends('last_gps_update', 'gps_speed')
|
||||||
annual_cost_avg_display = fields.Float(string="Average Annual Estimated Value", compute="_compute_financial_metrics")
|
def _compute_gps_status(self):
|
||||||
monthly_cost_avg_display = fields.Float(string="Average Monthly Estimated Value", compute="_compute_financial_metrics")
|
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ı
|
# Ç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')
|
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="Annual (TL)", 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="Monthly (TL)", 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')
|
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="Annual (FX 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="Monthly (FX 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')
|
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="Annual (FX 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="Monthly (FX 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')
|
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="Average Annual (TL)", 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="Average Monthly (TL)", 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)
|
# 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)]")
|
mymach_location_id = fields.Many2one('res.partner', string="Şirket Lokasyonu", domain="[('is_company', '=', True)]")
|
||||||
|
|
||||||
# Faz 8: Akaryakıt API ve Prediktif Bakım
|
# Faz 8: Akaryakıt API ve Prediktif Bakım
|
||||||
tank_capacity = fields.Float(string="Fuel Tank Capacity (Liters)", default=50.0)
|
tank_capacity = fields.Float(string="Yakıt Depo Kapasitesi (Litre)", default=50.0)
|
||||||
current_fuel_level = fields.Float(string="Current Fuel Level (Liters)", readonly=True)
|
current_fuel_level = fields.Float(string="Güncel Yakıt Seviyesi (Litre)", readonly=True)
|
||||||
needs_predictive_maintenance = fields.Boolean(string="Predictive Maintenance Warning (AI)", default=False, readonly=True)
|
needs_predictive_maintenance = fields.Boolean(string="Kestirimci Bakım Uyarısı (AI)", default=False, readonly=True)
|
||||||
factory_avg_consumption = fields.Float(string="Factory Average Consumption (L/100KM)", default=6.0)
|
factory_avg_consumption = fields.Float(string="Fabrika Ortalama Tüketimi (L/100KM)", default=6.0)
|
||||||
|
|
||||||
indexed_purchase_value = fields.Monetary(
|
indexed_purchase_value = fields.Monetary(
|
||||||
string="Indexed Purchase Value",
|
string="Endekslenmiş Satın Alma Bedeli",
|
||||||
compute="_compute_indexed_purchase_value",
|
compute="_compute_indexed_purchase_value",
|
||||||
currency_field='index_currency_id'
|
currency_field='index_currency_id'
|
||||||
)
|
)
|
||||||
|
|
||||||
moving_average_maintenance_cost = fields.Monetary(
|
moving_average_maintenance_cost = fields.Monetary(
|
||||||
string="Moving Average Maintenance Cost",
|
string="Hareketli Ortalama Bakım Maliyeti",
|
||||||
compute="_compute_moving_average",
|
compute="_compute_moving_average",
|
||||||
currency_field='currency_id'
|
currency_field='currency_id'
|
||||||
)
|
)
|
||||||
is_maintenance_critical = fields.Boolean(
|
is_maintenance_critical = fields.Boolean(
|
||||||
string="Critical Cost Alarm",
|
string="Kritik Maliyet Alarmi",
|
||||||
compute="_compute_moving_average",
|
compute="_compute_moving_average",
|
||||||
search="_search_is_maintenance_critical"
|
search="_search_is_maintenance_critical"
|
||||||
)
|
)
|
||||||
@@ -339,6 +404,6 @@ class FleetVehicleLogServices(models.Model):
|
|||||||
('manual', 'Manuel Giriş'),
|
('manual', 'Manuel Giriş'),
|
||||||
('mymach_ocr', 'MyMach Akıllı Fiş İşleme Motoru'),
|
('mymach_ocr', 'MyMach Akıllı Fiş İşleme Motoru'),
|
||||||
('hr_expense', 'Odoo Masraf Modülü')
|
('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.")
|
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'
|
_description = 'Akaryakıt Fiyat Endeksi'
|
||||||
_order = 'date desc, id desc'
|
_order = 'date desc, id desc'
|
||||||
|
|
||||||
name = fields.Char(string="Index Ref", compute="_compute_name", store=True)
|
name = fields.Char(string="Endeks Referansı", compute="_compute_name", store=True)
|
||||||
date = fields.Date(string="Date", required=True, default=fields.Date.context_today)
|
date = fields.Date(string="Tarih", required=True, default=fields.Date.context_today)
|
||||||
fuel_type = fields.Selection([
|
fuel_type = fields.Selection([
|
||||||
('gasoline', 'Benzinli'),
|
('gasoline', 'Benzin'),
|
||||||
('diesel', 'Dizel'),
|
('diesel', 'Dizel'),
|
||||||
|
('electric', 'Elektrik'),
|
||||||
|
('hybrid', 'Hibrit'),
|
||||||
('lpg', 'LPG')
|
('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)
|
mom_change_perc = fields.Float(string="Aylık Değişim (Enflasyon) %", compute="_compute_inflation", store=True)
|
||||||
yoy_change_perc = fields.Float(string="Annual Change (Inflation) %", 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')
|
@api.depends('date', 'fuel_type')
|
||||||
def _compute_name(self):
|
def _compute_name(self):
|
||||||
|
|||||||
@@ -10,36 +10,56 @@ class MymachFleetHome(models.Model):
|
|||||||
_name = 'mymach.fleet.home'
|
_name = 'mymach.fleet.home'
|
||||||
_description = 'MyMach Filo Zekası Ana Pano'
|
_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')
|
display_title = fields.Char(compute='_compute_display_title')
|
||||||
upcoming_maintenance_html = fields.Html(compute='_compute_upcoming_maintenance_html')
|
upcoming_maintenance_html = fields.Html(compute='_compute_upcoming_maintenance_html')
|
||||||
|
|
||||||
# Dil Seçimi (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
|
# 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([
|
time_range = fields.Selection([
|
||||||
('week', 'Son 7 Gün (Haftalık)'),
|
('week', 'Son 7 Gün (Haftalık)'),
|
||||||
('month', 'Son 30 Gün (Aylık)'),
|
('month', 'Son 30 Gün (Aylık)'),
|
||||||
('year', 'Son 12 Ay (Yıllık)')
|
('year', 'Son 12 Ay (Yıllık)')
|
||||||
], string="Time Range", default='month', required=True)
|
], string="Zaman Aralığı", default='month', required=True)
|
||||||
chart_html = fields.Html(compute='_compute_chart_html', string="Fuel & KM Chart", sanitize=False)
|
|
||||||
|
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([
|
chart_metric = fields.Selection([
|
||||||
('combined', 'Birleşik Analiz (KM & Yakıt Hacmi)'),
|
('combined', 'Birleşik Analiz (KM & Yakıt Hacmi)'),
|
||||||
('km', 'Kilometre Analizi (KM)'),
|
('km', 'Kilometre Analizi (KM)'),
|
||||||
('fuel_liters', 'Yakıt Tüketimi (Litre)'),
|
('fuel_liters', 'Yakıt Tüketimi (Litre)'),
|
||||||
('fuel_cost', 'Yakıt Maliyeti (TL / Gider)')
|
('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ı
|
# 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_selected = fields.Selection([
|
||||||
('route_a', 'Rota A (En Kısa - 15.2 KM)'),
|
('route_a', 'Rota A (En Kısa - 15.2 KM)'),
|
||||||
('route_b', 'Rota B (Ekonomik - 16.5 KM)'),
|
('route_b', 'Rota B (Ekonomik - 16.5 KM)'),
|
||||||
('route_c', 'Rota C (Alternatif - 18.0 KM)')
|
('route_c', 'Rota C (Alternatif - 18.0 KM)')
|
||||||
], string="Selected Route", default='route_a')
|
], string="Seçilen Rota", default='route_a')
|
||||||
route_simulated_distance = fields.Float(string="Simulated Travel Distance (KM)", default=15.2)
|
route_simulated_distance = fields.Float(string="Simüle Edilen Seyahat Mesafesi (KM)", default=15.2)
|
||||||
route_results_html = fields.Html(string="Alternative Routes Result", sanitize=False)
|
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'
|
_name = 'mymach.fleet.maintenance.cost'
|
||||||
_description = 'Filo Periyodik Gider Matrisi'
|
_description = 'Filo Periyodik Gider Matrisi'
|
||||||
|
|
||||||
name = fields.Char(string="Description", required=True)
|
name = fields.Char(string="Açıklama", required=True)
|
||||||
product_id = fields.Many2one('product.template', string="Expense Item (Product)", required=True)
|
product_id = fields.Many2one('product.template', string="Gider Kalemi (Ürün)", required=True)
|
||||||
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True)
|
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True)
|
||||||
amount = fields.Monetary(string="Amount", currency_field='currency_id', required=True)
|
amount = fields.Monetary(string="Tutar", 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)
|
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="Date", default=fields.Date.context_today)
|
date = fields.Date(string="Tarih", default=fields.Date.context_today)
|
||||||
|
|
||||||
@api.constrains('amount')
|
@api.constrains('amount')
|
||||||
def _check_amount(self):
|
def _check_amount(self):
|
||||||
|
|||||||
@@ -12,31 +12,31 @@ class MymachFleetPeriodicMaintenance(models.Model):
|
|||||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
_order = 'target_odometer asc, target_date asc'
|
_order = 'target_odometer asc, target_date asc'
|
||||||
|
|
||||||
name = fields.Char(string="Maintenance Reference", compute="_compute_name", store=True, tracking=True)
|
name = fields.Char(string="Bakım Referansı", compute="_compute_name", store=True, tracking=True)
|
||||||
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True, ondelete='cascade', 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="Fuel Type", readonly=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="Current Odometer (KM)", readonly=True)
|
odometer = fields.Float(related='vehicle_id.odometer', string="Güncel Kilometre (KM)", readonly=True)
|
||||||
|
|
||||||
maintenance_type = fields.Selection([
|
maintenance_type = fields.Selection([
|
||||||
('small', 'Küçük Bakım (10k-15k KM)'),
|
('small', 'Küçük Bakım (10k-15k KM)'),
|
||||||
('medium', 'Orta Bakım (30k-45k KM)'),
|
('medium', 'Orta Bakım (30k-45k KM)'),
|
||||||
('heavy', 'Ağır Bakım (60k-90k KM)'),
|
('heavy', 'Ağır Bakım (60k-90k KM)'),
|
||||||
('annual', 'Yıllık Periyodik Bakım')
|
('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)
|
last_odometer = fields.Float(string="Son Bakım Kilometresi (KM)", default=0.0)
|
||||||
target_odometer = fields.Float(string="Target Odometer (KM)", required=True, tracking=True)
|
target_odometer = fields.Float(string="Hedef Kilometre (KM)", required=True, tracking=True)
|
||||||
target_date = fields.Date(string="Target Date (Annual)", required=True, tracking=True)
|
target_date = fields.Date(string="Hedef Tarih (Yıllık)", required=True, tracking=True)
|
||||||
|
|
||||||
state = fields.Selection([
|
state = fields.Selection([
|
||||||
('pending', 'Bekliyor'),
|
('pending', 'Bekliyor'),
|
||||||
('overdue', 'Süresi/KM Geçti'),
|
('overdue', 'Süresi/KM Geçti'),
|
||||||
('done', 'Tamamlandı')
|
('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)
|
description = fields.Text(string="Uygulanacak Standart İşlemler", compute='_compute_description', store=True)
|
||||||
date_completed = fields.Date(string="Completion Date", tracking=True)
|
date_completed = fields.Date(string="Tamamlanma Tarihi", tracking=True)
|
||||||
odometer_completed = fields.Float(string="Completion Odometer", tracking=True)
|
odometer_completed = fields.Float(string="Tamamlanma Kilometresi", tracking=True)
|
||||||
|
|
||||||
@api.depends('maintenance_type', 'vehicle_id.license_plate')
|
@api.depends('maintenance_type', 'vehicle_id.license_plate')
|
||||||
def _compute_name(self):
|
def _compute_name(self):
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ class ResUsers(models.Model):
|
|||||||
('manager', 'Yönetici (Admin)')
|
('manager', 'Yönetici (Admin)')
|
||||||
], string="Kullanıcı Yetkisi", default='none', help="MyMach Filo Sistemi Erişim Yetkisi")
|
], 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
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
users = super(ResUsers, self).create(vals_list)
|
users = super(ResUsers, self).create(vals_list)
|
||||||
|
|||||||
@@ -10,21 +10,21 @@ class MymachFleetRouteExceptionWizard(models.TransientModel):
|
|||||||
_name = 'mymach.fleet.route.exception.wizard'
|
_name = 'mymach.fleet.route.exception.wizard'
|
||||||
_description = 'İstisna Rota Onay Sihirbazı'
|
_description = 'İstisna Rota Onay Sihirbazı'
|
||||||
|
|
||||||
log_id = fields.Many2one('mymach.fleet.security.log', string="Security Log", required=True)
|
log_id = fields.Many2one('mymach.fleet.security.log', string="Güvenlik Logu", required=True)
|
||||||
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True)
|
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True)
|
||||||
|
|
||||||
exception_type = fields.Selection([
|
exception_type = fields.Selection([
|
||||||
('crm', 'CRM Fırsatı / Müşteri Ziyareti'),
|
('crm', 'CRM Fırsatı / Müşteri Ziyareti'),
|
||||||
('project', 'Proje Görevi / Maliyet Yansıtma'),
|
('project', 'Proje Görevi / Maliyet Yansıtma'),
|
||||||
('personal', 'Kişisel / Bireysel Kullanım'),
|
('personal', 'Kişisel / Bireysel Kullanım'),
|
||||||
('other', 'Diğer Gerekçeler')
|
('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")
|
partner_id = fields.Many2one('res.partner', string="Ziyaret Edilen Kişi")
|
||||||
project_id = fields.Many2one('project.project', string="Related Project")
|
project_id = fields.Many2one('project.project', string="İlgili Proje")
|
||||||
distance = fields.Float(string="Travel Distance (KM)", default=0.0)
|
distance = fields.Float(string="Seyahat Mesafesi (KM)", default=0.0)
|
||||||
estimated_cost = fields.Float(string="Estimated Travel Cost (TRY)", default=0.0)
|
estimated_cost = fields.Float(string="Tahmini Seyahat Maliyeti (TRY)", default=0.0)
|
||||||
description = fields.Text(string="Description / Meeting Notes", required=True)
|
description = fields.Text(string="Açıklama / Toplantı Notları", required=True)
|
||||||
|
|
||||||
def action_confirm(self):
|
def action_confirm(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
|
|||||||
@@ -12,27 +12,27 @@ class MymachFleetSecurityLog(models.Model):
|
|||||||
_description = 'Fleet Security and Route Logs'
|
_description = 'Fleet Security and Route Logs'
|
||||||
_order = 'datetime desc'
|
_order = 'datetime desc'
|
||||||
|
|
||||||
name = fields.Char(string="Log Reference", required=True, default="Autonomous System Log")
|
name = fields.Char(string="Log Referansı", required=True, default="Otonom Sistem Logu")
|
||||||
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True)
|
vehicle_id = fields.Many2one('fleet.vehicle', string="Araç", required=True)
|
||||||
driver_id = fields.Many2one('res.partner', string="Driver", related='vehicle_id.driver_id', store=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)
|
datetime = fields.Datetime(string="Tarih / Saat", default=fields.Datetime.now)
|
||||||
latitude = fields.Float(string="Latitude", digits=(10, 7))
|
latitude = fields.Float(string="Enlem", digits=(10, 7))
|
||||||
longitude = fields.Float(string="Longitude", digits=(10, 7))
|
longitude = fields.Float(string="Boylam", digits=(10, 7))
|
||||||
|
|
||||||
status = fields.Selection([
|
status = fields.Selection([
|
||||||
('in_office_moving', 'WARNING: Employee in Office but Vehicle Moving'),
|
('in_office_moving', 'WARNING: Employee in Office but Vehicle Moving'),
|
||||||
('project_match', 'CONFIRMED: Project Route Match'),
|
('project_match', 'CONFIRMED: Project Route Match'),
|
||||||
('exception', 'ATTENTION: Surprise Route Exception')
|
('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)
|
is_resolved = fields.Boolean(string="Çözümlendi mi?", default=False)
|
||||||
deduction_amount = fields.Monetary(string="Payroll Deduction Amount", currency_field='currency_id')
|
deduction_amount = fields.Monetary(string="Maaş Kesinti Tutarı", currency_field='currency_id')
|
||||||
currency_id = fields.Many2one('res.currency', string="Currency", related='vehicle_id.currency_id')
|
currency_id = fields.Many2one('res.currency', string="Para Birimi", related='vehicle_id.currency_id')
|
||||||
|
|
||||||
# Yolculuk mesafesi ve B noktasındaki kontak tespiti
|
# Yolculuk mesafesi ve B noktasındaki kontak tespiti
|
||||||
distance = fields.Float(string="Travel Distance (KM)", default=0.0)
|
distance = fields.Float(string="Seyahat Mesafesi (KM)", default=0.0)
|
||||||
partner_id = fields.Many2one('res.partner', string="Visited Contact")
|
partner_id = fields.Many2one('res.partner', string="Ziyaret Edilen İletişim Kişisi")
|
||||||
|
|
||||||
def _calculate_haversine_distance(self, lat1, lon1, lat2, lon2):
|
def _calculate_haversine_distance(self, lat1, lon1, lat2, lon2):
|
||||||
""" Calculates straight-line distance in km """
|
""" 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');
|
var tableBody = document.getElementById('vehicleTableBody');
|
||||||
if (tableBody) {
|
if (tableBody) {
|
||||||
tableBody.innerHTML = '';
|
tableBody.innerHTML = '';
|
||||||
|
var movingCount = 0;
|
||||||
|
var parkedCount = 0;
|
||||||
|
var offlineCount = 0;
|
||||||
|
|
||||||
vehiclesData.forEach(function(v, idx) {
|
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');
|
var tr = document.createElement('tr');
|
||||||
tr.style.cursor = 'pointer';
|
tr.style.cursor = 'pointer';
|
||||||
tr.style.transition = 'all 0.2s ease';
|
tr.style.transition = 'all 0.2s ease';
|
||||||
|
tr.style.borderBottom = '1px solid rgba(255,255,255,0.05)';
|
||||||
|
|
||||||
var isRev = v.is_violation;
|
var isRev = v.is_violation;
|
||||||
var badgeStyle = isRev
|
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);
|
var statusLabel = isRev ? (v.badge_violation) : (v.is_manager_approved ? v.badge_approved : v.badge_ok);
|
||||||
|
|
||||||
tr.innerHTML =
|
tr.innerHTML =
|
||||||
'<td>' +
|
'<td style="padding: 10px;">' +
|
||||||
'<strong style="color: white;">' + (v.driver_tag_str || '') + ' - ' + v.driver_name + '</strong><br/>' +
|
'<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>' +
|
'<span style="color: #94a3b8; font-size: 11px;">' + v.license_plate + '</span>' +
|
||||||
'</td>' +
|
'</td>' +
|
||||||
'<td>' +
|
'<td style="padding: 10px;">' +
|
||||||
'<span style="font-size: 11px; font-weight: 600; color: #38bdf8;">' + v.destination + '</span><br/>' +
|
'<span style="font-size: 13px; font-weight: 700; color: #38bdf8;">' + speedStr + '</span>' +
|
||||||
'<span style="font-size: 10px; color: #94a3b8;">' + v.driven_km + ' KM</span>' +
|
|
||||||
'</td>' +
|
'</td>' +
|
||||||
'<td>' +
|
'<td style="padding: 10px;">' +
|
||||||
'<span style="font-weight: bold; color: ' + (v.score >= 80 ? '#10b981' : (v.score >= 70 ? '#f59e0b' : '#ef4444')) + '">' + v.score + '</span>' +
|
'<span style="' + badgeStyle + ' padding: 4px 8px; border-radius: 12px; font-size: 10px; font-weight: bold; display: inline-block;">' +
|
||||||
'</td>' +
|
|
||||||
'<td>' +
|
|
||||||
'<span style="' + badgeStyle + ' padding: 3px 6px; border-radius: 10px; font-size: 10px; font-weight: bold; display: inline-block;">' +
|
|
||||||
statusLabel +
|
statusLabel +
|
||||||
'</span>' +
|
'</span>' +
|
||||||
'</td>';
|
'</td>';
|
||||||
@@ -367,6 +386,16 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
setTimeout(function() { selectDriver(v); }, 300);
|
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');
|
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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- Ağaç (Liste) Görünümü -->
|
<!-- Tahsis Sözleşmesi List (Tree) Görünümü -->
|
||||||
<record id="view_mymach_fleet_contract_list" model="ir.ui.view">
|
<record id="view_mymach_fleet_contract_list_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.contract.list</field>
|
<field name="name">mymach.fleet.contract.list.v2</field>
|
||||||
<field name="model">mymach.fleet.contract</field>
|
<field name="model">mymach.fleet.contract</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list string="Tahsis Sözleşmeleri">
|
<list string="Tahsis Sözleşmeleri">
|
||||||
<field name="name"/>
|
<field name="name" string="Sözleşme Adı"/>
|
||||||
<field name="driver_id"/>
|
<field name="driver_id" string="Sürücü"/>
|
||||||
<field name="department_id"/>
|
<field name="department_id" string="Departman"/>
|
||||||
<field name="personal_use_policy"/>
|
<field name="personal_use_policy" string="Şahsi Kullanım İzni"/>
|
||||||
<field name="commute_allowed"/>
|
<field name="commute_allowed" string="İşe Gidiş Geliş İzni"/>
|
||||||
<field name="driver_score"/>
|
<field name="driver_score" widget="progressbar" string="Sürücü Puanı"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Form Görünümü -->
|
<!-- Tahsis Sözleşmesi Form Görünümü -->
|
||||||
<record id="view_mymach_fleet_contract_form" model="ir.ui.view">
|
<record id="view_mymach_fleet_contract_form_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.contract.form</field>
|
<field name="name">mymach.fleet.contract.form.v2</field>
|
||||||
<field name="model">mymach.fleet.contract</field>
|
<field name="model">mymach.fleet.contract</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Tahsis Sözleşmesi">
|
<form string="Tahsis Sözleşmesi">
|
||||||
@@ -29,21 +29,20 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group string="Personel Bilgileri">
|
<group string="PERSONEL BİLGİLERİ">
|
||||||
<field name="driver_id"/>
|
<field name="driver_id"/>
|
||||||
<field name="employee_id" invisible="1"/>
|
<field name="employee_id" invisible="1"/>
|
||||||
<field name="department_id"/>
|
<field name="department_id"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Politika ve İzinler">
|
<group string="POLİTİKA VE İZİNLER">
|
||||||
<field name="personal_use_policy"/>
|
<field name="personal_use_policy"/>
|
||||||
<!-- personal_use_policy 'allowed' veya 'limit' ise gösterilecek -->
|
<field name="max_km_limit" invisible="personal_use_policy not in ('limit', 'allowed')"/>
|
||||||
<field name="max_km_limit" invisible="personal_use_policy == 'forbidden'"/>
|
<field name="personal_km_used" invisible="personal_use_policy not in ('limit', 'allowed')"/>
|
||||||
<field name="personal_km_used" readonly="1" invisible="personal_use_policy == 'forbidden'"/>
|
|
||||||
<field name="commute_allowed"/>
|
<field name="commute_allowed"/>
|
||||||
<field name="driver_score"/>
|
<field name="driver_score" widget="gauge" style="width:100px; height:100px;"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<group string="Cihaz ve Güvenlik">
|
<group string="CİHAZ VE GÜVENLİK">
|
||||||
<group>
|
<group>
|
||||||
<field name="personal_device_gps_allowed"/>
|
<field name="personal_device_gps_allowed"/>
|
||||||
</group>
|
</group>
|
||||||
@@ -57,9 +56,9 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Arama Görünümü -->
|
<!-- Tahsis Sözleşmesi Arama (Search) Görünümü -->
|
||||||
<record id="view_mymach_fleet_contract_search" model="ir.ui.view">
|
<record id="view_mymach_fleet_contract_search_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.contract.search</field>
|
<field name="name">mymach.fleet.contract.search.v2</field>
|
||||||
<field name="model">mymach.fleet.contract</field>
|
<field name="model">mymach.fleet.contract</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<search>
|
<search>
|
||||||
@@ -73,10 +72,15 @@
|
|||||||
|
|
||||||
<!-- Menü Aksiyonu -->
|
<!-- Menü Aksiyonu -->
|
||||||
<record id="action_mymach_fleet_contract" model="ir.actions.act_window">
|
<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="res_model">mymach.fleet.contract</field>
|
||||||
<field name="view_mode">list,form</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>
|
</record>
|
||||||
|
|
||||||
<!-- Ana Menü ve Alt Menüler -->
|
<!-- Ana Menü ve Alt Menüler -->
|
||||||
|
|||||||
@@ -157,30 +157,99 @@
|
|||||||
50% { opacity: 1.0; }
|
50% { opacity: 1.0; }
|
||||||
100% { transform: scale(1.2, 1.2); opacity: 0.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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Sol Arayüz Paneli -->
|
<!-- Sol Arayüz Paneli -->
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<div class="card">
|
<div class="card" style="display: flex; justify-content: space-between; align-items: center; padding: 15px 20px;">
|
||||||
<div class="header-title">
|
<div>
|
||||||
<i class="fa-solid fa-users-viewfinder"></i> SÜRÜCÜ LİSTESİ
|
<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>
|
</div>
|
||||||
<p style="font-size: 13px; color: #94a3b8; margin-top: 5px;">
|
|
||||||
Zero-Trust Canlı Rota Takip ve Otonom Sapma Denetim Paneli
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Canlı Sürücü ve Araç Listesi Tablosu -->
|
<!-- 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 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>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Sürücü / Araç</th>
|
<th>Sürücü / Araç</th>
|
||||||
<th>Hedef</th>
|
<th>Hız</th>
|
||||||
<th>Skor</th>
|
|
||||||
<th>Durum</th>
|
<th>Durum</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -190,12 +259,6 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Sağ Canlı Harita Paneli -->
|
<!-- Sağ Canlı Harita Paneli -->
|
||||||
|
|||||||
@@ -209,9 +209,15 @@
|
|||||||
|
|
||||||
function errorPosition(err) {
|
function errorPosition(err) {
|
||||||
console.warn(`ERROR(${err.code}): ${err.message}`);
|
console.warn(`ERROR(${err.code}): ${err.message}`);
|
||||||
statusText.textContent = "Hata: Konum alınamadı!";
|
if (err.code === 1) { // PERMISSION_DENIED
|
||||||
statusText.style.color = "#ef4444";
|
statusText.textContent = "HATA: Konum izni reddedildi! Lütfen tarayıcı ayarlarından konum izni verin.";
|
||||||
stopTracking();
|
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() {
|
function startTracking() {
|
||||||
@@ -231,7 +237,7 @@
|
|||||||
// watchPosition keeps getting location updates
|
// watchPosition keeps getting location updates
|
||||||
watchId = navigator.geolocation.watchPosition(successPosition, errorPosition, {
|
watchId = navigator.geolocation.watchPosition(successPosition, errorPosition, {
|
||||||
enableHighAccuracy: true,
|
enableHighAccuracy: true,
|
||||||
timeout: 10000,
|
timeout: 30000,
|
||||||
maximumAge: 0
|
maximumAge: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +1,53 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- Gider/Ceza List (Tree) Görünümü -->
|
<!-- Gider/Ceza List (Tree) Görünümü -->
|
||||||
<record id="view_mymach_fleet_fine_list" model="ir.ui.view">
|
<record id="view_mymach_fleet_fine_list_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.fine.list</field>
|
<field name="name">mymach.fleet.fine.list.v2</field>
|
||||||
<field name="model">mymach.fleet.fine</field>
|
<field name="model">mymach.fleet.fine</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<list string="Giderler ve Cezalar">
|
<list string="Cezalar ve HGS Geçişleri" decoration-danger="is_personal_violation and state != 'deducted'" decoration-success="state == 'deducted'">
|
||||||
<field name="name"/>
|
<field name="date" string="İşlem Tarihi"/>
|
||||||
<field name="charge_type"/>
|
<field name="name" string="Kayıt Adı"/>
|
||||||
<field name="other_charge_type_name" invisible="charge_type != 'other'" required="charge_type == 'other'"/>
|
<field name="charge_type" string="Gider Tipi"/>
|
||||||
<field name="vehicle_id"/>
|
<field name="vehicle_id" string="Araç"/>
|
||||||
<field name="driver_id"/>
|
<field name="driver_id" string="Sürücü"/>
|
||||||
<field name="amount"/>
|
<field name="amount" widget="monetary" string="Tutar"/>
|
||||||
<field name="date"/>
|
<field name="currency_id" column_invisible="True"/>
|
||||||
<field name="is_personal_violation" widget="boolean_toggle"/>
|
<field name="is_personal_violation" widget="boolean_toggle" string="Şahsi İhlal mi?"/>
|
||||||
<field name="state" widget="badge" decoration-success="state == 'deducted'" decoration-info="state == 'confirmed'" decoration-muted="state == 'draft'"/>
|
<field name="state" widget="badge" string="Durum" decoration-info="state == 'draft'" decoration-warning="state == 'confirmed'" decoration-success="state == 'deducted'"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Gider/Ceza Form Görünümü -->
|
<!-- Gider/Ceza Form Görünümü -->
|
||||||
<record id="view_mymach_fleet_fine_form" model="ir.ui.view">
|
<record id="view_mymach_fleet_fine_form_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.fine.form</field>
|
<field name="name">mymach.fleet.fine.form.v2</field>
|
||||||
<field name="model">mymach.fleet.fine</field>
|
<field name="model">mymach.fleet.fine</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Gider ve Ceza Kaydı">
|
<form string="Ceza/Geçiş Kaydı">
|
||||||
<header>
|
<header>
|
||||||
<button name="action_confirm" string="Onayla" type="object" class="oe_highlight" invisible="state != 'draft'"/>
|
<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"/>
|
<field name="state" widget="statusbar" statusbar_visible="draft,confirmed,deducted"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
<div class="oe_title">
|
<div class="oe_title">
|
||||||
<h1>
|
<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>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group string="Detaylar">
|
<group string="DETAY">
|
||||||
<field name="charge_type"/>
|
<field name="charge_type" readonly="state != 'draft'"/>
|
||||||
<field name="other_charge_type_name" invisible="charge_type != 'other'" required="charge_type == 'other'"/>
|
<field name="other_charge_type_name" invisible="charge_type != 'other'" readonly="state != 'draft'"/>
|
||||||
<field name="vehicle_id"/>
|
<field name="vehicle_id" readonly="state != 'draft'"/>
|
||||||
<field name="driver_id"/>
|
<field name="driver_id" readonly="1"/>
|
||||||
</group>
|
</group>
|
||||||
<group string="Finansal Bilgiler">
|
<group string="FİNANSAL BİLGİLER">
|
||||||
<field name="amount"/>
|
<field name="amount" readonly="state != 'draft'"/>
|
||||||
<field name="currency_id" invisible="1"/>
|
<field name="currency_id" invisible="1"/>
|
||||||
<field name="date"/>
|
<field name="date" readonly="state != 'draft'"/>
|
||||||
<field name="is_personal_violation"/>
|
<field name="is_personal_violation" readonly="1"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</sheet>
|
</sheet>
|
||||||
@@ -57,32 +57,37 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Gider/Ceza Arama Görünümü -->
|
<!-- Gider/Ceza Arama Görünümü -->
|
||||||
<record id="view_mymach_fleet_fine_search" model="ir.ui.view">
|
<record id="view_mymach_fleet_fine_search_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.fine.search</field>
|
<field name="name">mymach.fleet.fine.search.v2</field>
|
||||||
<field name="model">mymach.fleet.fine</field>
|
<field name="model">mymach.fleet.fine</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<search>
|
<search>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="vehicle_id"/>
|
<field name="vehicle_id"/>
|
||||||
<field name="driver_id"/>
|
<field name="driver_id"/>
|
||||||
<filter string="Şahsi İhlaller" name="personal_violation" domain="[('is_personal_violation', '=', True)]"/>
|
<filter string="Taslak" name="draft" domain="[('state','=','draft')]"/>
|
||||||
<filter string="Gider Türü" name="group_by_type" context="{'group_by': 'charge_type'}"/>
|
<filter string="Onaylanan" name="confirmed" domain="[('state','=','confirmed')]"/>
|
||||||
<filter string="Durum" name="group_by_state" context="{'group_by': 'state'}"/>
|
<filter string="Şahsi İhlaller" name="personal" domain="[('is_personal_violation','=',True)]"/>
|
||||||
</search>
|
</search>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Aksiyon -->
|
<!-- Aksiyon -->
|
||||||
<record id="action_mymach_fleet_fine" model="ir.actions.act_window">
|
<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="res_model">mymach.fleet.fine</field>
|
||||||
<field name="view_mode">list,form</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>
|
</record>
|
||||||
|
|
||||||
<!-- Menü Elemanı -->
|
<!-- Menü Elemanı -->
|
||||||
<menuitem id="menu_mymach_fleet_fine"
|
<menuitem id="menu_mymach_fleet_fine"
|
||||||
name="Giderler & Cezalar"
|
name="Cezalar ve HGS"
|
||||||
parent="menu_mymach_fleet_intelligence_root"
|
parent="menu_mymach_fleet_intelligence_root"
|
||||||
action="action_mymach_fleet_fine"
|
action="action_mymach_fleet_fine"
|
||||||
sequence="3"/>
|
sequence="3"/>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
</group>
|
</group>
|
||||||
<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="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="try_currency_id" invisible="1"/>
|
||||||
<field name="active_display_currency_id" invisible="1"/>
|
<field name="active_display_currency_id" invisible="1"/>
|
||||||
</group>
|
</group>
|
||||||
@@ -156,6 +156,26 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</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"
|
<menuitem id="menu_mymach_fleet_vehicle"
|
||||||
name="Araçlar"
|
name="Araçlar"
|
||||||
parent="menu_mymach_fleet_intelligence_root"
|
parent="menu_mymach_fleet_intelligence_root"
|
||||||
|
|||||||
@@ -23,6 +23,13 @@
|
|||||||
<field name="domain">[('is_resolved', '=', False)]</field>
|
<field name="domain">[('is_resolved', '=', False)]</field>
|
||||||
</record>
|
</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 -->
|
<!-- Sunucu Aksiyonu: Sadece Canlı Haritayı Gösteren Görünüm İçin -->
|
||||||
<record id="action_mymach_fleet_home_chart_server" model="ir.actions.server">
|
<record id="action_mymach_fleet_home_chart_server" model="ir.actions.server">
|
||||||
<field name="name">Canlı Harita Analizi</field>
|
<field name="name">Canlı Harita Analizi</field>
|
||||||
@@ -46,8 +53,8 @@ action = {
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="view_mymach_fleet_home_form" model="ir.ui.view">
|
<record id="view_mymach_fleet_home_form_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.home.form</field>
|
<field name="name">mymach.fleet.home.form.v2</field>
|
||||||
<field name="model">mymach.fleet.home</field>
|
<field name="model">mymach.fleet.home</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="MyMach Filo Zekası" class="mymach_zara_form" create="false" delete="false">
|
<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) -->
|
<!-- Dil Seçici Kaldırıldı (Odoo Standardı Kullanılacak) -->
|
||||||
<!-- Zara-Style Minimalist Editorial Header -->
|
<!-- Zara-Style Minimalist Editorial Header -->
|
||||||
<div style="text-align: center; margin-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.08); padding-bottom: 25px;">
|
<div style="text-align: center; margin-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.08); padding-bottom: 25px;">
|
||||||
<h1 class="mymach_zara_title" style="color: #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);"/>
|
<field name="display_title" readonly="1" class="mymach_zara_subtitle" style="border: none; background: transparent; color: #cbd5e1 !important; text-align: center; display: block; width: 100%; pointer-events: none; text-shadow: 0 1px 5px rgba(0,0,0,0.5);"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -123,6 +130,11 @@ action = {
|
|||||||
<i class="fa fa-bell-o mb-2"></i><br/> Bekleyen Rota Uyarılarım
|
<i class="fa fa-bell-o mb-2"></i><br/> Bekleyen Rota Uyarılarım
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -204,7 +216,7 @@ action = {
|
|||||||
'res_model': 'mymach.fleet.home',
|
'res_model': 'mymach.fleet.home',
|
||||||
'res_id': record.id,
|
'res_id': record.id,
|
||||||
'view_mode': 'form',
|
'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',
|
'target': 'current',
|
||||||
'context': {'clear_breadcrumbs': True},
|
'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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<!-- Periyodik Bakım Hatırlatıcı Liste (Ağaç) Görünümü -->
|
<!-- 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">
|
<record id="view_mymach_fleet_periodic_maintenance_tree_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.periodic.maintenance.tree</field>
|
<field name="name">mymach.fleet.periodic.maintenance.tree.v2</field>
|
||||||
<field name="model">mymach.fleet.periodic.maintenance</field>
|
<field name="model">mymach.fleet.periodic.maintenance</field>
|
||||||
<field name="arch" type="xml">
|
<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'">
|
<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>
|
</record>
|
||||||
|
|
||||||
<!-- Periyodik Bakım Hatırlatıcı Form Görünümü -->
|
<!-- Periyodik Bakım Hatırlatıcı Form Görünümü -->
|
||||||
<record id="view_mymach_fleet_periodic_maintenance_form" model="ir.ui.view">
|
<record id="view_mymach_fleet_periodic_maintenance_form_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.periodic.maintenance.form</field>
|
<field name="name">mymach.fleet.periodic.maintenance.form.v2</field>
|
||||||
<field name="model">mymach.fleet.periodic.maintenance</field>
|
<field name="model">mymach.fleet.periodic.maintenance</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Periyodik Bakım Hatırlatıcısı">
|
<form string="Periyodik Bakım Hatırlatıcısı">
|
||||||
@@ -60,8 +60,8 @@
|
|||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- Periyodik Bakım Arama (Search) Görünümü -->
|
<!-- Periyodik Bakım Arama (Search) Görünümü -->
|
||||||
<record id="view_mymach_fleet_periodic_maintenance_search" model="ir.ui.view">
|
<record id="view_mymach_fleet_periodic_maintenance_search_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.periodic.maintenance.search</field>
|
<field name="name">mymach.fleet.periodic.maintenance.search.v2</field>
|
||||||
<field name="model">mymach.fleet.periodic.maintenance</field>
|
<field name="model">mymach.fleet.periodic.maintenance</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<search string="Bakım Arama">
|
<search string="Bakım Arama">
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
<field name="name">Periyodik Bakım Takibi</field>
|
<field name="name">Periyodik Bakım Takibi</field>
|
||||||
<field name="res_model">mymach.fleet.periodic.maintenance</field>
|
<field name="res_model">mymach.fleet.periodic.maintenance</field>
|
||||||
<field name="view_mode">list,form</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="context">{'search_default_overdue': 1, 'search_default_pending': 1}</field>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="view_mymach_fleet_security_log_list" model="ir.ui.view">
|
<record id="view_mymach_fleet_security_log_list_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.security.log.list</field>
|
<field name="name">mymach.fleet.security.log.list.v2</field>
|
||||||
<field name="model">mymach.fleet.security.log</field>
|
<field name="model">mymach.fleet.security.log</field>
|
||||||
<field name="arch" type="xml">
|
<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'">
|
<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="datetime" string="Tarih / Saat"/>
|
||||||
<field name="name"/>
|
<field name="name" string="Kayıt Adı"/>
|
||||||
<field name="vehicle_id"/>
|
<field name="vehicle_id" string="Araç"/>
|
||||||
<field name="driver_id"/>
|
<field name="driver_id" string="Sürücü"/>
|
||||||
<field name="status"/>
|
<field name="status" string="Durum"/>
|
||||||
<field name="is_resolved"/>
|
<field name="is_resolved" string="Çözümlendi mi?"/>
|
||||||
<field name="deduction_amount" widget="monetary" invisible="deduction_amount == 0"/>
|
<field name="deduction_amount" widget="monetary" invisible="deduction_amount == 0" string="Kesinti Tutarı"/>
|
||||||
<field name="currency_id" column_invisible="True"/>
|
<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'"/>
|
<button name="action_open_exception_wizard" type="object" string="Sürpriz Rotayı Çözümle" class="btn-primary" invisible="is_resolved or status != 'exception'"/>
|
||||||
</list>
|
</list>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="view_mymach_fleet_security_log_form" model="ir.ui.view">
|
<record id="view_mymach_fleet_security_log_form_v2" model="ir.ui.view">
|
||||||
<field name="name">mymach.fleet.security.log.form</field>
|
<field name="name">mymach.fleet.security.log.form.v2</field>
|
||||||
<field name="model">mymach.fleet.security.log</field>
|
<field name="model">mymach.fleet.security.log</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Güvenlik Logu">
|
<form string="Güvenlik Logu">
|
||||||
|
|||||||
BIN
error_logs.txt
Normal file
BIN
error_logs.txt
Normal file
Binary file not shown.
0
raw_logs.txt
Normal file
0
raw_logs.txt
Normal file
0
recent_logs.txt
Normal file
0
recent_logs.txt
Normal file
152
scratch_base_data.xml
Normal file
152
scratch_base_data.xml
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record model="ir.module.category" id="module_category_hidden">
|
||||||
|
<field name="name">Technical</field>
|
||||||
|
<field name="sequence">70</field>
|
||||||
|
<field name="visible" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_accounting">
|
||||||
|
<field name="name">Accounting</field>
|
||||||
|
<field name="sequence">20</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_accounting_localizations">
|
||||||
|
<field name="name">Localization</field>
|
||||||
|
<field name="sequence">65</field>
|
||||||
|
<field name="visible" eval="0" />
|
||||||
|
<field name="parent_id" ref="module_category_accounting"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_payroll_localization">
|
||||||
|
<field name="name">Payroll Localization</field>
|
||||||
|
<field name="visible" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_accounting_localizations_account_charts">
|
||||||
|
<field name="parent_id" ref="module_category_accounting_localizations" />
|
||||||
|
<field name="name">Account Charts</field>
|
||||||
|
<field name="visible" eval="0" />
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_accounting_accounting">
|
||||||
|
<field name="name">Invoicing</field>
|
||||||
|
<field name="sequence">4</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_sales">
|
||||||
|
<field name="name">Sales</field>
|
||||||
|
<field name="sequence">5</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_human_resources">
|
||||||
|
<field name="name">Human Resources</field>
|
||||||
|
<field name="sequence">45</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_marketing">
|
||||||
|
<field name="name">Marketing</field>
|
||||||
|
<field name="sequence">40</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_supply_chain">
|
||||||
|
<field name="name">Supply Chain</field>
|
||||||
|
<field name="sequence">25</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_shipping_connectors">
|
||||||
|
<field name="name">Shipping Connectors</field>
|
||||||
|
<field name="sequence">50</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_website">
|
||||||
|
<field name="name">Website</field>
|
||||||
|
<field name="sequence">10</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_theme">
|
||||||
|
<field name="name">Theme</field>
|
||||||
|
<field name="exclusive" eval="0"/>
|
||||||
|
<field name="sequence">50</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_administration">
|
||||||
|
<field name="name">Administration</field>
|
||||||
|
<field name="sequence">100</field>
|
||||||
|
<field name="parent_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="base.module_category_human_resources_referrals">
|
||||||
|
<field name="name">Referrals</field>
|
||||||
|
<field name="description">Helps you manage referrals
|
||||||
|
User : Access to referral, share job, gain points, buy rewards
|
||||||
|
Administrator : edit rewards and more</field>
|
||||||
|
<field name="sequence">11</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_human_resources_appraisals">
|
||||||
|
<field name="name">Appraisals</field>
|
||||||
|
<field name="description">A user without any rights on Appraisals will be able to see the application, create and manage appraisals for himself and the people he's manager of.</field>
|
||||||
|
<field name="sequence">15</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_sales_sign">
|
||||||
|
<field name="name">Sign</field>
|
||||||
|
<field name="description">Helps you sign and complete your documents easily.</field>
|
||||||
|
<field name="sequence">25</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_services">
|
||||||
|
<field name="name">Services</field>
|
||||||
|
<field name="sequence">15</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_services_helpdesk">
|
||||||
|
<field name="name">Helpdesk</field>
|
||||||
|
<field name="description" />
|
||||||
|
<field name="sequence">14</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_services_appointment">
|
||||||
|
<field name="name">Appointment</field>
|
||||||
|
<field name="parent_id" ref="module_category_services"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_services_field_service">
|
||||||
|
<field name="name">Field Service</field>
|
||||||
|
<field name="parent_id" ref="module_category_services"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_productivity">
|
||||||
|
<field name="name">Productivity</field>
|
||||||
|
<field name="sequence">30</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_esg">
|
||||||
|
<field name="name">ESG</field>
|
||||||
|
<field name="sequence">52</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_customizations">
|
||||||
|
<field name="name">Customizations</field>
|
||||||
|
<field name="sequence">55</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_internet_of_things_(iot)">
|
||||||
|
<field name="name">Internet of Things (IoT)</field>
|
||||||
|
<field name="sequence">60</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_administration_administration">
|
||||||
|
<field name="name">Administration</field>
|
||||||
|
<field name="parent_id" ref="module_category_administration"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.module.category" id="module_category_extra">
|
||||||
|
<field name="name">Other Extra Rights</field>
|
||||||
|
<field name="sequence">102</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
18
scratch_fix_db.py
Normal file
18
scratch_fix_db.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import urllib.request
|
||||||
|
import json
|
||||||
|
|
||||||
|
url = "https://odoodev2.mymachconnect.com/mymach/api/ping"
|
||||||
|
data = json.dumps({}).encode('utf-8')
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Odoo-Database': 'mymach-odoodev2-2026'
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, data=data, headers=headers, method='POST')
|
||||||
|
response = urllib.request.urlopen(req)
|
||||||
|
print("Response:", response.read().decode())
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
print("Error HTTP:", e.code, e.read().decode())
|
||||||
|
except Exception as e:
|
||||||
|
print("Error:", e)
|
||||||
14
scratch_privilege.py
Normal file
14
scratch_privilege.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ResGroupsPrivilege(models.Model):
|
||||||
|
_name = 'res.groups.privilege'
|
||||||
|
_description = "Privileges"
|
||||||
|
_order = 'sequence, name, id'
|
||||||
|
|
||||||
|
name = fields.Char(string='Name', required=True, translate=True)
|
||||||
|
description = fields.Text(string='Description')
|
||||||
|
placeholder = fields.Char(string='Placeholder', default="No", help="Text that is displayed as placeholder in the selection field of the user form.")
|
||||||
|
sequence = fields.Integer(string='Sequence', default=100)
|
||||||
|
category_id = fields.Many2one('ir.module.category', string='Category', index=True)
|
||||||
|
group_ids = fields.One2many('res.groups', 'privilege_id', string='Groups')
|
||||||
397
scratch_res_groups.py
Normal file
397
scratch_res_groups.py
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, fields, models, tools
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
from odoo.fields import Command, Domain
|
||||||
|
from odoo.tools import SetDefinitions
|
||||||
|
|
||||||
|
|
||||||
|
class ResGroups(models.Model):
|
||||||
|
_name = 'res.groups'
|
||||||
|
_description = "Access Groups"
|
||||||
|
_rec_name = 'full_name'
|
||||||
|
_allow_sudo_commands = False
|
||||||
|
_order = 'privilege_id, sequence, name, id'
|
||||||
|
|
||||||
|
name = fields.Char(required=True, translate=True)
|
||||||
|
user_ids = fields.Many2many('res.users', 'res_groups_users_rel', 'gid', 'uid', help='Users explicitly in this group')
|
||||||
|
all_user_ids = fields.Many2many('res.users', string='Users and implied users',
|
||||||
|
compute='_compute_all_user_ids', search='_search_all_user_ids', inverse='_inverse_all_user_ids')
|
||||||
|
|
||||||
|
all_users_count = fields.Integer('# Users', help='Number of users having this group (implicitly or explicitly)',
|
||||||
|
compute='_compute_all_users_count', compute_sudo=True)
|
||||||
|
|
||||||
|
model_access = fields.One2many('ir.model.access', 'group_id', string='Access Controls', copy=True)
|
||||||
|
rule_groups = fields.Many2many('ir.rule', 'rule_group_rel',
|
||||||
|
'group_id', 'rule_group_id', string='Rules', domain="[('global', '=', False)]")
|
||||||
|
menu_access = fields.Many2many('ir.ui.menu', 'ir_ui_menu_group_rel', 'gid', 'menu_id', string='Access Menu')
|
||||||
|
view_access = fields.Many2many('ir.ui.view', 'ir_ui_view_group_rel', 'group_id', 'view_id', string='Views')
|
||||||
|
comment = fields.Text(translate=True)
|
||||||
|
full_name = fields.Char(compute='_compute_full_name', string='Group Name', search='_search_full_name')
|
||||||
|
share = fields.Boolean(string='Share Group', help="Group created to set access rights for sharing data with some users.")
|
||||||
|
api_key_duration = fields.Float(string='API Keys maximum duration days',
|
||||||
|
help="Determines the maximum duration of an api key created by a user belonging to this group.")
|
||||||
|
|
||||||
|
sequence = fields.Integer(string='Sequence')
|
||||||
|
privilege_id = fields.Many2one('res.groups.privilege', string='Privilege', index=True)
|
||||||
|
view_group_hierarchy = fields.Json(string='Technical field for default group setting', compute='_compute_view_group_hierarchy')
|
||||||
|
|
||||||
|
_name_uniq = models.Constraint("UNIQUE (privilege_id, name)",
|
||||||
|
'The name of the group must be unique within a group privilege!')
|
||||||
|
_check_api_key_duration = models.Constraint(
|
||||||
|
'CHECK(api_key_duration >= 0)',
|
||||||
|
'The api key duration cannot be a negative value.',
|
||||||
|
)
|
||||||
|
|
||||||
|
""" The groups involved are to be interpreted as sets.
|
||||||
|
Thus we can define groups that we will call for example N, Z... such as mathematical sets.
|
||||||
|
┌──────────────────────────────────────────┐
|
||||||
|
│ C ┌──────────────────────────┐ │
|
||||||
|
│ │ R ┌───────────────────┐ │ ┌──────┐ | "C"
|
||||||
|
│ │ │ Q ┌────────────┐ │ │ │ I | | "I" implied "C"
|
||||||
|
│ │ │ │ Z ┌─────┐ │ │ │ │ | | "R" implied "C"
|
||||||
|
│ │ │ │ │ N │ │ │ │ │ │ │ "Q" implied "R"
|
||||||
|
│ │ │ │ └─────┘ │ │ │ │ │ │ "P" implied "R"
|
||||||
|
│ │ │ └────────────┘ │ │ │ │ │ "Z" implied "Q"
|
||||||
|
│ │ └───────────────────┘ │ │ │ │ "N" implied "Z"
|
||||||
|
│ │ ┌───────────────┐ │ │ │ │
|
||||||
|
│ │ │ P │ │ │ │ │
|
||||||
|
│ │ └───────────────┘ │ └──────┘ │
|
||||||
|
│ └──────────────────────────┘ │
|
||||||
|
└──────────────────────────────────────────┘
|
||||||
|
For example:
|
||||||
|
* A manager group will imply a user group: all managers are users (like Z imply C);
|
||||||
|
* A group "computer developer employee" will imply that he is an employee group, a user
|
||||||
|
group, that he has access to the timesheet user group.... "computer developer employee"
|
||||||
|
is therefore a set of users in the intersection of these groups. These users will
|
||||||
|
therefore have all the rights of these groups in addition to their own access rights.
|
||||||
|
"""
|
||||||
|
implied_ids = fields.Many2many('res.groups', 'res_groups_implied_rel', 'gid', 'hid',
|
||||||
|
string='Implied Groups', help='Users of this group are also implicitly part of those groups')
|
||||||
|
all_implied_ids = fields.Many2many('res.groups', string='Transitively Implied Groups', recursive=True,
|
||||||
|
compute='_compute_all_implied_ids', compute_sudo=True, search='_search_all_implied_ids',
|
||||||
|
help="The group itself with all its implied groups.")
|
||||||
|
implied_by_ids = fields.Many2many('res.groups', 'res_groups_implied_rel', 'hid', 'gid',
|
||||||
|
string='Implying Groups', help="Users in those groups are implicitly part of this group.")
|
||||||
|
all_implied_by_ids = fields.Many2many('res.groups', string='Transitively Implying Groups', recursive=True,
|
||||||
|
compute='_compute_all_implied_by_ids', compute_sudo=True, search='_search_all_implied_by_ids')
|
||||||
|
disjoint_ids = fields.Many2many('res.groups', string='Disjoint Groups',
|
||||||
|
help="A user may not belong to this group and one of those. For instance, users may not be portal users and internal users.",
|
||||||
|
compute='_compute_disjoint_ids')
|
||||||
|
|
||||||
|
@api.constrains('implied_ids', 'implied_by_ids')
|
||||||
|
def _check_disjoint_groups(self):
|
||||||
|
# check for users that might have two exclusive groups
|
||||||
|
self.env.registry.clear_cache('groups')
|
||||||
|
self.all_implied_by_ids._check_user_disjoint_groups()
|
||||||
|
|
||||||
|
@api.constrains('view_access')
|
||||||
|
def _check_inherited_view_groups(self):
|
||||||
|
self.view_access._check_groups()
|
||||||
|
|
||||||
|
@api.constrains('user_ids')
|
||||||
|
def _check_user_disjoint_groups(self):
|
||||||
|
# Here we should check all the users in any group of 'self':
|
||||||
|
#
|
||||||
|
# self.user_ids._check_disjoint_groups()
|
||||||
|
#
|
||||||
|
# But that wouldn't scale at all for large groups, like more than 10K
|
||||||
|
# users. So instead we search for such a nasty user.
|
||||||
|
gids = self._get_user_type_groups().ids
|
||||||
|
domain = (
|
||||||
|
Domain('active', '=', True)
|
||||||
|
& Domain('group_ids', 'in', self.ids)
|
||||||
|
& Domain.OR(
|
||||||
|
Domain('all_group_ids', 'in', [gids[index]])
|
||||||
|
& Domain('all_group_ids', 'in', gids[index+1:])
|
||||||
|
for index in range(0, len(gids) - 1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
user = self.env['res.users'].search(domain, order='id', limit=1)
|
||||||
|
if user:
|
||||||
|
user._check_disjoint_groups() # raises a ValidationError
|
||||||
|
|
||||||
|
@api.ondelete(at_uninstall=False)
|
||||||
|
def _unlink_except_settings_group(self):
|
||||||
|
classified = self.env['res.config.settings']._get_classified_fields()
|
||||||
|
for _name, _groups, implied_group in classified['group']:
|
||||||
|
if implied_group.id in self.ids:
|
||||||
|
raise ValidationError(self.env._('You cannot delete a group linked with a settings field.'))
|
||||||
|
|
||||||
|
@api.depends('privilege_id.name', 'name')
|
||||||
|
@api.depends_context('short_display_name')
|
||||||
|
def _compute_full_name(self):
|
||||||
|
# Important: value must be stored in environment of group, not group1!
|
||||||
|
for group, group1 in zip(self, self.sudo()):
|
||||||
|
if group1.privilege_id and not self.env.context.get('short_display_name'):
|
||||||
|
group.full_name = '%s / %s' % (group1.privilege_id.name, group1.name)
|
||||||
|
else:
|
||||||
|
group.full_name = group1.name
|
||||||
|
|
||||||
|
def _search_full_name(self, operator, operand):
|
||||||
|
if operator in Domain.NEGATIVE_OPERATORS:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
if isinstance(operand, str):
|
||||||
|
def make_operand(val): return val
|
||||||
|
operands = [operand]
|
||||||
|
else:
|
||||||
|
def make_operand(val): return [val]
|
||||||
|
operands = operand
|
||||||
|
|
||||||
|
where_domains = [Domain('name', operator, operand)]
|
||||||
|
for group in operands:
|
||||||
|
if not group:
|
||||||
|
continue
|
||||||
|
domain = Domain('name', operator, make_operand(group))
|
||||||
|
where_domains.append(domain)
|
||||||
|
|
||||||
|
if '/' in group:
|
||||||
|
privilege_name, _, group_name = group.partition('/')
|
||||||
|
group_name = group_name.strip()
|
||||||
|
privilege_name = privilege_name.strip()
|
||||||
|
else:
|
||||||
|
privilege_name = group
|
||||||
|
group_name = None
|
||||||
|
|
||||||
|
if privilege_name:
|
||||||
|
domain = Domain(
|
||||||
|
'privilege_id', 'any!', Domain('name', operator, make_operand(privilege_name)),
|
||||||
|
)
|
||||||
|
if group_name:
|
||||||
|
domain &= Domain('name', operator, make_operand(group_name))
|
||||||
|
where_domains.append(domain)
|
||||||
|
|
||||||
|
return Domain.OR(where_domains)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _search(self, domain, offset=0, limit=None, order=None, **kwargs):
|
||||||
|
# add explicit ordering if search is sorted on full_name
|
||||||
|
if order and order.startswith('full_name'):
|
||||||
|
groups = super().search(domain)
|
||||||
|
groups = groups.sorted('full_name', reverse=order.endswith('DESC'))
|
||||||
|
groups = groups[offset:offset+limit] if limit else groups[offset:]
|
||||||
|
return groups._as_query(order)
|
||||||
|
return super()._search(domain, offset, limit, order, **kwargs)
|
||||||
|
|
||||||
|
def copy_data(self, default=None):
|
||||||
|
default = dict(default or {})
|
||||||
|
vals_list = super().copy_data(default=default)
|
||||||
|
for group, vals in zip(self, vals_list):
|
||||||
|
vals['name'] = default.get('name') or self.env._('%s (copy)', group.name)
|
||||||
|
return vals_list
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
if 'name' in vals:
|
||||||
|
if vals['name'].startswith('-'):
|
||||||
|
raise UserError(self.env._('The name of the group can not start with "-"'))
|
||||||
|
|
||||||
|
# invalidate caches before updating groups, since the recomputation of
|
||||||
|
# field 'share' depends on method has_group()
|
||||||
|
# DLE P139
|
||||||
|
if self.ids:
|
||||||
|
self.env['ir.model.access'].call_cache_clearing_methods()
|
||||||
|
|
||||||
|
res = super().write(vals)
|
||||||
|
|
||||||
|
if 'implied_ids' in vals or 'implied_by_ids' in vals:
|
||||||
|
# Invalidate the cache of groups and their relationships
|
||||||
|
self.env.registry.clear_cache('groups')
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _ensure_xml_id(self):
|
||||||
|
"""Return the groups external identifiers, creating the external identifier for groups missing one"""
|
||||||
|
result = self.get_external_id()
|
||||||
|
missings = {group_id: f'__custom__.group_{group_id}' for group_id, ext_id in result.items() if not ext_id}
|
||||||
|
if missings:
|
||||||
|
self.env['ir.model.data'].sudo().create(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'name': name.split('.')[1],
|
||||||
|
'model': 'res.groups',
|
||||||
|
'res_id': group_id,
|
||||||
|
'module': name.split('.')[0],
|
||||||
|
}
|
||||||
|
for group_id, name in missings.items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
result.update(missings)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@api.depends('all_implied_by_ids.user_ids')
|
||||||
|
def _compute_all_user_ids(self):
|
||||||
|
for group in self.with_context(active_test=False):
|
||||||
|
group.all_user_ids = group.all_implied_by_ids.user_ids
|
||||||
|
|
||||||
|
def _inverse_all_user_ids(self):
|
||||||
|
for group in self:
|
||||||
|
user_to_add = group.all_user_ids - group.all_implied_by_ids.user_ids
|
||||||
|
user_to_remove = group.all_implied_by_ids.user_ids - group.all_user_ids
|
||||||
|
group.user_ids = group.user_ids - user_to_remove + user_to_add
|
||||||
|
|
||||||
|
cannot_remove = group.all_implied_by_ids.user_ids & user_to_remove
|
||||||
|
if cannot_remove:
|
||||||
|
raise UserError(self.env._(
|
||||||
|
"It is not possible to remove implied group %(group)s from users %(users)s",
|
||||||
|
group=repr(group.name),
|
||||||
|
users=', '.join(cannot_remove.mapped('name')),
|
||||||
|
))
|
||||||
|
|
||||||
|
def _search_all_user_ids(self, operator, value):
|
||||||
|
return [('all_implied_by_ids.user_ids', operator, value)]
|
||||||
|
|
||||||
|
@api.depends('implied_ids.all_implied_ids')
|
||||||
|
def _compute_all_implied_ids(self):
|
||||||
|
""" Compute the reflexive transitive closure of implied_ids. """
|
||||||
|
group_definitions = self._get_group_definitions()
|
||||||
|
for g in self:
|
||||||
|
g.all_implied_ids = g.ids + group_definitions.get_superset_ids(g.ids)
|
||||||
|
|
||||||
|
def _search_all_implied_ids(self, operator, value):
|
||||||
|
""" Compute the search on the reflexive transitive closure of implied_ids. """
|
||||||
|
if operator not in ('in', 'not in'):
|
||||||
|
return NotImplemented
|
||||||
|
group_definitions = self._get_group_definitions()
|
||||||
|
ids = [*value, *group_definitions.get_subset_ids(value)]
|
||||||
|
return [('id', operator, ids)]
|
||||||
|
|
||||||
|
@api.depends('implied_by_ids.all_implied_by_ids')
|
||||||
|
def _compute_all_implied_by_ids(self):
|
||||||
|
""" Compute the reflexive transitive closure of implied_by_ids. """
|
||||||
|
group_definitions = self._get_group_definitions()
|
||||||
|
for g in self:
|
||||||
|
g.all_implied_by_ids = g.ids + group_definitions.get_subset_ids(g.ids)
|
||||||
|
|
||||||
|
def _search_all_implied_by_ids(self, operator, value):
|
||||||
|
""" Compute the search on the reflexive transitive closure of implied_by_ids. """
|
||||||
|
if operator in ("any", "not any") and isinstance(value, Domain):
|
||||||
|
value = self.search(value).ids
|
||||||
|
operator = "in" if operator == "any" else "not in"
|
||||||
|
elif operator not in ('in', 'not in'):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
group_definitions = self._get_group_definitions()
|
||||||
|
ids = [*value, *group_definitions.get_superset_ids(value)]
|
||||||
|
|
||||||
|
return [('id', operator, ids)]
|
||||||
|
|
||||||
|
def _get_user_type_groups(self):
|
||||||
|
""" Return the (disjoint) user type groups (employee, portal, public). """
|
||||||
|
group_ids = [
|
||||||
|
gid
|
||||||
|
for xid in ('base.group_user', 'base.group_portal', 'base.group_public')
|
||||||
|
if (gid := self.env['ir.model.data']._xmlid_to_res_id(xid, raise_if_not_found=False))
|
||||||
|
]
|
||||||
|
return self.sudo().browse(group_ids)
|
||||||
|
|
||||||
|
def _compute_disjoint_ids(self):
|
||||||
|
user_type_groups = self._get_user_type_groups()
|
||||||
|
for group in self:
|
||||||
|
if group in user_type_groups:
|
||||||
|
group.disjoint_ids = user_type_groups - group
|
||||||
|
else:
|
||||||
|
group.disjoint_ids = False
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
groups = super().create(vals_list)
|
||||||
|
self.env.registry.clear_cache('groups')
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
res = super().unlink()
|
||||||
|
self.env.registry.clear_cache('groups')
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _apply_group(self, implied_group):
|
||||||
|
""" Add the given group to the groups implied by the current group
|
||||||
|
:param implied_group: the implied group to add
|
||||||
|
"""
|
||||||
|
groups = self.filtered(lambda g: implied_group not in g.all_implied_ids)
|
||||||
|
groups.write({'implied_ids': [Command.link(implied_group.id)]})
|
||||||
|
|
||||||
|
def _remove_group(self, implied_group):
|
||||||
|
""" Remove the given group from the implied groups of the current group
|
||||||
|
:param implied_group: the implied group to remove
|
||||||
|
"""
|
||||||
|
groups = self.all_implied_ids.filtered(lambda g: implied_group in g.implied_ids)
|
||||||
|
groups.write({'implied_ids': [Command.unlink(implied_group.id)]})
|
||||||
|
|
||||||
|
def _compute_view_group_hierarchy(self):
|
||||||
|
self.view_group_hierarchy = self._get_view_group_hierarchy()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
@tools.ormcache('self.env.lang', cache='groups')
|
||||||
|
def _get_view_group_hierarchy(self):
|
||||||
|
return {
|
||||||
|
'groups': {
|
||||||
|
group.id: {
|
||||||
|
'id': group.id,
|
||||||
|
'name': group.name,
|
||||||
|
'comment': group.comment,
|
||||||
|
'privilege_id': group.privilege_id.id,
|
||||||
|
'disjoint_ids': group.disjoint_ids.ids,
|
||||||
|
'implied_ids': group.implied_ids.ids,
|
||||||
|
'all_implied_ids': group.all_implied_ids.ids,
|
||||||
|
'all_implied_by_ids': group.all_implied_by_ids.ids,
|
||||||
|
}
|
||||||
|
for group in self.search([])
|
||||||
|
},
|
||||||
|
'privileges': {
|
||||||
|
privilege.id: {
|
||||||
|
'id': privilege.id,
|
||||||
|
'name': privilege.name,
|
||||||
|
'category_id': privilege.category_id.id,
|
||||||
|
'description': privilege.description,
|
||||||
|
'placeholder': privilege.placeholder,
|
||||||
|
'group_ids': [group.id for group in privilege.group_ids.sorted(lambda g: (len(g.all_implied_ids & privilege.group_ids) if g.privilege_id else 0, g.sequence, g.id))]
|
||||||
|
}
|
||||||
|
for privilege in self.env['res.groups.privilege'].search([])
|
||||||
|
},
|
||||||
|
'categories': [
|
||||||
|
{
|
||||||
|
'id': category.id,
|
||||||
|
'name': category.name,
|
||||||
|
'privilege_ids': category.privilege_ids.sorted(lambda p: p.sequence).filtered(lambda p: p.group_ids).ids,
|
||||||
|
} for category in self.env['ir.module.category'].search([('privilege_ids.group_ids', '!=', False)])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
@tools.ormcache(cache='groups')
|
||||||
|
def _get_group_definitions(self):
|
||||||
|
""" Return the definition of all the groups as a :class:`~odoo.tools.SetDefinitions`. """
|
||||||
|
groups = self.sudo().search([], order='id')
|
||||||
|
id_to_ref = groups.get_external_id()
|
||||||
|
data = {
|
||||||
|
group.id: {
|
||||||
|
'ref': id_to_ref[group.id] or str(group.id),
|
||||||
|
'supersets': group.implied_ids.ids,
|
||||||
|
'disjoints': group.disjoint_ids.ids,
|
||||||
|
}
|
||||||
|
for group in groups
|
||||||
|
}
|
||||||
|
return SetDefinitions(data)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _is_feature_enabled(self, group_reference):
|
||||||
|
return self.env['res.users'].sudo().browse(api.SUPERUSER_ID)._has_group(group_reference)
|
||||||
|
|
||||||
|
@api.depends('all_user_ids')
|
||||||
|
def _compute_all_users_count(self):
|
||||||
|
for group in self:
|
||||||
|
group.all_users_count = len(group.all_user_ids)
|
||||||
|
|
||||||
|
def action_show_all_users(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return {
|
||||||
|
'name': self.env._('Users and implied users of %(group)s', group=self.display_name),
|
||||||
|
'view_mode': 'list,form',
|
||||||
|
'res_model': 'res.users',
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'context': {'create': False, 'delete': False, 'form_view_ref': 'base.view_users_form'},
|
||||||
|
'domain': [('all_group_ids', 'in', self.ids)],
|
||||||
|
'target': 'current',
|
||||||
|
}
|
||||||
81
scratch_xmlrpc.py
Normal file
81
scratch_xmlrpc.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import xmlrpc.client
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
url = "https://odoodev2.mymachconnect.com"
|
||||||
|
dbs = ['mymach-odoodev2-2026', 'mymach_odoodev2']
|
||||||
|
username = "ceren.camkaya@mymach.com.tr"
|
||||||
|
password = "cerenhanım"
|
||||||
|
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
|
||||||
|
common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url), context=ctx)
|
||||||
|
models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url), context=ctx)
|
||||||
|
|
||||||
|
for db_name in dbs:
|
||||||
|
print(f"--- Trying DB: {db_name} ---")
|
||||||
|
try:
|
||||||
|
uid = common.authenticate(db_name, username, password, {})
|
||||||
|
if uid:
|
||||||
|
print(f"Logged in successfully to {db_name}! UID: {uid}")
|
||||||
|
|
||||||
|
# Check module status
|
||||||
|
module_ids = models.execute_kw(db_name, uid, password,
|
||||||
|
'ir.module.module', 'search_read',
|
||||||
|
[[['name', '=', 'mymach_fleet_intelligence']]],
|
||||||
|
{'fields': ['state', 'latest_version']})
|
||||||
|
|
||||||
|
if module_ids:
|
||||||
|
mod = module_ids[0]
|
||||||
|
print(f"Module found! State: {mod['state']}, Version: {mod['latest_version']}")
|
||||||
|
|
||||||
|
# Check current view XML (e.g. looking for the language selector removal)
|
||||||
|
# Let's search for a view that we modified.
|
||||||
|
# 'fleet.vehicle.form' was modified? No, 'fleet_vehicle_view_form' might have been.
|
||||||
|
# Actually, let's look for 'res.groups' view if we added it, but it's data.
|
||||||
|
# Let's look for 'mymach_fleet_intelligence.view_home_form' (which had lang removed)
|
||||||
|
|
||||||
|
# Find the view by external ID
|
||||||
|
model_data = models.execute_kw(db_name, uid, password,
|
||||||
|
'ir.model.data', 'search_read',
|
||||||
|
[[['module', '=', 'mymach_fleet_intelligence'], ['name', '=', 'view_home_form']]],
|
||||||
|
{'fields': ['res_id']})
|
||||||
|
|
||||||
|
if model_data:
|
||||||
|
view_id = model_data[0]['res_id']
|
||||||
|
view = models.execute_kw(db_name, uid, password,
|
||||||
|
'ir.ui.view', 'read',
|
||||||
|
[[view_id]],
|
||||||
|
{'fields': ['arch_db']})
|
||||||
|
if view:
|
||||||
|
arch = view[0]['arch_db']
|
||||||
|
if 'field name="lang"' in arch:
|
||||||
|
print("The view STILL HAS the language selector (Old code).")
|
||||||
|
else:
|
||||||
|
print("The view DOES NOT have language selector (New code).")
|
||||||
|
|
||||||
|
# Try to trigger an upgrade
|
||||||
|
print("Triggering module upgrade...")
|
||||||
|
models.execute_kw(db_name, uid, password,
|
||||||
|
'ir.module.module', 'button_immediate_upgrade',
|
||||||
|
[[mod['id']]])
|
||||||
|
print("Upgrade finished! Let's check the view again...")
|
||||||
|
|
||||||
|
if model_data:
|
||||||
|
view = models.execute_kw(db_name, uid, password,
|
||||||
|
'ir.ui.view', 'read',
|
||||||
|
[[view_id]],
|
||||||
|
{'fields': ['arch_db']})
|
||||||
|
if view:
|
||||||
|
arch = view[0]['arch_db']
|
||||||
|
if 'field name="lang"' in arch:
|
||||||
|
print("AFTER UPGRADE: The view STILL HAS the language selector (Old code). This means git pull failed on the server!")
|
||||||
|
else:
|
||||||
|
print("AFTER UPGRADE: The view is updated! The issue was just that it wasn't upgraded properly.")
|
||||||
|
else:
|
||||||
|
print("Module not found in this DB.")
|
||||||
|
else:
|
||||||
|
print("Login failed for this DB.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error for DB {db_name}: {e}")
|
||||||
Reference in New Issue
Block a user