diff --git a/loko/assets/models/trafficlights.py b/loko/assets/models/trafficlights.py index d7b4805..4224eff 100644 --- a/loko/assets/models/trafficlights.py +++ b/loko/assets/models/trafficlights.py @@ -498,8 +498,18 @@ class TrafficLightDetector(TrafficLightAbstractGeoAsset): phases = ArrayField(models.CharField(max_length=50), null=True, blank=True, verbose_name=_("Phases associées")) mentionned_on_plan = models.BooleanField(default=False, verbose_name=_("Mentionné sur le plan")) + def get_name(self): + return self.name or self.name_fr or super().get_name() + def clean(self): super().clean() + if self.name: + if not self.name_fr: + self.name_fr = self.name + if not self.name_nl: + self.name_nl = self.name + elif self.name_fr and not self.name: + self.name = self.name_fr support = self.pole or self.controller if support and getattr(support, 'geom', None): self.geom = support.geom @@ -512,6 +522,13 @@ class TrafficLightDetector(TrafficLightAbstractGeoAsset): self.intersection = self.controller.intersection def save(self, *args, **kwargs): + if self.name: + if not self.name_fr: + self.name_fr = self.name + if not self.name_nl: + self.name_nl = self.name + elif self.name_fr and not self.name: + self.name = self.name_fr support = self.pole or self.controller if support and getattr(support, 'geom', None): self.geom = support.geom diff --git a/loko/assets/tests.py b/loko/assets/tests.py index ac95619..19b89d2 100644 --- a/loko/assets/tests.py +++ b/loko/assets/tests.py @@ -1324,16 +1324,19 @@ class TrafficLightCrossPlanImportTest(TestCase): self.assertEqual(old_lantern.replaced_by, new_active) def test_modify_action_updates_code_and_properties(self): - from assets.models import TrafficLightPole, TrafficLightLantern, TrafficLightDetector + from assets.models import TrafficLightPole, TrafficLightLantern, TrafficLightDetector, TrafficLightPoleModel, TrafficLightLanternModel + pole_model = TrafficLightPoleModel.objects.first() + lantern_model = TrafficLightLanternModel.objects.first() pole = TrafficLightPole.objects.create( intersection=self.intersection, code="SWB01_A01", + model=pole_model, status="active" ) old_lantern = TrafficLightLantern.objects.create( pole=pole, code="SWB01_A01_LAN01", - model=self.lantern_model, + model=lantern_model, status="active" ) self.client.force_login(self.user) @@ -1369,18 +1372,27 @@ class TrafficLightCrossPlanImportTest(TestCase): self.assertEqual(old_lantern.code, "SWB01_A01_LAN01_T1") def test_other_associated_assets_preview_and_integration(self): - from assets.models import TrafficLightPole, TrafficLightController, TrafficLightControllerHardware, TrafficLightAccessory - from assets.models.sign import SignPanel - from assets.models.its import ITSGeoAsset + from assets.models import ( + TrafficLightPole, TrafficLightController, TrafficLightControllerHardware, + TrafficLightAccessory, TrafficLightPoleModel, TrafficLightControllerModel + ) + from assets.models.sign import SignPanel, SignPanelModel + from assets.models.its import ITSGeoAsset, ITSAssetModel + pole_model = TrafficLightPoleModel.objects.first() + ctrl_model, _ = TrafficLightControllerModel.objects.get_or_create(code="ctrl_mod", defaults={"name_fr": "Model Ctrl"}) + sign_model, _ = SignPanelModel.objects.get_or_create(code="sp_mod", defaults={"name_fr": "Model SP"}) + its_model, _ = ITSAssetModel.objects.get_or_create(code="its_mod", defaults={"name_fr": "Model ITS"}) pole = TrafficLightPole.objects.create( intersection=self.intersection, code="SWB01_A01", + model=pole_model, status="active" ) ctrl = TrafficLightController.objects.create( intersection=self.intersection, code="SWB01_C01", + model=ctrl_model, status="active" ) hw = TrafficLightControllerHardware.objects.create( @@ -1391,11 +1403,13 @@ class TrafficLightCrossPlanImportTest(TestCase): sign = SignPanel.objects.create( trafficlight_pole=pole, code="SWB01_SP01", + model=sign_model, status="active" ) its = ITSGeoAsset.objects.create( trafficlights_pole=pole, code="SWB01_ITS01", + model=its_model, status="active" ) acc = TrafficLightAccessory.objects.create( @@ -2232,6 +2246,73 @@ class TrafficLightCrossPlanImportTest(TestCase): if os.path.exists(tmp_path): os.remove(tmp_path) + def test_parse_cross_plan_radar_power_ignored_and_detector_names(self): + import openpyxl + import tempfile + from assets.utils.cross_plan_parser import parse_cross_plan + from assets.models import TrafficLightDetector + + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "kruisjesplan" + ws.cell(row=1, column=3, value="sleutel:") + ws.cell(row=1, column=4, value="SAT110") + ws.cell(row=2, column=7, value="Câble 01 SVAVB 30x1,5mm²") + ws.cell(row=4, column=1, value="Klem controller") + ws.cell(row=4, column=2, value="Klemnummer") + ws.cell(row=4, column=3, value="Richting") + ws.cell(row=4, column=4, value="Kringen") + ws.cell(row=5, column=5, value="A01") + + # Row 6: RADAR power line -> should be IGNORED + ws.cell(row=6, column=2, value=1) + ws.cell(row=6, column=3, value="RADAR") + ws.cell(row=6, column=4, value="RADAR") + ws.cell(row=6, column=5, value="X") + + # Row 7: RB2 radar detector -> should be parsed as detector + ws.cell(row=7, column=2, value=2) + ws.cell(row=7, column=3, value="RB2") + ws.cell(row=7, column=4, value="RB2") + ws.cell(row=7, column=5, value="X") + + # Row 8: Push button BPb1 -> should be parsed as push button with name and phase + ws.cell(row=8, column=2, value=3) + ws.cell(row=8, column=3, value="BPb1") + ws.cell(row=8, column=4, value="DK b1") + ws.cell(row=8, column=5, value="X") + + with tempfile.NamedTemporaryFile(suffix=".xlsx", delete=False) as tmp: + tmp_path = tmp.name + wb.save(tmp_path) + + try: + deduced = parse_cross_plan(tmp_path) + detectors = deduced["detectors"] + self.assertEqual(len(detectors), 2) + + # Verify RADAR was NOT included + codes = [d["code"] for d in detectors] + self.assertNotIn("SAT110_A01_RADAR", codes) + self.assertIn("SAT110_A01_RB2", codes) + self.assertIn("SAT110_A01_BPb1", codes) + + bp = next(d for d in detectors if d["code"] == "SAT110_A01_BPb1") + self.assertEqual(bp["phases"], ["b"]) + self.assertIn("bouton poussoir", bp["name"]) + + # Test model get_name synchronization + from assets.models import TrafficLightDetectorModel + det_model = TrafficLightDetectorModel.objects.first() + det = TrafficLightDetector(intersection=self.intersection, name=bp["name"], code=bp["code"], model=det_model) + det.save() + self.assertEqual(det.name_fr, bp["name"]) + self.assertEqual(det.get_name(), bp["name"]) + finally: + import os + if os.path.exists(tmp_path): + os.remove(tmp_path) + class AssetDefaultPositionTest(TestCase): def test_assign_default_position_hierarchy(self): diff --git a/loko/assets/utils/cross_plan_parser.py b/loko/assets/utils/cross_plan_parser.py index af06e04..85b15a6 100644 --- a/loko/assets/utils/cross_plan_parser.py +++ b/loko/assets/utils/cross_plan_parser.py @@ -287,6 +287,10 @@ def parse_cross_plan(excel_path): normalized_upper = richting.upper().replace(" ", "") + # Check if RADAR power supply (Alimentation radar, not a detector) + if normalized_upper == "RADAR" or normalized_upper.startswith("RADAR") or (bool(re.search(r'\bradar\b', richting, re.IGNORECASE)) and not bool(re.match(r'^(?:RA|RB|RC|RD|RM|RP)\d*$', normalized_upper, re.IGNORECASE))): + continue + if conn.get("is_detector", False): is_detector = True elif normalized_upper in ("RA", "RAB", "RB", "RC", "RD", "VD", "VC", "VT1", "VT2") or normalized_upper.startswith(("RM", "RP", "RA", "RB", "RC", "RD", "VD", "VC", "VT")): diff --git a/loko/assets/views/trafficlights.py b/loko/assets/views/trafficlights.py index 2f3d96c..ba8f386 100644 --- a/loko/assets/views/trafficlights.py +++ b/loko/assets/views/trafficlights.py @@ -4751,6 +4751,8 @@ def integrate_cross_plan(request, intersection_id): pole=pole, code=dd["code"], name=custom_name, + name_fr=custom_name, + name_nl=custom_name, model=db_model, phases=dd["phases"], status='active', @@ -5067,6 +5069,8 @@ def integrate_cross_plan(request, intersection_id): pole=pole, code=code, name=custom_name, + name_fr=custom_name, + name_nl=custom_name, model=db_model, phases=dd["phases"], status='active', @@ -5079,6 +5083,8 @@ def integrate_cross_plan(request, intersection_id): existing.pole = pole existing.model = db_model existing.name = custom_name + existing.name_fr = custom_name + existing.name_nl = custom_name existing.phases = dd["phases"] existing.updated_by = request.user existing.save() @@ -5090,6 +5096,8 @@ def integrate_cross_plan(request, intersection_id): pole=pole, code=code, name=custom_name, + name_fr=custom_name, + name_nl=custom_name, model=db_model, phases=dd["phases"], status='active', @@ -5102,6 +5110,8 @@ def integrate_cross_plan(request, intersection_id): # 'keep' if existing: existing.name = custom_name + existing.name_fr = custom_name + existing.name_nl = custom_name existing.save() for key in request.POST.keys():