# -*- coding: utf-8 -*- # Copyright 2024-2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) """ Fusion Technician Location GPS location logging for field technicians. """ from odoo import models, fields, api, _ import logging _logger = logging.getLogger(__name__) class FusionTechnicianLocation(models.Model): _name = 'fusion.technician.location' _description = 'Technician Location Log' _order = 'logged_at desc' user_id = fields.Many2one( 'res.users', string='Technician', required=True, index=True, ondelete='cascade', ) latitude = fields.Float( string='Latitude', digits=(10, 7), required=True, ) longitude = fields.Float( string='Longitude', digits=(10, 7), required=True, ) accuracy = fields.Float( string='Accuracy (m)', help='GPS accuracy in meters', ) logged_at = fields.Datetime( string='Logged At', default=fields.Datetime.now, required=True, index=True, ) source = fields.Selection([ ('portal', 'Portal'), ('app', 'Mobile App'), ('sync', 'Synced'), ], string='Source', default='portal') sync_instance = fields.Char( 'Sync Instance', index=True, help='Source instance ID if synced (e.g. westin, mobility)', ) @api.model def log_location(self, latitude, longitude, accuracy=None): """Log the current user's location. Called from portal JS.""" return self.sudo().create({ 'user_id': self.env.user.id, 'latitude': latitude, 'longitude': longitude, 'accuracy': accuracy or 0, 'source': 'portal', }) @api.model def get_latest_locations(self): """Get the most recent location for each technician (for map view). Includes both local GPS pings and synced locations from remote instances, so the map shows all shared technicians regardless of which Odoo instance they are clocked into. """ self.env.cr.execute(""" SELECT DISTINCT ON (user_id) user_id, latitude, longitude, accuracy, logged_at, COALESCE(sync_instance, '') AS sync_instance FROM fusion_technician_location WHERE logged_at > NOW() - INTERVAL '24 hours' ORDER BY user_id, logged_at DESC """) rows = self.env.cr.dictfetchall() local_id = self.env['ir.config_parameter'].sudo().get_param( 'fusion_claims.sync_instance_id', '') result = [] for row in rows: user = self.env['res.users'].sudo().browse(row['user_id']) src = row.get('sync_instance') or local_id result.append({ 'user_id': row['user_id'], 'name': user.name, 'latitude': row['latitude'], 'longitude': row['longitude'], 'accuracy': row['accuracy'], 'logged_at': str(row['logged_at']), 'sync_instance': src, }) return result @api.model def _cron_cleanup_old_locations(self): """Remove location logs based on configurable retention setting. Setting (fusion_claims.location_retention_days): - Empty / not set => keep 30 days (default) - "0" => delete at end of day (keep today only) - "1" .. "N" => keep for N days """ ICP = self.env['ir.config_parameter'].sudo() raw = (ICP.get_param('fusion_claims.location_retention_days') or '').strip() if raw == '': retention_days = 30 # default: 1 month else: try: retention_days = max(int(raw), 0) except (ValueError, TypeError): retention_days = 30 cutoff = fields.Datetime.subtract(fields.Datetime.now(), days=retention_days) old_records = self.search([('logged_at', '<', cutoff)]) count = len(old_records) if count: old_records.unlink() _logger.info( "Cleaned up %d technician location records (retention=%d days)", count, retention_days, )