From 4bd71fa1a2be2d456f94f29a7b09f72b13ac2571 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Wed, 22 Jul 2026 14:24:18 +0800 Subject: [PATCH] feat(registrant_gis): show latitude/longitude inputs on registrant form (#1143) --- spp_registrant_gis/models/res_partner.py | 48 ++++++++++++++++++- .../tests/test_registrant_gis.py | 22 +++++++++ .../views/res_partner_views.xml | 10 ++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/spp_registrant_gis/models/res_partner.py b/spp_registrant_gis/models/res_partner.py index 1ba8b3ad1..60d20d31d 100644 --- a/spp_registrant_gis/models/res_partner.py +++ b/spp_registrant_gis/models/res_partner.py @@ -1,5 +1,7 @@ # Part of OpenSPP. See LICENSE file for full copyright and licensing details. -from odoo import fields, models +import json + +from odoo import api, fields, models class ResPartner(models.Model): @@ -12,3 +14,47 @@ class ResPartner(models.Model): help="Geographic coordinates (latitude/longitude) for spatial queries and mapping. " "Used for proximity-based targeting and geographic analysis of registrants.", ) + # OP#1143: plain latitude/longitude inputs kept in sync with `coordinates` + # so the values can be entered (registry create, change requests) and + # imported without needing the MapTiler map widget configured. Editing the + # map updates these; editing these rebuilds the point. + gis_latitude = fields.Float( + string="Latitude", + digits=(9, 6), + compute="_compute_gis_lat_long", + inverse="_inverse_gis_lat_long", + store=True, + help="Latitude in decimal degrees (WGS84). Kept in sync with GPS Coordinates.", + ) + gis_longitude = fields.Float( + string="Longitude", + digits=(9, 6), + compute="_compute_gis_lat_long", + inverse="_inverse_gis_lat_long", + store=True, + help="Longitude in decimal degrees (WGS84). Kept in sync with GPS Coordinates.", + ) + + @api.depends("coordinates") + def _compute_gis_lat_long(self): + """Derive latitude/longitude from the point geometry (x=lon, y=lat).""" + for rec in self: + geom = rec.coordinates + if geom and not geom.is_empty and getattr(geom, "geom_type", None) == "Point": + rec.gis_longitude = geom.x + rec.gis_latitude = geom.y + else: + rec.gis_longitude = 0.0 + rec.gis_latitude = 0.0 + + def _inverse_gis_lat_long(self): + """Rebuild the point geometry when latitude/longitude are entered. + + Both values are taken from the record, so filling either field commits + the current pair. (0, 0) is treated as "unset" and clears the point. + """ + for rec in self: + if rec.gis_latitude or rec.gis_longitude: + rec.coordinates = json.dumps({"type": "Point", "coordinates": [rec.gis_longitude, rec.gis_latitude]}) + else: + rec.coordinates = False diff --git a/spp_registrant_gis/tests/test_registrant_gis.py b/spp_registrant_gis/tests/test_registrant_gis.py index c815820e2..64c2fe2d2 100644 --- a/spp_registrant_gis/tests/test_registrant_gis.py +++ b/spp_registrant_gis/tests/test_registrant_gis.py @@ -79,3 +79,25 @@ def test_coordinates_empty_by_default(self): # Coordinates should be False/empty by default self.assertFalse(registrant.coordinates) + + # ── OP#1143: visible latitude/longitude inputs synced with coordinates ── + def test_lat_long_computed_from_coordinates(self): + """Setting coordinates populates the latitude/longitude inputs.""" + registrant = self.partner_model.create({"name": "LatLong From Point", "is_registrant": True}) + registrant.write({"coordinates": json.dumps({"type": "Point", "coordinates": [121.5, 14.25]})}) + self.assertAlmostEqual(registrant.gis_longitude, 121.5, places=5) + self.assertAlmostEqual(registrant.gis_latitude, 14.25, places=5) + + def test_coordinates_built_from_lat_long(self): + """Typing latitude/longitude rebuilds the coordinates point.""" + group = self.partner_model.create({"name": "LatLong Group", "is_registrant": True, "is_group": True}) + group.write({"gis_latitude": 8.5, "gis_longitude": 124.75}) + self.assertTrue(group.coordinates) + self.assertAlmostEqual(group.coordinates.x, 124.75, places=5) # x = longitude + self.assertAlmostEqual(group.coordinates.y, 8.5, places=5) # y = latitude + + def test_lat_long_empty_when_no_coordinates(self): + """With no point set, the latitude/longitude inputs read as 0.""" + registrant = self.partner_model.create({"name": "No Coords", "is_registrant": True}) + self.assertEqual(registrant.gis_latitude, 0.0) + self.assertEqual(registrant.gis_longitude, 0.0) diff --git a/spp_registrant_gis/views/res_partner_views.xml b/spp_registrant_gis/views/res_partner_views.xml index 5a258a565..3bed3464e 100644 --- a/spp_registrant_gis/views/res_partner_views.xml +++ b/spp_registrant_gis/views/res_partner_views.xml @@ -14,6 +14,11 @@ > + + + @@ -33,6 +38,11 @@ > + + +