diff --git a/loko/assets/tests_its_bollard_systems.py b/loko/assets/tests_its_bollard_systems.py new file mode 100644 index 0000000..31e78e6 --- /dev/null +++ b/loko/assets/tests_its_bollard_systems.py @@ -0,0 +1,311 @@ +import json +from django.test import TestCase, Client +from django.contrib.auth import get_user_model +from django.contrib.gis.geos import Point, LineString +from django.urls import reverse + +from common.models import Thematic, Role, UserConfig, UserThematics, UserContractAccess +from contracts.models import Contract +from assets.models.its import ( + ITSLocation, ITSBollardSystem, ITSBollardPlot, + ITSBollardSystemAsset, ITSBollardSystemContract, ITSGeoAsset +) + +User = get_user_model() + + +class ITSBollardSystemsGeojsonTests(TestCase): + def setUp(self): + self.thematic_its, _ = Thematic.objects.get_or_create( + code="its", + defaults={"name_fr": "Systèmes de transport intelligents", "name_nl": "ITS"} + ) + + self.role_admin, _ = Role.objects.get_or_create(name="admin") + + # Superuser + self.superuser = User.objects.create_superuser( + username="admin_its", + email="admin_its@example.com", + password="password123" + ) + UserConfig.objects.get_or_create(user=self.superuser, defaults={"is_intern": True}) + + # Internal user with ITS view access + self.user_internal = User.objects.create_user( + username="user_internal", + email="user_internal@example.com", + password="password123" + ) + cfg_internal, _ = UserConfig.objects.get_or_create( + user=self.user_internal, + defaults={"is_intern": True, "limit_assets_to_contracts": False} + ) + UserThematics.objects.create( + user_config=cfg_internal, + thematic=self.thematic_its, + can_view_assets=True, + can_edit_assets=False + ) + + # External user restricted to contracts + self.user_external = User.objects.create_user( + username="user_external", + email="user_external@example.com", + password="password123" + ) + self.cfg_external, _ = UserConfig.objects.get_or_create( + user=self.user_external, + defaults={"is_intern": False, "limit_assets_to_contracts": True} + ) + UserThematics.objects.create( + user_config=self.cfg_external, + thematic=self.thematic_its, + can_view_assets=True, + can_edit_assets=False + ) + + # Company & Contracts + from datetime import date + from contracts.models import Company + + self.company = Company.objects.create(name="ITS Maintenance Corp") + + self.contract_allowed = Contract.objects.create( + company=self.company, + contract_number="CTR-ITS-001", + start_date=date(2025, 1, 1), + end_date=date(2030, 1, 1), + description="Contrat Maintenance Bornes" + ) + self.contract_allowed.thematics.add(self.thematic_its) + + UserContractAccess.objects.create( + user_config=self.cfg_external, + contract=self.contract_allowed, + can_view_assets=True + ) + + self.contract_other = Contract.objects.create( + company=self.company, + contract_number="CTR-ITS-002", + start_date=date(2025, 1, 1), + end_date=date(2030, 1, 1), + description="Contrat Autre" + ) + self.contract_other.thematics.add(self.thematic_its) + + # Base Location + from django.contrib.gis.geos import Polygon, MultiPolygon + center = Point(4.3517, 50.8400, srid=4326) + center.transform(3812) + poly = center.buffer(20) + self.location = ITSLocation.objects.create( + code="LOC-001", + name_fr="Rue Haute - Entrée Piétonne", + lon=4.3517, + lat=50.8400, + geom=MultiPolygon([poly], srid=3812) + ) + + # Bollard System 1 (with geometry LineString) + # 3812 coords approx around Brussels + line_3812 = LineString((148000, 170000), (148020, 170010), srid=3812) + self.system1 = ITSBollardSystem.objects.create( + code="BS-001", + name_fr="Traverse Bornes Rue Haute", + status="active", + width=6.5, + direction="bidirectional", + geom=line_3812, + lon=4.3517, + lat=50.8400, + location=self.location + ) + ITSBollardSystemContract.objects.create( + system=self.system1, + contract=self.contract_allowed, + status="active" + ) + + # Bollard Plots for System 1 + plot_pt1 = Point(148005, 170002, srid=3812) + self.plot1 = ITSBollardPlot.objects.create( + code="PL-001", + name_fr="Borne 1 (Gauche)", + system=self.system1, + position=1, + bollard_type="automatic", + status="active", + geom=plot_pt1, + lon=4.35172, + lat=50.84002 + ) + plot_pt2 = Point(148015, 170007, srid=3812) + self.plot2 = ITSBollardPlot.objects.create( + code="PL-002", + name_fr="Borne 2 (Droite)", + system=self.system1, + position=2, + bollard_type="automatic", + status="active", + geom=plot_pt2, + lon=4.35178, + lat=50.84005 + ) + + # Linked Camera for System 1 + from assets.models.its import ITSAssetModel + self.cam_model = ITSAssetModel.objects.create( + code="MOD-CAM-01", + name_fr="Caméra Axis LAPI" + ) + cam_pt = Point(148010, 170015, srid=3812) + self.camera = ITSGeoAsset.objects.create( + model=self.cam_model, + code="CAM-001", + name_fr="Caméra LAPI Entrée", + status="active", + geom=cam_pt, + lon=4.35175, + lat=50.84010, + location=self.location + ) + self.link_cam = ITSBollardSystemAsset.objects.create( + bollard_system=self.system1, + asset=self.camera, + role=ITSBollardSystemAsset.AssetRole.CAMERA_ENTRY, + comment="Caméra fixée sur candélabre" + ) + + # Bollard System 2 (Linked only to contract_other) + line2_3812 = LineString((149000, 171000), (149020, 171010), srid=3812) + self.system2 = ITSBollardSystem.objects.create( + code="BS-002", + name_fr="Traverse Bornes Boulevard", + status="active", + width=8.0, + direction="entry_only", + geom=line2_3812, + lon=4.3600, + lat=50.8500 + ) + ITSBollardSystemContract.objects.create( + system=self.system2, + contract=self.contract_other, + status="active" + ) + + # Bollard System 3 (Removed status - should be excluded) + line3_3812 = LineString((150000, 172000), (150020, 172010), srid=3812) + self.system_removed = ITSBollardSystem.objects.create( + code="BS-REMOVED", + name_fr="Traverse Supprimée", + status="removed", + geom=line3_3812, + lon=4.3700, + lat=50.8600 + ) + + self.client = Client() + + def test_superuser_sees_all_active_assets(self): + self.client.force_login(self.superuser) + url = reverse('assets:its_bollard_systems_geojson') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + data = response.json() + self.assertEqual(data.get('type'), 'FeatureCollection') + features = data.get('features', []) + codes = [f['properties']['code'] for f in features] + + # Check system 1 & 2 are included, removed system is excluded + self.assertIn("BS-001", codes) + self.assertIn("BS-002", codes) + self.assertNotIn("BS-REMOVED", codes) + + # Check plots are included + self.assertIn("PL-001", codes) + self.assertIn("PL-002", codes) + + # Check camera is included + self.assertIn("CAM-001", codes) + + # Check feature properties for system + bs1_feat = next(f for f in features if f['properties']['code'] == 'BS-001') + self.assertEqual(bs1_feat['geometry']['type'], 'LineString') + self.assertEqual(bs1_feat['properties']['asset_type'], 'itsbollardsystem') + self.assertEqual(bs1_feat['properties']['type'], 'asset') + self.assertEqual(bs1_feat['properties']['width'], 6.5) + self.assertEqual(bs1_feat['properties']['plots_count'], 2) + self.assertEqual(bs1_feat['properties']['location_id'], self.location.id) + + # Check feature properties for plot + pl1_feat = next(f for f in features if f['properties']['code'] == 'PL-001') + self.assertEqual(pl1_feat['geometry']['type'], 'Point') + self.assertEqual(pl1_feat['properties']['asset_type'], 'itsbollardplot') + self.assertEqual(pl1_feat['properties']['type'], 'asset') + self.assertEqual(pl1_feat['properties']['position'], 1) + self.assertEqual(pl1_feat['properties']['system_code'], 'BS-001') + + # Check feature properties for camera + cam_feat = next(f for f in features if f['properties']['code'] == 'CAM-001') + self.assertEqual(cam_feat['geometry']['type'], 'Point') + self.assertEqual(cam_feat['properties']['asset_type'], 'itsgeoasset') + self.assertTrue(cam_feat['properties']['is_camera']) + self.assertEqual(cam_feat['properties']['role'], 'camera_entry') + self.assertEqual(cam_feat['properties']['system_code'], 'BS-001') + + def test_external_user_restricted_by_contracts(self): + self.client.force_login(self.user_external) + url = reverse('assets:its_bollard_systems_geojson') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + data = response.json() + features = data.get('features', []) + codes = [f['properties']['code'] for f in features] + + # External user should only see BS-001 and its plots/cameras + self.assertIn("BS-001", codes) + self.assertIn("PL-001", codes) + self.assertIn("CAM-001", codes) + + # System 2 is not linked to their contract + self.assertNotIn("BS-002", codes) + + def test_internal_user_can_view_all_active_systems(self): + self.client.force_login(self.user_internal) + url = reverse('assets:its_bollard_systems_geojson') + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + data = response.json() + features = data.get('features', []) + codes = [f['properties']['code'] for f in features] + + self.assertIn("BS-001", codes) + self.assertIn("BS-002", codes) + self.assertNotIn("BS-REMOVED", codes) + + def test_unauthenticated_user_forbidden(self): + url = reverse('assets:its_bollard_systems_geojson') + response = self.client.get(url) + self.assertIn(response.status_code, [302, 403]) + + def test_get_nearest_its_location(self): + self.client.force_login(self.user_internal) + url = reverse('assets:get_nearest_its_location') + response = self.client.get(url, {'lon': 4.3517, 'lat': 50.8400}) + self.assertEqual(response.status_code, 200) + + data = response.json() + self.assertEqual(data.get('location_id'), self.location.id) + self.assertIn(self.location.code, data.get('location_label', '')) + + def test_get_nearest_its_location_invalid_coords(self): + self.client.force_login(self.user_internal) + url = reverse('assets:get_nearest_its_location') + response = self.client.get(url, {'lon': 'invalid', 'lat': 'invalid'}) + self.assertEqual(response.status_code, 400) diff --git a/loko/assets/urls.py b/loko/assets/urls.py index 1871419..f1b8aeb 100644 --- a/loko/assets/urls.py +++ b/loko/assets/urls.py @@ -217,11 +217,13 @@ urlpatterns = [ path('its/', its.its_index, name='its_index'), path('its/locations/', its.its_locations, name='its_locations'), path('its/locations/geojson/', its.its_locations_geojson, name='its_locations_geojson'), + path('its/locations/get-nearest/', its.get_nearest_its_location, name='get_nearest_its_location'), path('its/locations//', its.its_locations_detail, name='its_locations_detail'), path('its/locations//add-document/', its.its_location_add_document, name='its_location_add_document'), path('its/locations//delete-document/', its.its_location_delete_document, name='its_location_delete_document'), path('its/assets/', its.its_assets, name='its_assets'), path('its/assets/geojson/', its.its_assets_geojson, name='its_assets_geojson'), + path('its/bollard-systems/geojson/', its.its_bollard_systems_geojson, name='its_bollard_systems_geojson'), path('its/assets/autocomplete/', its.its_assets_autocomplete, name='its_autocomplete_assets'), path('its///', its.its_assets_detail, name='its_assets_detail'), diff --git a/loko/assets/views/its.py b/loko/assets/views/its.py index 6d6ca3e..45676b4 100644 --- a/loko/assets/views/its.py +++ b/loko/assets/views/its.py @@ -491,6 +491,263 @@ def its_assets_geojson(request): +@check_thematic_access('its') +@require_GET +def its_bollard_systems_geojson(request): + """ + Returns a GeoJSON FeatureCollection of retractable bollard system assets: + - LineString features for ITSBollardSystem + - Point features for ITSBollardPlot + - Point features for linked cameras (ITSGeoAsset linked via ITSBollardSystemAsset) + """ + bollard_system_ct = ContentType.objects.get_for_model(ITSBollardSystem) + bollard_plot_ct = ContentType.objects.get_for_model(ITSBollardPlot) + geoasset_ct = ContentType.objects.get_for_model(ITSGeoAsset) + location_ct = ContentType.objects.get_for_model(ITSLocation) + + bollard_system_qs = ( + ITSBollardSystem.objects + .select_related('location', 'category', 'model') + .prefetch_related('plots') + .exclude(status='removed') + ) + + # Restriction par contrat pour utilisateurs externes + user = request.user + if user.is_authenticated: + try: + user_config = user.config + except UserConfig.DoesNotExist: + user_config = None + + is_external = (user_config is None) or (not user_config.is_intern) + must_limit = is_external and (not user_config or user_config.limit_assets_to_contracts) + + if must_limit: + accessible_contract_ids = UserContractAccess.objects.filter( + user_config=user_config, + can_view_assets=True + ).values_list('contract_id', flat=True) + + bollard_system_qs = bollard_system_qs.filter( + bollard_systems_contracts__status='active', + bollard_systems_contracts__contract_id__in=accessible_contract_ids + ).distinct() + else: + bollard_system_qs = bollard_system_qs.none() + + def _extract_geom_and_coords(obj): + geometry = None + if getattr(obj, 'geojson', None): + try: + geometry = json.loads(obj.geojson) + except Exception: + geometry = None + + lon = getattr(obj, 'lon', None) + lat = getattr(obj, 'lat', None) + + if (lon is None or lat is None or geometry is None) and getattr(obj, 'geom', None): + try: + g = obj.geom + if g.srid and g.srid != 4326: + g = g.transform(4326, clone=True) + if geometry is None: + geometry = json.loads(g.geojson) + coords = getattr(g, 'coords', None) + if coords: + if isinstance(coords[0], (int, float)): + if lon is None: lon = coords[0] + if lat is None: lat = coords[1] + elif isinstance(coords[0], (list, tuple)) and coords[0]: + mid = len(coords) // 2 + if lon is None: lon = coords[mid][0] + if lat is None: lat = coords[mid][1] + except Exception: + pass + + if geometry is None and lon is not None and lat is not None: + geometry = { + "type": "Point", + "coordinates": [lon, lat] + } + + return geometry, lon, lat + + features = [] + + # 1. Bollard Systems (Lines) + for system in bollard_system_qs: + geometry, lon, lat = _extract_geom_and_coords(system) + if not geometry: + continue + + loc = system.location + loc_label = f"{loc.get_name()} ({loc.code})" if (loc and loc.code) else (loc.get_name() if loc else None) + label = f"{system.code} - {system.get_name()}" if system.get_name() else system.code + + features.append({ + "type": "Feature", + "id": system.id, + "geometry": geometry, + "properties": { + "type": "asset", + "asset_type": "itsbollardsystem", + "id": system.id, + "ct": bollard_system_ct.id, + "code": system.code, + "name": system.get_name(), + "label": label, + "equipment_type": _("Système de bornes"), + "status": system.status, + "width": system.width, + "direction": system.get_direction_display() if hasattr(system, 'get_direction_display') else system.direction, + "plots_count": system.plots.count(), + "lon": lon, + "lat": lat, + "location_id": loc.id if loc else None, + "location_ct": location_ct.id if loc else None, + "location_label": loc_label, + } + }) + + # 2. Bollard Plots (Points) + bollard_plot_qs = ( + ITSBollardPlot.objects + .filter(system__in=bollard_system_qs) + .select_related('system', 'location', 'category', 'model', 'system__location') + .exclude(status='removed') + ) + + for plot in bollard_plot_qs: + geometry, lon, lat = _extract_geom_and_coords(plot) + if not geometry and plot.system: + _ignored, lon, lat = _extract_geom_and_coords(plot.system) + if lon is not None and lat is not None: + geometry = {"type": "Point", "coordinates": [lon, lat]} + + if not geometry: + continue + + loc = plot.location or (plot.system.location if plot.system else None) + loc_label = f"{loc.get_name()} ({loc.code})" if (loc and loc.code) else (loc.get_name() if loc else None) + plot_name = plot.get_name() or f"Plot #{plot.position}" + label = f"{plot.code} - {plot_name}" if plot.code else plot_name + + features.append({ + "type": "Feature", + "id": plot.id, + "geometry": geometry, + "properties": { + "type": "asset", + "asset_type": "itsbollardplot", + "id": plot.id, + "ct": bollard_plot_ct.id, + "code": plot.code, + "name": plot_name, + "label": label, + "equipment_type": plot.get_bollard_type_display() if hasattr(plot, 'get_bollard_type_display') else _("Plot de borne"), + "bollard_type": plot.bollard_type, + "bollard_type_display": plot.get_bollard_type_display() if hasattr(plot, 'get_bollard_type_display') else plot.bollard_type, + "status": plot.status, + "position": plot.position, + "system_id": plot.system_id, + "system_code": plot.system.code if plot.system else None, + "lon": lon, + "lat": lat, + "location_id": loc.id if loc else None, + "location_ct": location_ct.id if loc else None, + "location_label": loc_label, + } + }) + + # 3. Linked Cameras (Points) + camera_roles = [ + ITSBollardSystemAsset.AssetRole.CAMERA_ENTRY, + ITSBollardSystemAsset.AssetRole.CAMERA_EXIT, + ITSBollardSystemAsset.AssetRole.CAMERA_CCTV, + ] + system_assets_qs = ( + ITSBollardSystemAsset.objects + .filter(bollard_system__in=bollard_system_qs) + .filter( + Q(role__in=camera_roles) | + Q(role__icontains='camera') | + Q(asset__category__name_fr__icontains='caméra') | + Q(asset__category__name_nl__icontains='camera') | + Q(asset__category__code__icontains='camera') | + Q(asset__model__name_fr__icontains='caméra') | + Q(asset__model__name_nl__icontains='camera') | + Q(asset__model__code__icontains='camera') + ) + .select_related( + 'asset', 'asset__model', 'asset__category', 'bollard_system', + 'asset__location', 'bollard_system__location', + 'asset__trafficlights_pole', 'asset__lighting_pole' + ) + .exclude(asset__status='removed') + ) + + seen_camera_ids = set() + for link in system_assets_qs: + camera = link.asset + if camera.id in seen_camera_ids: + continue + seen_camera_ids.add(camera.id) + + geometry, lon, lat = _extract_geom_and_coords(camera) + if not geometry: + support = camera.trafficlights_pole or camera.lighting_pole + if support: + geometry, lon, lat = _extract_geom_and_coords(support) + if not geometry and link.bollard_system: + _ignored, lon, lat = _extract_geom_and_coords(link.bollard_system) + if lon is not None and lat is not None: + geometry = {"type": "Point", "coordinates": [lon, lat]} + + if not geometry: + continue + + loc = camera.location or (link.bollard_system.location if link.bollard_system else None) + loc_label = f"{loc.get_name()} ({loc.code})" if (loc and loc.code) else (loc.get_name() if loc else None) + cam_name = camera.get_name() or link.get_role_display() + label = f"{camera.code} - {cam_name}" if camera.code else cam_name + + features.append({ + "type": "Feature", + "id": camera.id, + "geometry": geometry, + "properties": { + "type": "asset", + "asset_type": "itsgeoasset", + "is_camera": True, + "id": camera.id, + "ct": geoasset_ct.id, + "code": camera.code, + "name": cam_name, + "label": label, + "equipment_type": link.get_role_display() or _("Caméra"), + "status": camera.status, + "role": link.role, + "role_display": link.get_role_display(), + "comment": link.comment, + "system_id": link.bollard_system_id, + "system_code": link.bollard_system.code if link.bollard_system else None, + "lon": lon, + "lat": lat, + "location_id": loc.id if loc else None, + "location_ct": location_ct.id if loc else None, + "location_label": loc_label, + } + }) + + return JsonResponse({ + "type": "FeatureCollection", + "features": features, + }, safe=False) + + + @check_thematic_access('its') def its_locations_detail(request, location_id): # Préfetch (réduction N+1) : ASSETS + enfants (2 niveaux) diff --git a/loko/interventions/static/interventions/add/autocomplete.js b/loko/interventions/static/interventions/add/autocomplete.js index d108b6d..cccf0e2 100644 --- a/loko/interventions/static/interventions/add/autocomplete.js +++ b/loko/interventions/static/interventions/add/autocomplete.js @@ -186,7 +186,7 @@ function updateSelectedEquipmentsUI() { } } -function applyEquipmentLocation(item) { +function applyEquipmentLocation(item, options = {}) { if (!item) { return; } @@ -247,7 +247,7 @@ function applyEquipmentLocation(item) { } } - if (item.lon && item.lat) { + if (!options.preserveCoordinates && item.lon && item.lat) { $('#id_lon').val(item.lon); $('#id_lat').val(item.lat); centerMap(); @@ -279,7 +279,7 @@ function addEquipmentSelection(item, options = {}) { } updateSelectedEquipmentsUI(); - applyEquipmentLocation(item); + applyEquipmentLocation(item, options); } $(function() { diff --git a/loko/interventions/static/interventions/add/map.js b/loko/interventions/static/interventions/add/map.js index a5a9755..ea9cb05 100644 --- a/loko/interventions/static/interventions/add/map.js +++ b/loko/interventions/static/interventions/add/map.js @@ -222,7 +222,17 @@ document.addEventListener('DOMContentLoaded', function () { const isSelected = selectedEquipments.some(s => s.equipment_ct == eq.ct && s.equipment_id == eq.id); html += `
- + @@ -252,11 +262,15 @@ document.addEventListener('DOMContentLoaded', function () { const item = { equipment_id: cb.dataset.id, equipment_ct: cb.dataset.ct, + equipment_type: cb.dataset.equipmentType || '', label: cb.dataset.label, + location_id: cb.dataset.locationId || null, + location_ct: cb.dataset.locationCt || null, + location_label: cb.dataset.locationLabel || null, lon: parseFloat(cb.dataset.lon), lat: parseFloat(cb.dataset.lat) }; - addEquipmentSelection(item, { replaceExisting: false }); + addEquipmentSelection(item, { replaceExisting: false, preserveCoordinates: true }); } }); // NOTE: marker stays at click position — not moved to any equipment. @@ -330,8 +344,18 @@ document.addEventListener('DOMContentLoaded', function () { 'traffic-lights', 'controlcenters', 'fountains', - 'monuments' - ].filter(layerId => map.getLayer(layerId)); + 'monuments', + 'its-bollard-systems-line', + 'its-bollard-plots-circle', + 'its-cameras-circle' + ].filter(layerId => { + if (!map.getLayer(layerId)) return false; + try { + return map.getLayoutProperty(layerId, 'visibility') !== 'none'; + } catch (err) { + return true; + } + }); const bbox = [ [e.point.x - 5, e.point.y - 5], @@ -346,13 +370,13 @@ document.addEventListener('DOMContentLoaded', function () { clickedFeatures.forEach(feat => { const props = feat.properties || {}; - const type = props.type; // 'asset' or 'location' for inspection-zones + const type = props.type; // 'asset' or 'location' - // 1. From our custom inspection configuration layers - if (feat.layer.id.startsWith('inspection-zones')) { + // 1. From our custom inspection configuration layers or ITS bollard layers + if (feat.layer.id.startsWith('inspection-zones') || feat.layer.id.startsWith('its-') || type === 'asset') { const ct = props.ct; - const id = props.id; - const label = props.label || props.name || props.code || `${type} ${id}`; + const id = props.id || feat.id; + const label = props.label || props.name || props.code || `${type || 'asset'} ${id}`; let lon = props.lon ? parseFloat(props.lon) : null; let lat = props.lat ? parseFloat(props.lat) : null; @@ -366,11 +390,21 @@ document.addEventListener('DOMContentLoaded', function () { } } - if (type === 'asset' && ct && id) { + if ((type === 'asset' || feat.layer.id.startsWith('its-')) && ct && id) { const key = `${ct}:${id}`; if (!seenEquip.has(key)) { seenEquip.add(key); - equipments.push({ ct: ct, id: id, label: label, lon: lon, lat: lat }); + equipments.push({ + ct: ct, + id: id, + label: label, + equipment_type: props.equipment_type || '', + location_id: props.location_id || null, + location_ct: props.location_ct || null, + location_label: props.location_label || null, + lon: lon, + lat: lat + }); } } else if (type === 'location' && ct && id) { const key = `${ct}:${id}`; @@ -423,10 +457,14 @@ document.addEventListener('DOMContentLoaded', function () { addEquipmentSelection({ equipment_id: eq.id, equipment_ct: eq.ct, + equipment_type: eq.equipment_type || '', label: eq.label, + location_id: eq.location_id, + location_ct: eq.location_ct, + location_label: eq.location_label, lon: eq.lon, lat: eq.lat - }, { replaceExisting: true }); + }, { replaceExisting: true, preserveCoordinates: true }); // NOTE: marker stays at click position — not moved to equipment coordinates. } // 2) Si plusieurs équipements sont présents, popup checkboxes diff --git a/loko/interventions/templates/interventions/add/_intervention_add_scripts.html b/loko/interventions/templates/interventions/add/_intervention_add_scripts.html index 00d711a..239b6c9 100644 --- a/loko/interventions/templates/interventions/add/_intervention_add_scripts.html +++ b/loko/interventions/templates/interventions/add/_intervention_add_scripts.html @@ -48,6 +48,7 @@ var autocomplete_assets_url = "{% url 'assets:sign_autocomplete_assets' %}"; {% elif thematic.code == 'its' %} var autocomplete_assets_url = "{% url 'assets:its_autocomplete_assets' %}"; + var its_bollard_systems_geojson_url = "{% url 'assets:its_bollard_systems_geojson' %}"; {% elif thematic.code == 'clean' %} var autocomplete_assets_url = "{% url 'assets:clean_autocomplete_assets' %}"; {% else %} @@ -362,6 +363,308 @@ map.on('load', setupArtworks); } +{% elif thematic.code == 'its' %} + {% endif %}