diff --git a/odoo/addons/dsrpt_repair_config/data/repair_fsm_zone_data_atlanta.xml b/odoo/addons/dsrpt_repair_config/data/repair_fsm_zone_data_atlanta.xml
index f171d77..1af7b36 100644
--- a/odoo/addons/dsrpt_repair_config/data/repair_fsm_zone_data_atlanta.xml
+++ b/odoo/addons/dsrpt_repair_config/data/repair_fsm_zone_data_atlanta.xml
@@ -3,6 +3,9 @@
Atlanta Metro
ATL
+
active
True
diff --git a/odoo/addons/dsrpt_repair_config/models/fsm_zone.py b/odoo/addons/dsrpt_repair_config/models/fsm_zone.py
index d640b42..51bbbfe 100644
--- a/odoo/addons/dsrpt_repair_config/models/fsm_zone.py
+++ b/odoo/addons/dsrpt_repair_config/models/fsm_zone.py
@@ -1,4 +1,8 @@
-from odoo import fields, models
+import json
+from urllib.parse import quote
+
+from odoo import api, fields, models
+from odoo.exceptions import ValidationError
class RepairFsmZone(models.Model):
@@ -9,6 +13,12 @@ class RepairFsmZone(models.Model):
name = fields.Char(required=True, tracking=True)
code = fields.Char(tracking=True)
+ polygon_geojson = fields.Text(
+ string="Polygon (GeoJSON)",
+ required=True,
+ tracking=True,
+ help="GeoJSON Polygon geometry. Coordinates order: [longitude, latitude].",
+ )
state = fields.Selection(
selection=[
("draft", "Draft"),
@@ -32,3 +42,79 @@ class RepairFsmZone(models.Model):
def action_reset_draft(self):
self.write({"state": "draft", "active": True})
+
+ @staticmethod
+ def _point_in_polygon(longitude, latitude, points):
+ inside = False
+ j = len(points) - 1
+ for i, (xi, yi) in enumerate(points):
+ xj, yj = points[j]
+ intersects = ((yi > latitude) != (yj > latitude)) and (
+ longitude < (xj - xi) * (latitude - yi) / ((yj - yi) or 1e-12) + xi
+ )
+ if intersects:
+ inside = not inside
+ j = i
+ return inside
+
+ def _extract_polygon_points(self):
+ self.ensure_one()
+ try:
+ data = json.loads(self.polygon_geojson or "")
+ except json.JSONDecodeError as exc:
+ raise ValidationError(f"Invalid GeoJSON: {exc.msg}") from exc
+
+ ring = []
+ if isinstance(data, dict):
+ if data.get("type") == "Feature":
+ data = data.get("geometry") or {}
+ if data.get("type") != "Polygon":
+ raise ValidationError("GeoJSON must be of type Polygon.")
+ coords = data.get("coordinates") or []
+ if not coords or not isinstance(coords[0], list):
+ raise ValidationError("Polygon coordinates are missing.")
+ ring = coords[0]
+ elif isinstance(data, list):
+ ring = data
+ else:
+ raise ValidationError("Polygon must be a GeoJSON object or an array of points.")
+
+ points = []
+ for pair in ring:
+ if not isinstance(pair, (list, tuple)) or len(pair) < 2:
+ raise ValidationError("Each polygon point must be [longitude, latitude].")
+ points.append((float(pair[0]), float(pair[1])))
+
+ if len(points) >= 2 and points[0] == points[-1]:
+ points = points[:-1]
+ if len(points) < 3:
+ raise ValidationError("Polygon must contain at least 3 points.")
+ return points
+
+ def contains_point(self, latitude, longitude):
+ self.ensure_one()
+ if latitude is None or longitude is None:
+ return False
+ points = self._extract_polygon_points()
+ return self._point_in_polygon(float(longitude), float(latitude), points)
+
+ def action_open_polygon_in_map(self):
+ self.ensure_one()
+ encoded = quote(self.polygon_geojson or '{"type":"Polygon","coordinates":[[]]}')
+ return {
+ "type": "ir.actions.act_url",
+ "url": f"https://geojson.io/#data=data:application/json,{encoded}",
+ "target": "new",
+ }
+
+ def action_open_point_picker_map(self):
+ return {
+ "type": "ir.actions.act_url",
+ "url": "https://www.openstreetmap.org",
+ "target": "new",
+ }
+
+ @api.constrains("polygon_geojson")
+ def _check_polygon_geojson(self):
+ for rec in self:
+ rec._extract_polygon_points()
diff --git a/odoo/addons/dsrpt_repair_config/views/repair_fsm_zone_view_form.xml b/odoo/addons/dsrpt_repair_config/views/repair_fsm_zone_view_form.xml
index 40a8842..38f8bbd 100644
--- a/odoo/addons/dsrpt_repair_config/views/repair_fsm_zone_view_form.xml
+++ b/odoo/addons/dsrpt_repair_config/views/repair_fsm_zone_view_form.xml
@@ -9,6 +9,8 @@
+
+
@@ -17,6 +19,9 @@
+
+
+
diff --git a/odoo/addons/dsrpt_repair_config/views/repair_fsm_zone_view_list.xml b/odoo/addons/dsrpt_repair_config/views/repair_fsm_zone_view_list.xml
index 3407051..8bf726c 100644
--- a/odoo/addons/dsrpt_repair_config/views/repair_fsm_zone_view_list.xml
+++ b/odoo/addons/dsrpt_repair_config/views/repair_fsm_zone_view_list.xml
@@ -8,6 +8,7 @@
+
diff --git a/odoo/addons/dsrpt_repair_work_orders/models/work_order.py b/odoo/addons/dsrpt_repair_work_orders/models/work_order.py
index 9e5cbfc..0b7bd69 100644
--- a/odoo/addons/dsrpt_repair_work_orders/models/work_order.py
+++ b/odoo/addons/dsrpt_repair_work_orders/models/work_order.py
@@ -10,6 +10,9 @@ class RepairWorkOrder(models.Model):
name = fields.Char(default="New", copy=False, readonly=True, tracking=True)
contact_id = fields.Many2one("dsrpt.contact", required=True, tracking=True)
+ service_address = fields.Char(tracking=True)
+ service_latitude = fields.Float(digits=(10, 6), tracking=True)
+ service_longitude = fields.Float(digits=(10, 6), tracking=True)
zone_id = fields.Many2one("repair.fsm.zone", string="FSM Zone", tracking=True)
description = fields.Text(tracking=True)
requested_datetime = fields.Datetime(default=fields.Datetime.now, tracking=True)
@@ -39,7 +42,15 @@ class RepairWorkOrder(models.Model):
for vals in vals_list:
if vals.get("name", "New") == "New":
vals["name"] = self.env["ir.sequence"].next_by_code("repair.work.order") or "New"
- return super().create(vals_list)
+ records = super().create(vals_list)
+ for record, vals in zip(records, vals_list):
+ if vals.get("zone_id"):
+ continue
+ if record._has_service_point():
+ zone = record._find_zone_for_point(record.service_latitude, record.service_longitude)
+ if zone and record.zone_id != zone:
+ record.zone_id = zone.id
+ return records
def _group_expand_states(self, states, domain, order):
return [key for key, _label in self._fields["state"].selection]
@@ -50,6 +61,45 @@ class RepairWorkOrder(models.Model):
rec.total_time_hours = sum(rec.time_line_ids.mapped("hours"))
rec.total_material_cost = sum(rec.material_line_ids.mapped("subtotal"))
+ def _has_service_point(self):
+ self.ensure_one()
+ return self.service_latitude is not False and self.service_longitude is not False
+
+ def _find_zone_for_point(self, latitude, longitude):
+ zones = self.env["repair.fsm.zone"].search([("active", "=", True), ("state", "=", "active")])
+ for zone in zones:
+ if zone.contains_point(latitude, longitude):
+ return zone
+ return self.env["repair.fsm.zone"]
+
+ @api.onchange("service_latitude", "service_longitude")
+ def _onchange_service_coordinates(self):
+ for rec in self:
+ if rec._has_service_point():
+ zone = rec._find_zone_for_point(rec.service_latitude, rec.service_longitude)
+ if zone:
+ rec.zone_id = zone
+
+ def action_detect_zone(self):
+ for rec in self:
+ if not rec._has_service_point():
+ raise ValidationError("Service coordinates are required to detect FSM Zone.")
+ zone = rec._find_zone_for_point(rec.service_latitude, rec.service_longitude)
+ if not zone:
+ raise ValidationError("No active FSM Zone contains this point.")
+ rec.zone_id = zone.id
+ return True
+
+ def write(self, vals):
+ result = super().write(vals)
+ if ("service_latitude" in vals or "service_longitude" in vals) and "zone_id" not in vals:
+ for rec in self:
+ if rec._has_service_point():
+ zone = rec._find_zone_for_point(rec.service_latitude, rec.service_longitude)
+ if zone and rec.zone_id != zone:
+ super(RepairWorkOrder, rec).write({"zone_id": zone.id})
+ return result
+
def action_confirm(self):
self.write({"state": "confirmed"})
diff --git a/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_form.xml b/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_form.xml
index 0361a8a..175de19 100644
--- a/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_form.xml
+++ b/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_form.xml
@@ -8,6 +8,7 @@
+
@@ -19,6 +20,9 @@
+
+
+
diff --git a/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_kanban.xml b/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_kanban.xml
index 25d014d..5817847 100644
--- a/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_kanban.xml
+++ b/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_kanban.xml
@@ -7,6 +7,7 @@
+
@@ -19,6 +20,9 @@
+
+
+
diff --git a/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_list.xml b/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_list.xml
index 4f6f423..067b99b 100644
--- a/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_list.xml
+++ b/odoo/addons/dsrpt_repair_work_orders/views/repair_work_order_view_list.xml
@@ -7,12 +7,15 @@
+
+
+