diff --git a/streetup/assets/admin.py b/streetup/assets/admin.py
index c089b69..931f0ff 100644
--- a/streetup/assets/admin.py
+++ b/streetup/assets/admin.py
@@ -694,7 +694,7 @@ class ParkingLocationAdmin(AssetGISAdmin):
class ParkingSpotAdmin(AssetGISAdmin):
list_display = (
"code", "name_fr", "spot_type", "stand_type",
- "standard_stand_count", "cargo_stand_count", "total_stand_count",
+ "standard_stand_count", "cargo_stand_count", "drop_off_stand_count", "total_stand_count",
"has_scooter_drop_zone", "is_compliant", "inspection_status", "status"
)
list_filter = (
diff --git a/streetup/assets/locale/fr/LC_MESSAGES/django.mo b/streetup/assets/locale/fr/LC_MESSAGES/django.mo
index aef3922..0e611af 100644
Binary files a/streetup/assets/locale/fr/LC_MESSAGES/django.mo and b/streetup/assets/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/streetup/assets/locale/fr/LC_MESSAGES/django.po b/streetup/assets/locale/fr/LC_MESSAGES/django.po
index 1561f87..ae0bb33 100644
--- a/streetup/assets/locale/fr/LC_MESSAGES/django.po
+++ b/streetup/assets/locale/fr/LC_MESSAGES/django.po
@@ -972,7 +972,7 @@ msgstr "À inspecter"
#: assets/models/core.py:260
#: assets/views/core.py:852
msgid "Conforme"
-msgstr "Normal"
+msgstr "Conforme"
#: assets/models/core.py:261
#: assets/views/core.py:853
@@ -2102,7 +2102,7 @@ msgstr "Autres arceaux / agencements"
#: assets/models/parking.py:90
msgid "Type d'arceau principal"
-msgstr "Type d'asset inconnu"
+msgstr "Type d'arceau principal"
#: assets/models/parking.py:93
msgid "Capacité estimée trottinettes"
diff --git a/streetup/assets/migrations/0016_remove_assetinspection_asset_content_type_and_more.py b/streetup/assets/migrations/0016_remove_assetinspection_asset_content_type_and_more.py
new file mode 100644
index 0000000..dbe46fb
--- /dev/null
+++ b/streetup/assets/migrations/0016_remove_assetinspection_asset_content_type_and_more.py
@@ -0,0 +1,39 @@
+# Generated by Django 6.0.7 on 2026-08-10 08:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0015_parkingspotinspection_apply_physical_counts_to_asset'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='assetinspection',
+ name='asset_content_type',
+ ),
+ migrations.RemoveField(
+ model_name='assetinspection',
+ name='inspector',
+ ),
+ migrations.RemoveField(
+ model_name='inspectiondocument',
+ name='inspection',
+ ),
+ migrations.AddField(
+ model_name='parkingspot',
+ name='drop_off_stand_count',
+ field=models.IntegerField(default=0, verbose_name="Nombre d'arceaux drop-off"),
+ ),
+ migrations.DeleteModel(
+ name='ParkingSpotInspection',
+ ),
+ migrations.DeleteModel(
+ name='AssetInspection',
+ ),
+ migrations.DeleteModel(
+ name='InspectionDocument',
+ ),
+ ]
diff --git a/streetup/assets/migrations/0017_add_bike_parking_category.py b/streetup/assets/migrations/0017_add_bike_parking_category.py
new file mode 100644
index 0000000..70af41f
--- /dev/null
+++ b/streetup/assets/migrations/0017_add_bike_parking_category.py
@@ -0,0 +1,59 @@
+from django.db import migrations
+
+
+def create_bike_parking_category(apps, schema_editor):
+ Thematic = apps.get_model('common', 'Thematic')
+ AssetCategory = apps.get_model('assets', 'AssetCategory')
+ ContentType = apps.get_model('contenttypes', 'ContentType')
+
+ try:
+ thematic = Thematic.objects.get(code='parking')
+ except Thematic.DoesNotExist:
+ # Fallback if thematic doesn't exist yet
+ thematic, _ = Thematic.objects.get_or_create(
+ code='parking',
+ defaults={
+ 'name_fr': 'Stationnement & Mobilité',
+ 'name_nl': 'Parkeren & Mobiliteit',
+ 'description': 'Thématique dédiée aux emplacements de stationnement et zones de mobilité (vélos, trottinettes, PMR, livraison)',
+ 'icon': 'bi-p-square',
+ 'color': '#0d6efd',
+ }
+ )
+
+ category, _cat_created = AssetCategory.objects.get_or_create(
+ code='bike_parking_spot',
+ defaults={
+ 'thematic': thematic,
+ 'name_fr': 'Emplacement vélos',
+ 'name_nl': 'Fietsenstalling',
+ }
+ )
+
+ # Link ContentTypes
+ try:
+ spot_ct = ContentType.objects.get(app_label='assets', model='parkingspot')
+ category.allowed_models.add(spot_ct)
+ except Exception:
+ pass
+
+ try:
+ loc_ct = ContentType.objects.get(app_label='assets', model='parkinglocation')
+ category.allowed_models.add(loc_ct)
+ except Exception:
+ pass
+
+
+def remove_bike_parking_category(apps, schema_editor):
+ AssetCategory = apps.get_model('assets', 'AssetCategory')
+ AssetCategory.objects.filter(code='bike_parking_spot').delete()
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('assets', '0016_remove_assetinspection_asset_content_type_and_more'),
+ ]
+
+ operations = [
+ migrations.RunPython(create_bike_parking_category, remove_bike_parking_category),
+ ]
diff --git a/streetup/assets/migrations/0018_remove_parkingspot_scooter_capacity.py b/streetup/assets/migrations/0018_remove_parkingspot_scooter_capacity.py
new file mode 100644
index 0000000..5d41273
--- /dev/null
+++ b/streetup/assets/migrations/0018_remove_parkingspot_scooter_capacity.py
@@ -0,0 +1,17 @@
+# Generated by Django 6.0.7 on 2026-08-10 09:37
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('assets', '0017_add_bike_parking_category'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='parkingspot',
+ name='scooter_capacity',
+ ),
+ ]
diff --git a/streetup/assets/models/parking.py b/streetup/assets/models/parking.py
index fd65233..63484a7 100644
--- a/streetup/assets/models/parking.py
+++ b/streetup/assets/models/parking.py
@@ -82,6 +82,7 @@ class ParkingSpot(AbstractGeoAsset):
# Métriques et types d'arceaux vélos
standard_stand_count = models.IntegerField(default=0, verbose_name=_("Nombre d'arceaux vélos classiques"))
cargo_stand_count = models.IntegerField(default=0, verbose_name=_("Nombre d'arceaux vélos cargo"))
+ drop_off_stand_count = models.IntegerField(default=0, verbose_name=_("Nombre d'arceaux drop-off"))
other_stand_count = models.IntegerField(default=0, verbose_name=_("Autres arceaux / agencements"))
stand_type = models.CharField(
max_length=50,
@@ -90,7 +91,6 @@ class ParkingSpot(AbstractGeoAsset):
verbose_name=_("Type d'arceau principal")
)
has_scooter_drop_zone = models.BooleanField(default=False, verbose_name=_("Drop zone trottinettes / EDPM"))
- scooter_capacity = models.IntegerField(null=True, blank=True, verbose_name=_("Capacité estimée trottinettes"))
# Métriques générales & stationnement auto/livraison/PMR
capacity = models.IntegerField(null=True, blank=True, verbose_name=_("Capacité générale (nbr véhicules)"))
@@ -124,8 +124,8 @@ class ParkingSpot(AbstractGeoAsset):
@property
def total_stand_count(self):
- """Retourne le nombre total d'arceaux (classiques + cargo + autres)."""
- return (self.standard_stand_count or 0) + (self.cargo_stand_count or 0) + (self.other_stand_count or 0)
+ """Retourne le nombre total d'arceaux (classiques + cargo + drop-off + autres)."""
+ return (self.standard_stand_count or 0) + (self.cargo_stand_count or 0) + (self.drop_off_stand_count or 0) + (self.other_stand_count or 0)
class Meta:
verbose_name = _("Emplacement de stationnement")
diff --git a/streetup/assets/templates/assets/components/asset_inspections.html b/streetup/assets/templates/assets/components/asset_inspections.html
index 1805e6c..26e7fa8 100644
--- a/streetup/assets/templates/assets/components/asset_inspections.html
+++ b/streetup/assets/templates/assets/components/asset_inspections.html
@@ -126,6 +126,12 @@
{{ leaf.verified_cargo_stand_count }}
{% endif %}
+ {% if leaf and leaf.verified_drop_off_stand_count is not None %}
+
+ {% translate "Arceaux drop-off constatés" %}
+ {{ leaf.verified_drop_off_stand_count }}
+
+ {% endif %}
{% endwith %}
diff --git a/streetup/assets/tests_parking_inspections.py b/streetup/assets/tests_parking_inspections.py
index 63aea4a..219221a 100644
--- a/streetup/assets/tests_parking_inspections.py
+++ b/streetup/assets/tests_parking_inspections.py
@@ -48,13 +48,13 @@ class ParkingSpotAndInspectionTest(TestCase):
cargo_stand_count=2,
stand_type="mixed",
has_scooter_drop_zone=True,
- scooter_capacity=10,
geom=poly
)
def test_parking_spot_creation_and_stand_counts(self):
self.assertEqual(self.spot.standard_stand_count, 3)
self.assertEqual(self.spot.cargo_stand_count, 2)
+ self.assertEqual(self.spot.drop_off_stand_count, 0)
self.assertEqual(self.spot.total_stand_count, 5)
self.assertTrue(self.spot.has_scooter_drop_zone)
self.assertTrue(self.spot.is_compliant)
@@ -91,6 +91,7 @@ class ParkingSpotAndInspectionTest(TestCase):
result_status="updated",
verified_standard_stand_count=4,
verified_cargo_stand_count=3,
+ verified_drop_off_stand_count=5,
verified_has_scooter_drop_zone=True,
verified_marking_quality="good",
apply_changes_to_asset=True,
@@ -101,7 +102,8 @@ class ParkingSpotAndInspectionTest(TestCase):
self.spot.refresh_from_db()
self.assertEqual(self.spot.standard_stand_count, 4) # Modifié avec l'opt-in
self.assertEqual(self.spot.cargo_stand_count, 3) # Modifié avec l'opt-in
- self.assertEqual(self.spot.total_stand_count, 7)
+ self.assertEqual(self.spot.drop_off_stand_count, 5) # Modifié avec l'opt-in
+ self.assertEqual(self.spot.total_stand_count, 12)
self.assertEqual(self.spot.validation_status, "validated")
self.assertEqual(self.spot.inspection_status, "updated")
self.assertIsNotNone(self.spot.last_inspection_date)
@@ -134,6 +136,7 @@ class ParkingSpotAndInspectionTest(TestCase):
self.assertEqual(feat["properties"]["code"], "SPOT-VELO-01")
self.assertEqual(feat["properties"]["standard_stand_count"], 3)
self.assertEqual(feat["properties"]["cargo_stand_count"], 2)
+ self.assertEqual(feat["properties"]["drop_off_stand_count"], 0)
self.assertEqual(feat["properties"]["total_stand_count"], 5)
self.assertTrue(feat["properties"]["has_scooter_drop_zone"])
diff --git a/streetup/assets/views/parking.py b/streetup/assets/views/parking.py
index 1c6c42b..695d90f 100644
--- a/streetup/assets/views/parking.py
+++ b/streetup/assets/views/parking.py
@@ -174,11 +174,11 @@ def parking_assets_geojson(request):
"spot_type_display": spot.get_spot_type_display(),
"standard_stand_count": spot.standard_stand_count,
"cargo_stand_count": spot.cargo_stand_count,
+ "drop_off_stand_count": spot.drop_off_stand_count,
"total_stand_count": spot.total_stand_count,
"stand_type": spot.stand_type,
"stand_type_display": spot.get_stand_type_display(),
"has_scooter_drop_zone": spot.has_scooter_drop_zone,
- "scooter_capacity": spot.scooter_capacity,
"capacity": spot.capacity,
"marking_quality": spot.marking_quality,
"is_compliant": spot.is_compliant,
diff --git a/streetup/inspections/admin.py b/streetup/inspections/admin.py
index dfd0509..5445602 100644
--- a/streetup/inspections/admin.py
+++ b/streetup/inspections/admin.py
@@ -32,7 +32,7 @@ class ParkingSpotInspectionAdmin(admin.ModelAdmin):
list_display = (
"id", "asset", "inspector", "inspection_date",
"result_status", "verified_standard_stand_count",
- "verified_cargo_stand_count", "verified_has_scooter_drop_zone",
+ "verified_cargo_stand_count", "verified_drop_off_stand_count", "verified_has_scooter_drop_zone",
"apply_changes_to_asset"
)
list_filter = ("result_status", "apply_changes_to_asset", "inspection_date")
diff --git a/streetup/inspections/migrations/0003_parkingspotinspection_verified_drop_off_stand_count_and_more.py b/streetup/inspections/migrations/0003_parkingspotinspection_verified_drop_off_stand_count_and_more.py
new file mode 100644
index 0000000..9b3e184
--- /dev/null
+++ b/streetup/inspections/migrations/0003_parkingspotinspection_verified_drop_off_stand_count_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 6.0.7 on 2026-08-10 08:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('inspections', '0002_migrate_existing_inspections'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='parkingspotinspection',
+ name='verified_drop_off_stand_count',
+ field=models.IntegerField(blank=True, null=True, verbose_name="Nombre d'arceaux drop-off constaté"),
+ ),
+ migrations.AlterField(
+ model_name='inspection',
+ name='result_status',
+ field=models.CharField(choices=[('compliant', 'Conforme'), ('non_compliant', 'Non conforme / Anomalie'), ('damaged', 'Endommagé'), ('missing', 'Absent / Retiré')], default='compliant', max_length=50, verbose_name='Résultat du contrôle'),
+ ),
+ ]
diff --git a/streetup/inspections/models.py b/streetup/inspections/models.py
index 93fdca7..a6f8582 100644
--- a/streetup/inspections/models.py
+++ b/streetup/inspections/models.py
@@ -17,7 +17,7 @@ INSPECTION_RESULT_CONFIG = {
'color_hex': '#198754',
},
'non_compliant': {
- 'label': _('Non conforme / Anomalie'),
+ 'label': _('Non conforme'),
'badge_class': 'bg-warning text-dark',
'btn_class': 'btn-outline-warning text-dark',
'icon': 'bi-exclamation-triangle-fill',
@@ -331,6 +331,11 @@ class ParkingSpotInspection(Inspection):
blank=True,
verbose_name=_("Nombre d'arceaux cargo constaté")
)
+ verified_drop_off_stand_count = models.IntegerField(
+ null=True,
+ blank=True,
+ verbose_name=_("Nombre d'arceaux drop-off constaté")
+ )
verified_stand_type = models.CharField(
max_length=50,
null=True,
@@ -375,6 +380,9 @@ class ParkingSpotInspection(Inspection):
if self.verified_cargo_stand_count is not None and hasattr(target_asset, 'cargo_stand_count'):
target_asset.cargo_stand_count = self.verified_cargo_stand_count
modified = True
+ if self.verified_drop_off_stand_count is not None and hasattr(target_asset, 'drop_off_stand_count'):
+ target_asset.drop_off_stand_count = self.verified_drop_off_stand_count
+ modified = True
if self.verified_has_scooter_drop_zone is not None and hasattr(target_asset, 'has_scooter_drop_zone'):
target_asset.has_scooter_drop_zone = self.verified_has_scooter_drop_zone
modified = True
diff --git a/streetup/inspections/templates/inspections/components/asset_fields/_parkingspot.html b/streetup/inspections/templates/inspections/components/asset_fields/_parkingspot.html
index df3b766..e3d4bba 100644
--- a/streetup/inspections/templates/inspections/components/asset_fields/_parkingspot.html
+++ b/streetup/inspections/templates/inspections/components/asset_fields/_parkingspot.html
@@ -5,24 +5,87 @@
{% translate "Contrôles spécifiques stationnement" %}
-
-
+
+
{% translate "Arceaux classiques" %}
-
+
-
-
-
{% translate "Qualité marquage" %}
-
- {% translate "-- Sélectionner --" %}
- {% translate "Bon état" %}
- {% translate "Moyen" %}
- {% translate "Dégradé" %}
- {% translate "Absent / Effacé" %}
-
+
+
+
+
{% translate "Drop zone trottinettes" %}
+
-
+
+
+
+
{% translate "Qualité du marquage au sol" %}
+
+
+
+ {% if show_apply_physical_counts %}
+
+
+
+ {% translate "Mettre à jour la capacité / les comptages de l'emplacement vélos" %}
+
+
+ {% endif %}
+
\ No newline at end of file
diff --git a/streetup/inspections/templates/inspections/components/inspection_form_modal.html b/streetup/inspections/templates/inspections/components/inspection_form_modal.html
index 4ec8594..51bcb87 100644
--- a/streetup/inspections/templates/inspections/components/inspection_form_modal.html
+++ b/streetup/inspections/templates/inspections/components/inspection_form_modal.html
@@ -87,6 +87,7 @@
{% endif %}
+ {% if show_create_intervention %}
@@ -94,6 +95,7 @@
{% translate "Créer automatiquement une intervention en cas d'anomalie / dégât" %}
+ {% endif %}
diff --git a/streetup/inspections/templates/inspections/inspections_list.html b/streetup/inspections/templates/inspections/inspections_list.html
index f395635..b5c32a2 100644
--- a/streetup/inspections/templates/inspections/inspections_list.html
+++ b/streetup/inspections/templates/inspections/inspections_list.html
@@ -217,6 +217,12 @@
{{ leaf.verified_cargo_stand_count }}
{% endif %}
+ {% if leaf.verified_drop_off_stand_count is not None %}
+
+ {% translate "Arceaux drop-off constatés" %}
+ {{ leaf.verified_drop_off_stand_count }}
+
+ {% endif %}
{% if leaf.verified_marking_quality %}
{% translate "Qualité du marquage" %}
diff --git a/streetup/inspections/views.py b/streetup/inspections/views.py
index 27541ad..f37419d 100644
--- a/streetup/inspections/views.py
+++ b/streetup/inspections/views.py
@@ -299,12 +299,22 @@ def create_inspection_api(request):
inspection.verified_standard_stand_count = int(request.POST['verified_standard_stand_count'])
if 'verified_cargo_stand_count' in request.POST and request.POST['verified_cargo_stand_count'] != '':
inspection.verified_cargo_stand_count = int(request.POST['verified_cargo_stand_count'])
+ if 'verified_drop_off_stand_count' in request.POST and request.POST['verified_drop_off_stand_count'] != '':
+ inspection.verified_drop_off_stand_count = int(request.POST['verified_drop_off_stand_count'])
if 'verified_stand_type' in request.POST and request.POST['verified_stand_type'] != '':
inspection.verified_stand_type = request.POST['verified_stand_type']
if 'verified_has_scooter_drop_zone' in request.POST:
- inspection.verified_has_scooter_drop_zone = (request.POST['verified_has_scooter_drop_zone'] == 'true')
+ val = request.POST['verified_has_scooter_drop_zone']
+ if val == 'true':
+ inspection.verified_has_scooter_drop_zone = True
+ elif val == 'false':
+ inspection.verified_has_scooter_drop_zone = False
+ elif val == '':
+ inspection.verified_has_scooter_drop_zone = None
if 'verified_marking_quality' in request.POST and request.POST['verified_marking_quality'] != '':
inspection.verified_marking_quality = request.POST['verified_marking_quality']
+
+ inspection.apply_physical_counts_to_asset = (request.POST.get('apply_physical_counts_to_asset') == 'true')
inspection.save()
else:
diff --git a/streetup/interventions/locale/fr/LC_MESSAGES/django.mo b/streetup/interventions/locale/fr/LC_MESSAGES/django.mo
index 248183c..2737a68 100644
Binary files a/streetup/interventions/locale/fr/LC_MESSAGES/django.mo and b/streetup/interventions/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/streetup/interventions/locale/fr/LC_MESSAGES/django.po b/streetup/interventions/locale/fr/LC_MESSAGES/django.po
index e0ac1a5..f261a04 100644
--- a/streetup/interventions/locale/fr/LC_MESSAGES/django.po
+++ b/streetup/interventions/locale/fr/LC_MESSAGES/django.po
@@ -4138,7 +4138,7 @@ msgstr "Quel est l'état de la végétation ?"
#: interventions/templates/interventions/guided_operations/inspection_rain_water.html:2665
#: interventions/templates/interventions/guided_operations/inspection_rain_water.html:2843
msgid "Bon"
-msgstr "Non"
+msgstr "Bon"
#: interventions/templates/interventions/guided_operations/inspection_rain_water.html:488
#: interventions/templates/interventions/guided_operations/inspection_rain_water.html:1183
diff --git a/streetup/sign/migrations/0002_signpanel_funding_program_and_more.py b/streetup/sign/migrations/0002_signpanel_funding_program_and_more.py
new file mode 100644
index 0000000..b087ad4
--- /dev/null
+++ b/streetup/sign/migrations/0002_signpanel_funding_program_and_more.py
@@ -0,0 +1,33 @@
+# Generated by Django 6.0.7 on 2026-08-10 08:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('sign', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='signpanel',
+ name='funding_program',
+ field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Programme de financement / Projet'),
+ ),
+ migrations.AddField(
+ model_name='signpanel',
+ name='inspection_status',
+ field=models.CharField(blank=True, choices=[('to_inspect', 'À inspecter'), ('compliant', 'Conforme'), ('non_compliant', 'Non conforme'), ('updated', 'Mis à jour'), ('unknown', 'Non inspecté')], default='unknown', max_length=50, null=True, verbose_name="Statut d'inspection"),
+ ),
+ migrations.AddField(
+ model_name='signpole',
+ name='funding_program',
+ field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Programme de financement / Projet'),
+ ),
+ migrations.AddField(
+ model_name='signpole',
+ name='inspection_status',
+ field=models.CharField(blank=True, choices=[('to_inspect', 'À inspecter'), ('compliant', 'Conforme'), ('non_compliant', 'Non conforme'), ('updated', 'Mis à jour'), ('unknown', 'Non inspecté')], default='unknown', max_length=50, null=True, verbose_name="Statut d'inspection"),
+ ),
+ ]