284 lines
13 KiB
Python
284 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
||
from odoo import models, fields, api
|
||
import math
|
||
import requests
|
||
|
||
class MymachFleetSecurityLog(models.Model):
|
||
"""
|
||
Component 2 & 3: Security & OSRM Routing Engine
|
||
Stores telemetry and processes route matches and deviations.
|
||
"""
|
||
_name = 'mymach.fleet.security.log'
|
||
_description = 'Fleet Security and Route Logs'
|
||
_order = 'datetime desc'
|
||
|
||
name = fields.Char(string="Log Reference", required=True, default="Autonomous System Log")
|
||
vehicle_id = fields.Many2one('fleet.vehicle', string="Vehicle", required=True)
|
||
driver_id = fields.Many2one('res.partner', string="Driver", related='vehicle_id.driver_id', store=True)
|
||
|
||
datetime = fields.Datetime(string="Datetime", default=fields.Datetime.now)
|
||
latitude = fields.Float(string="Latitude", digits=(10, 7))
|
||
longitude = fields.Float(string="Longitude", digits=(10, 7))
|
||
|
||
status = fields.Selection([
|
||
('in_office_moving', 'WARNING: Employee in Office but Vehicle Moving'),
|
||
('project_match', 'CONFIRMED: Project Route Match'),
|
||
('exception', 'ATTENTION: Surprise Route Exception')
|
||
], string="Security Status", required=True)
|
||
|
||
is_resolved = fields.Boolean(string="Is Resolved?", default=False)
|
||
deduction_amount = fields.Monetary(string="Payroll Deduction Amount", currency_field='currency_id')
|
||
currency_id = fields.Many2one('res.currency', string="Currency", related='vehicle_id.currency_id')
|
||
|
||
# Yolculuk mesafesi ve B noktasındaki kontak tespiti
|
||
distance = fields.Float(string="Travel Distance (KM)", default=0.0)
|
||
partner_id = fields.Many2one('res.partner', string="Visited Contact")
|
||
|
||
def _calculate_haversine_distance(self, lat1, lon1, lat2, lon2):
|
||
""" Calculates straight-line distance in km """
|
||
R = 6371.0
|
||
dlat = math.radians(lat2 - lat1)
|
||
dlon = math.radians(lon2 - lon1)
|
||
a = math.sin(dlat / 2)**2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon / 2)**2
|
||
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
||
return R * c
|
||
|
||
def _calculate_osrm_road_distance(self, lat1, lon1, lat2, lon2):
|
||
""" Calculates real driving distance using public OSRM API (km) """
|
||
try:
|
||
url = f"http://router.project-osrm.org/route/v1/driving/{lon1},{lat1};{lon2},{lat2}?overview=false"
|
||
res = requests.get(url, timeout=3)
|
||
if res.status_code == 200:
|
||
data = res.json()
|
||
if data.get('code') == 'Ok' and data.get('routes'):
|
||
return data['routes'][0]['distance'] / 1000.0
|
||
except Exception:
|
||
pass
|
||
return None
|
||
|
||
def _find_nearest_partner(self, lat, lon):
|
||
""" B noktasının 200m yakınında kayıtlı res.partner arar """
|
||
if not lat or not lon:
|
||
return False
|
||
max_dist = 0.2 # 200 metre
|
||
partners = self.env['res.partner'].search([
|
||
('partner_latitude', '!=', 0.0),
|
||
('partner_longitude', '!=', 0.0)
|
||
])
|
||
for p in partners:
|
||
dist = self._calculate_haversine_distance(lat, lon, p.partner_latitude, p.partner_longitude)
|
||
if dist <= max_dist:
|
||
return p.id
|
||
return False
|
||
|
||
@api.model_create_multi
|
||
def create(self, vals_list):
|
||
for vals in vals_list:
|
||
# OSRM Rota Sapma Analizi
|
||
vehicle_id = vals.get('vehicle_id')
|
||
lat = vals.get('latitude')
|
||
lon = vals.get('longitude')
|
||
|
||
if vehicle_id and lat and lon:
|
||
# 1. Rota/Mesafe Hesaplama (Eğer dışarıdan mesafe verilmemişse)
|
||
if not vals.get('distance'):
|
||
last_log = self.search([
|
||
('vehicle_id', '=', vehicle_id)
|
||
], order='datetime desc', limit=1)
|
||
|
||
if last_log and last_log.latitude and last_log.longitude:
|
||
haversine_dist = self._calculate_haversine_distance(
|
||
last_log.latitude, last_log.longitude, lat, lon
|
||
)
|
||
osrm_dist = self._calculate_osrm_road_distance(
|
||
last_log.latitude, last_log.longitude, lat, lon
|
||
)
|
||
|
||
# Eğer karayolu sürüş mesafesi kuş uçuşunun %40'ından fazla sapmışsa ve mesafe 1.5 km'den büyükse istisna olarak işaretle
|
||
if osrm_dist and osrm_dist > 1.4 * haversine_dist and osrm_dist > 1.5:
|
||
vals['status'] = 'exception'
|
||
vals['name'] = f"OSRM Rota Sapması Sezildi (OSRM: {osrm_dist:.2f} km / Kuş Uçuşu: {haversine_dist:.2f} km)"
|
||
vals['distance'] = osrm_dist
|
||
elif osrm_dist:
|
||
vals['distance'] = osrm_dist
|
||
else:
|
||
vals['distance'] = haversine_dist
|
||
else:
|
||
vals['distance'] = 0.0
|
||
|
||
# 2. Yakındaki kontağı otomatik tespit et (Mesafe dışarıdan verilmiş olsa dahi)
|
||
nearest_partner = self._find_nearest_partner(lat, lon)
|
||
if nearest_partner:
|
||
vals['partner_id'] = nearest_partner
|
||
|
||
records = super(MymachFleetSecurityLog, self).create(vals_list)
|
||
for record in records:
|
||
if record.status in ['exception', 'in_office_moving'] and record.vehicle_id:
|
||
manager_id = record.vehicle_id.manager_id.id if record.vehicle_id.manager_id else self.env.uid
|
||
record.vehicle_id.activity_schedule(
|
||
'mail.mail_activity_data_warning',
|
||
summary=f'🚨 Otonom Güvenlik İhlali: {record.status}',
|
||
note=f'Sürücü: {record.driver_id.name} rotadan saptı veya yetkisiz araç kullandı. Lütfen kontrol edin!',
|
||
user_id=manager_id
|
||
)
|
||
|
||
if record.driver_id:
|
||
contract = self.env['mymach.fleet.contract'].search([
|
||
('driver_id', '=', record.driver_id.id),
|
||
], limit=1)
|
||
if contract:
|
||
deduct = 15 if record.status == 'in_office_moving' else 10
|
||
contract.write({'driver_score': contract.driver_score - deduct})
|
||
return records
|
||
|
||
@api.model
|
||
def get_dashboard_data(self):
|
||
"""
|
||
OWL Harita Arayüzünün veri okuması için optimize edilmiş ORM RPC metodu.
|
||
"""
|
||
vehicles = self.env['fleet.vehicle'].search([])
|
||
vehicle_list = []
|
||
for v in vehicles:
|
||
score = 100
|
||
commute_allowed = 'forbidden'
|
||
if v.driver_id:
|
||
contract = self.env['mymach.fleet.contract'].search([
|
||
('driver_id', '=', v.driver_id.id)
|
||
], limit=1)
|
||
if contract:
|
||
score = contract.driver_score
|
||
commute_allowed = contract.commute_allowed
|
||
|
||
vehicle_list.append({
|
||
'id': v.id,
|
||
'name': v.name,
|
||
'license_plate': v.license_plate,
|
||
'driver_name': v.driver_id.name or 'Sürücü Atanmamış',
|
||
'fuel_level': round(v.current_fuel_level, 1),
|
||
'tank_capacity': v.tank_capacity,
|
||
'needs_predictive_maintenance': v.needs_predictive_maintenance,
|
||
'is_maintenance_critical': v.is_maintenance_critical,
|
||
'score': score,
|
||
'commute_allowed': commute_allowed
|
||
})
|
||
|
||
logs = self.search([], limit=50)
|
||
log_list = []
|
||
for l in logs:
|
||
log_list.append({
|
||
'id': l.id,
|
||
'vehicle_name': l.vehicle_id.license_plate,
|
||
'driver_name': l.driver_id.name or 'Bilinmiyor',
|
||
'latitude': l.latitude,
|
||
'longitude': l.longitude,
|
||
'status': l.status,
|
||
'status_desc': dict(l._fields['status'].selection).get(l.status, ''),
|
||
'datetime': l.datetime.strftime('%Y-%m-%d %H:%M:%S') if l.datetime else ''
|
||
})
|
||
|
||
google_maps_api_key = self.env['ir.config_parameter'].sudo().get_param('mymach.google_maps_api_key', default='')
|
||
|
||
return {
|
||
'vehicles': vehicle_list,
|
||
'logs': log_list,
|
||
'google_maps_api_key': google_maps_api_key
|
||
}
|
||
|
||
def action_open_exception_wizard(self):
|
||
self.ensure_one()
|
||
return {
|
||
'name': 'İstisna Rota Karar Motoru',
|
||
'type': 'ir.actions.act_window',
|
||
'res_model': 'mymach.fleet.route.exception.wizard',
|
||
'view_mode': 'form',
|
||
'target': 'new',
|
||
'context': {
|
||
'default_log_id': self.id,
|
||
'default_vehicle_id': self.vehicle_id.id,
|
||
'default_partner_id': self.partner_id.id,
|
||
'default_distance': self.distance,
|
||
'default_estimated_cost': self.distance * (self.vehicle_id.km_cost or 5.0),
|
||
}
|
||
}
|
||
|
||
@api.model
|
||
def action_mymach_scrape_google_location(self):
|
||
"""
|
||
Google test hesabına giriş yaparak Kerem Bey'in canlı konum,
|
||
hız ve pil durumunu çeken otonom test fonksiyonu.
|
||
"""
|
||
# ==============================================================================
|
||
# BU SCRIPT: Kerem Bey'in şahsi hesabından test hesabına aktarılan canlı GPS
|
||
# verilerini Google Haritalar arayüzünden kazıyarak Odoo'ya aktarmak için yazılmıştır.
|
||
# Madde 3 uyarınca tüm kod blokları Türkçe teknik yorum satırları ile mühürlenmiştir.
|
||
# ==============================================================================
|
||
import logging
|
||
from datetime import datetime
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
try:
|
||
# Google konum paylaşım kütüphanesi (Gerekirse sunucuya pip ile kurulacak)
|
||
from locationsharinglib import Service
|
||
except ImportError:
|
||
_logger.error("locationsharinglib kurulu değil. Lütfen 'pip install locationsharinglib' çalıştırın.")
|
||
return False
|
||
|
||
# Etap 1'de oluşturulan test hesabı
|
||
google_email = "mymach.fleet.test@gmail.com"
|
||
# NOT: Yeni kütüphane sürümlerinde (v5+) email/şifre yerine cookies_file kullanılmaktadır.
|
||
cookies_file_path = "/tmp/google_cookies.txt"
|
||
|
||
try:
|
||
# Google servis bağlantısını başlatıyoruz
|
||
# ==============================================================================
|
||
# NOT: Bu bağlantı Google Cloud API faturalandırmasına tabi değildir, tamamen ücretsizdir.
|
||
# ==============================================================================
|
||
service = Service(cookies_file=cookies_file_path, authenticating_account=google_email)
|
||
|
||
# Paylaşılan konumlar içinden Kerem Bey'in şahsi cihaz kaydını buluyoruz
|
||
for person in service.get_all_people():
|
||
if "Kerem" in person.full_name or person.is_self == False:
|
||
|
||
# Google'dan gelen ham koordinat ve durum verileri
|
||
latitude = person.latitude
|
||
longitude = person.longitude
|
||
accuracy = person.accuracy # Metre cinsinden sapma payı
|
||
battery_level = person.battery_level # Telefonun şarj durumu
|
||
last_seen = person.timestamp # Son konum güncelleme zamanı
|
||
|
||
# Okunan bu verileri test loguna veya Odoo geçici tablosuna yazıyoruz
|
||
_logger.info(
|
||
"MyMach GPS Yakalandı - Enlem: %s, Boylam: %s, Şarj: %s%%",
|
||
latitude, longitude, battery_level
|
||
)
|
||
|
||
# Bu koordinatlar Odoo rota doğrulama hiyerarşisine gönderilecek.
|
||
# Kerem isimli sürücünün aracını veya sistemdeki ilk aracı buluyoruz.
|
||
vehicle = self.env['fleet.vehicle'].search([('driver_id.name', 'ilike', 'Kerem')], limit=1)
|
||
if not vehicle:
|
||
vehicle = self.env['fleet.vehicle'].search([], limit=1)
|
||
|
||
if vehicle:
|
||
# mymach.security.log tablosuna yeni hamle oluşturuluyor
|
||
self.create({
|
||
'name': f"Google Canlı Konum: {person.full_name} ({battery_level}%)",
|
||
'vehicle_id': vehicle.id,
|
||
'latitude': latitude,
|
||
'longitude': longitude,
|
||
'status': 'project_match', # Varsayılan olarak proje güzergahında sayılır
|
||
})
|
||
|
||
return {
|
||
'lat': latitude,
|
||
'lng': longitude,
|
||
'battery': battery_level,
|
||
'time': datetime.fromtimestamp(last_seen)
|
||
}
|
||
|
||
return False
|
||
|
||
except Exception as e:
|
||
_logger.error("Google konum kazıma tünelinde hata oluştu: %s", str(e))
|
||
return False
|