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" %} -
    -
    +
    +
    - +
    -
    +
    - + +
    +
    + +
    -
    - - + + +
    + +
    +
    + + +
    +
    + + +
    +
    -
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + + {% if show_apply_physical_counts %} +
    + + +
    + {% 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"), + ), + ]