130 lines
4.8 KiB
Python
130 lines
4.8 KiB
Python
from odoo import models, fields, api
|
|
from math import radians, sin, cos, sqrt, atan2
|
|
|
|
|
|
class WtLocationLog(models.Model):
|
|
_name = 'wt.location.log'
|
|
_description = 'WorkTrace Location Log'
|
|
_order = 'arrived_at asc'
|
|
|
|
name = fields.Char(
|
|
string='Location',
|
|
compute='_compute_name',
|
|
store=True
|
|
)
|
|
date = fields.Date(string='Date', required=True, index=True)
|
|
arrived_at = fields.Datetime(string='Arrived At', required=True)
|
|
departed_at = fields.Datetime(string='Departed At')
|
|
|
|
latitude = fields.Float(string='Latitude', digits=(10, 7))
|
|
longitude = fields.Float(string='Longitude', digits=(10, 7))
|
|
address = fields.Char(string='Address')
|
|
place_name = fields.Char(string='Place / Business Name')
|
|
|
|
time_at_location = fields.Float(
|
|
string='Time at Location (hrs)',
|
|
compute='_compute_time_at_location',
|
|
store=True
|
|
)
|
|
distance_from_previous = fields.Float(
|
|
string='Distance from Previous (mi)',
|
|
digits=(10, 2)
|
|
)
|
|
travel_time_from_previous = fields.Float(
|
|
string='Travel Time from Previous (hrs)',
|
|
digits=(10, 2)
|
|
)
|
|
travel_mode = fields.Selection([
|
|
('driving', 'Driving'),
|
|
('walking', 'Walking'),
|
|
('transit', 'Transit'),
|
|
('unknown', 'Unknown'),
|
|
], string='Travel Mode', default='unknown')
|
|
|
|
source = fields.Selection([
|
|
('google_timeline', 'Google Timeline'),
|
|
('manual', 'Manual'),
|
|
], string='Source', default='google_timeline')
|
|
|
|
calendar_event_id = fields.Many2one(
|
|
'calendar.event',
|
|
string='Calendar Event',
|
|
ondelete='set null'
|
|
)
|
|
add_to_calendar = fields.Boolean(string='Add to Calendar', default=True)
|
|
notes = fields.Text(string='Notes')
|
|
|
|
@api.depends('address', 'place_name', 'latitude', 'longitude')
|
|
def _compute_name(self):
|
|
for rec in self:
|
|
rec.name = (
|
|
rec.place_name
|
|
or rec.address
|
|
or (f'{rec.latitude:.5f}, {rec.longitude:.5f}' if rec.latitude else 'Unknown Location')
|
|
)
|
|
|
|
@api.depends('arrived_at', 'departed_at')
|
|
def _compute_time_at_location(self):
|
|
for rec in self:
|
|
if rec.arrived_at and rec.departed_at:
|
|
delta = rec.departed_at - rec.arrived_at
|
|
rec.time_at_location = delta.total_seconds() / 3600
|
|
else:
|
|
rec.time_at_location = 0.0
|
|
|
|
@staticmethod
|
|
def _haversine_distance(lat1, lon1, lat2, lon2):
|
|
"""Calculate distance in miles between two GPS coordinates."""
|
|
R = 3958.8 # Earth radius in miles
|
|
lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
|
|
dlat = lat2 - lat1
|
|
dlon = lon2 - lon1
|
|
a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
|
|
return R * 2 * atan2(sqrt(a), sqrt(1 - a))
|
|
|
|
def action_geocode(self):
|
|
"""Resolve coordinates to address using OpenStreetMap Nominatim."""
|
|
import requests
|
|
for rec in self:
|
|
if not rec.latitude or not rec.longitude:
|
|
continue
|
|
url = 'https://nominatim.openstreetmap.org/reverse'
|
|
params = {
|
|
'lat': rec.latitude,
|
|
'lon': rec.longitude,
|
|
'format': 'json',
|
|
'addressdetails': 1,
|
|
}
|
|
headers = {'User-Agent': 'WorkTrace/1.0 (Odoo Module)'}
|
|
try:
|
|
resp = requests.get(url, params=params, headers=headers, timeout=10)
|
|
resp.raise_for_status()
|
|
result = resp.json()
|
|
rec.address = result.get('display_name', '')
|
|
rec.place_name = (
|
|
result.get('name')
|
|
or result.get('address', {}).get('amenity')
|
|
or result.get('address', {}).get('shop')
|
|
or result.get('address', {}).get('building')
|
|
or ''
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
def action_compute_distances(self):
|
|
"""Compute distances and travel times between sequential location logs for the same date."""
|
|
dates = self.mapped('date')
|
|
for date in set(dates):
|
|
logs = self.search([('date', '=', date)], order='arrived_at asc')
|
|
prev = None
|
|
for log in logs:
|
|
if prev and prev.latitude and log.latitude:
|
|
log.distance_from_previous = self._haversine_distance(
|
|
prev.latitude, prev.longitude,
|
|
log.latitude, log.longitude
|
|
)
|
|
if prev.departed_at and log.arrived_at:
|
|
delta = log.arrived_at - prev.departed_at
|
|
log.travel_time_from_previous = max(delta.total_seconds() / 3600, 0)
|
|
prev = log
|