Files
mymach_fleet_intelligence/addons/mymach_fleet_intelligence/models/security_log.py

284 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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