feat: implement generic inspection system for assets and interventions
This commit is contained in:
parent
bc08402f34
commit
93f568a9ac
20 changed files with 297 additions and 28 deletions
|
|
@ -694,7 +694,7 @@ class ParkingLocationAdmin(AssetGISAdmin):
|
||||||
class ParkingSpotAdmin(AssetGISAdmin):
|
class ParkingSpotAdmin(AssetGISAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"code", "name_fr", "spot_type", "stand_type",
|
"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"
|
"has_scooter_drop_zone", "is_compliant", "inspection_status", "status"
|
||||||
)
|
)
|
||||||
list_filter = (
|
list_filter = (
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -972,7 +972,7 @@ msgstr "À inspecter"
|
||||||
#: assets/models/core.py:260
|
#: assets/models/core.py:260
|
||||||
#: assets/views/core.py:852
|
#: assets/views/core.py:852
|
||||||
msgid "Conforme"
|
msgid "Conforme"
|
||||||
msgstr "Normal"
|
msgstr "Conforme"
|
||||||
|
|
||||||
#: assets/models/core.py:261
|
#: assets/models/core.py:261
|
||||||
#: assets/views/core.py:853
|
#: assets/views/core.py:853
|
||||||
|
|
@ -2102,7 +2102,7 @@ msgstr "Autres arceaux / agencements"
|
||||||
|
|
||||||
#: assets/models/parking.py:90
|
#: assets/models/parking.py:90
|
||||||
msgid "Type d'arceau principal"
|
msgid "Type d'arceau principal"
|
||||||
msgstr "Type d'asset inconnu"
|
msgstr "Type d'arceau principal"
|
||||||
|
|
||||||
#: assets/models/parking.py:93
|
#: assets/models/parking.py:93
|
||||||
msgid "Capacité estimée trottinettes"
|
msgid "Capacité estimée trottinettes"
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
),
|
||||||
|
]
|
||||||
59
streetup/assets/migrations/0017_add_bike_parking_category.py
Normal file
59
streetup/assets/migrations/0017_add_bike_parking_category.py
Normal file
|
|
@ -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),
|
||||||
|
]
|
||||||
|
|
@ -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',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -82,6 +82,7 @@ class ParkingSpot(AbstractGeoAsset):
|
||||||
# Métriques et types d'arceaux vélos
|
# Métriques et types d'arceaux vélos
|
||||||
standard_stand_count = models.IntegerField(default=0, verbose_name=_("Nombre d'arceaux vélos classiques"))
|
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"))
|
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"))
|
other_stand_count = models.IntegerField(default=0, verbose_name=_("Autres arceaux / agencements"))
|
||||||
stand_type = models.CharField(
|
stand_type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
|
|
@ -90,7 +91,6 @@ class ParkingSpot(AbstractGeoAsset):
|
||||||
verbose_name=_("Type d'arceau principal")
|
verbose_name=_("Type d'arceau principal")
|
||||||
)
|
)
|
||||||
has_scooter_drop_zone = models.BooleanField(default=False, verbose_name=_("Drop zone trottinettes / EDPM"))
|
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
|
# 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)"))
|
capacity = models.IntegerField(null=True, blank=True, verbose_name=_("Capacité générale (nbr véhicules)"))
|
||||||
|
|
@ -124,8 +124,8 @@ class ParkingSpot(AbstractGeoAsset):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total_stand_count(self):
|
def total_stand_count(self):
|
||||||
"""Retourne le nombre total d'arceaux (classiques + cargo + autres)."""
|
"""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.other_stand_count or 0)
|
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:
|
class Meta:
|
||||||
verbose_name = _("Emplacement de stationnement")
|
verbose_name = _("Emplacement de stationnement")
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,12 @@
|
||||||
<span class="badge bg-info text-dark rounded-pill fs-6">{{ leaf.verified_cargo_stand_count }}</span>
|
<span class="badge bg-info text-dark rounded-pill fs-6">{{ leaf.verified_cargo_stand_count }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if leaf and leaf.verified_drop_off_stand_count is not None %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
|
||||||
|
<span class="text-muted"><i class="bi bi-scooter me-1 text-warning"></i>{% translate "Arceaux drop-off constatés" %}</span>
|
||||||
|
<span class="badge bg-warning text-dark rounded-pill fs-6">{{ leaf.verified_drop_off_stand_count }}</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,13 +48,13 @@ class ParkingSpotAndInspectionTest(TestCase):
|
||||||
cargo_stand_count=2,
|
cargo_stand_count=2,
|
||||||
stand_type="mixed",
|
stand_type="mixed",
|
||||||
has_scooter_drop_zone=True,
|
has_scooter_drop_zone=True,
|
||||||
scooter_capacity=10,
|
|
||||||
geom=poly
|
geom=poly
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_parking_spot_creation_and_stand_counts(self):
|
def test_parking_spot_creation_and_stand_counts(self):
|
||||||
self.assertEqual(self.spot.standard_stand_count, 3)
|
self.assertEqual(self.spot.standard_stand_count, 3)
|
||||||
self.assertEqual(self.spot.cargo_stand_count, 2)
|
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.assertEqual(self.spot.total_stand_count, 5)
|
||||||
self.assertTrue(self.spot.has_scooter_drop_zone)
|
self.assertTrue(self.spot.has_scooter_drop_zone)
|
||||||
self.assertTrue(self.spot.is_compliant)
|
self.assertTrue(self.spot.is_compliant)
|
||||||
|
|
@ -91,6 +91,7 @@ class ParkingSpotAndInspectionTest(TestCase):
|
||||||
result_status="updated",
|
result_status="updated",
|
||||||
verified_standard_stand_count=4,
|
verified_standard_stand_count=4,
|
||||||
verified_cargo_stand_count=3,
|
verified_cargo_stand_count=3,
|
||||||
|
verified_drop_off_stand_count=5,
|
||||||
verified_has_scooter_drop_zone=True,
|
verified_has_scooter_drop_zone=True,
|
||||||
verified_marking_quality="good",
|
verified_marking_quality="good",
|
||||||
apply_changes_to_asset=True,
|
apply_changes_to_asset=True,
|
||||||
|
|
@ -101,7 +102,8 @@ class ParkingSpotAndInspectionTest(TestCase):
|
||||||
self.spot.refresh_from_db()
|
self.spot.refresh_from_db()
|
||||||
self.assertEqual(self.spot.standard_stand_count, 4) # Modifié avec l'opt-in
|
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.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.validation_status, "validated")
|
||||||
self.assertEqual(self.spot.inspection_status, "updated")
|
self.assertEqual(self.spot.inspection_status, "updated")
|
||||||
self.assertIsNotNone(self.spot.last_inspection_date)
|
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"]["code"], "SPOT-VELO-01")
|
||||||
self.assertEqual(feat["properties"]["standard_stand_count"], 3)
|
self.assertEqual(feat["properties"]["standard_stand_count"], 3)
|
||||||
self.assertEqual(feat["properties"]["cargo_stand_count"], 2)
|
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.assertEqual(feat["properties"]["total_stand_count"], 5)
|
||||||
self.assertTrue(feat["properties"]["has_scooter_drop_zone"])
|
self.assertTrue(feat["properties"]["has_scooter_drop_zone"])
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,11 +174,11 @@ def parking_assets_geojson(request):
|
||||||
"spot_type_display": spot.get_spot_type_display(),
|
"spot_type_display": spot.get_spot_type_display(),
|
||||||
"standard_stand_count": spot.standard_stand_count,
|
"standard_stand_count": spot.standard_stand_count,
|
||||||
"cargo_stand_count": spot.cargo_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,
|
"total_stand_count": spot.total_stand_count,
|
||||||
"stand_type": spot.stand_type,
|
"stand_type": spot.stand_type,
|
||||||
"stand_type_display": spot.get_stand_type_display(),
|
"stand_type_display": spot.get_stand_type_display(),
|
||||||
"has_scooter_drop_zone": spot.has_scooter_drop_zone,
|
"has_scooter_drop_zone": spot.has_scooter_drop_zone,
|
||||||
"scooter_capacity": spot.scooter_capacity,
|
|
||||||
"capacity": spot.capacity,
|
"capacity": spot.capacity,
|
||||||
"marking_quality": spot.marking_quality,
|
"marking_quality": spot.marking_quality,
|
||||||
"is_compliant": spot.is_compliant,
|
"is_compliant": spot.is_compliant,
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class ParkingSpotInspectionAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
"id", "asset", "inspector", "inspection_date",
|
"id", "asset", "inspector", "inspection_date",
|
||||||
"result_status", "verified_standard_stand_count",
|
"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"
|
"apply_changes_to_asset"
|
||||||
)
|
)
|
||||||
list_filter = ("result_status", "apply_changes_to_asset", "inspection_date")
|
list_filter = ("result_status", "apply_changes_to_asset", "inspection_date")
|
||||||
|
|
|
||||||
|
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -17,7 +17,7 @@ INSPECTION_RESULT_CONFIG = {
|
||||||
'color_hex': '#198754',
|
'color_hex': '#198754',
|
||||||
},
|
},
|
||||||
'non_compliant': {
|
'non_compliant': {
|
||||||
'label': _('Non conforme / Anomalie'),
|
'label': _('Non conforme'),
|
||||||
'badge_class': 'bg-warning text-dark',
|
'badge_class': 'bg-warning text-dark',
|
||||||
'btn_class': 'btn-outline-warning text-dark',
|
'btn_class': 'btn-outline-warning text-dark',
|
||||||
'icon': 'bi-exclamation-triangle-fill',
|
'icon': 'bi-exclamation-triangle-fill',
|
||||||
|
|
@ -331,6 +331,11 @@ class ParkingSpotInspection(Inspection):
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_("Nombre d'arceaux cargo constaté")
|
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(
|
verified_stand_type = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
null=True,
|
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'):
|
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
|
target_asset.cargo_stand_count = self.verified_cargo_stand_count
|
||||||
modified = True
|
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'):
|
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
|
target_asset.has_scooter_drop_zone = self.verified_has_scooter_drop_zone
|
||||||
modified = True
|
modified = True
|
||||||
|
|
|
||||||
|
|
@ -5,24 +5,87 @@
|
||||||
<span class="small fw-bold text-secondary mb-2 d-block">
|
<span class="small fw-bold text-secondary mb-2 d-block">
|
||||||
<i class="bi bi-p-square me-1"></i>{% translate "Contrôles spécifiques stationnement" %}
|
<i class="bi bi-p-square me-1"></i>{% translate "Contrôles spécifiques stationnement" %}
|
||||||
</span>
|
</span>
|
||||||
<div class="row g-2 mb-2">
|
<div class="row g-2 mb-3">
|
||||||
<div class="col-6">
|
<div class="col-4">
|
||||||
<label class="form-label small mb-1">{% translate "Arceaux classiques" %}</label>
|
<label class="form-label small mb-1">{% translate "Arceaux classiques" %}</label>
|
||||||
<input type="number" name="verified_standard_stand_count" id="insp_std_stands" class="form-control form-control-sm" min="0" placeholder="{% translate 'Saisir le nombre constaté' %}">
|
<input type="number" name="verified_standard_stand_count" id="insp_std_stands"
|
||||||
|
class="form-control form-control-sm" min="0">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-4">
|
||||||
<label class="form-label small mb-1">{% translate "Arceaux cargo" %}</label>
|
<label class="form-label small mb-1">{% translate "Arceaux cargo" %}</label>
|
||||||
<input type="number" name="verified_cargo_stand_count" id="insp_cargo_stands" class="form-control form-control-sm" min="0" placeholder="{% translate 'Saisir le nombre constaté' %}">
|
<input type="number" name="verified_cargo_stand_count" id="insp_cargo_stands"
|
||||||
|
class="form-control form-control-sm" min="0">
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<label class="form-label small mb-1">{% translate "Arceaux drop-off" %}</label>
|
||||||
|
<input type="number" name="verified_drop_off_stand_count" id="insp_drop_off_stands"
|
||||||
|
class="form-control form-control-sm" min="0">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-1">
|
|
||||||
<label class="form-label small mb-1">{% translate "Qualité marquage" %}</label>
|
<!-- Drop zone trottinettes -->
|
||||||
<select name="verified_marking_quality" id="insp_marking_quality" class="form-select form-select-sm">
|
<div class="mb-3">
|
||||||
<option value="">{% translate "-- Sélectionner --" %}</option>
|
<label class="form-label small fw-bold text-muted mb-1">{% translate "Drop zone trottinettes" %}</label>
|
||||||
<option value="good">{% translate "Bon état" %}</option>
|
<div class="row g-2">
|
||||||
<option value="medium">{% translate "Moyen" %}</option>
|
<div class="col-6">
|
||||||
<option value="damaged">{% translate "Dégradé" %}</option>
|
<input type="radio" class="btn-check" name="verified_has_scooter_drop_zone" id="dz_yes" value="true">
|
||||||
<option value="missing">{% translate "Absent / Effacé" %}</option>
|
<label class="btn btn-outline-secondary btn-sm w-100"
|
||||||
</select>
|
style="padding: 0.5rem 0.2rem; border-radius: 10px;" for="dz_yes">
|
||||||
|
<i class="bi bi-check-circle me-1"></i>{% translate "Oui" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input type="radio" class="btn-check" name="verified_has_scooter_drop_zone" id="dz_no" value="false">
|
||||||
|
<label class="btn btn-outline-secondary btn-sm w-100"
|
||||||
|
style="padding: 0.5rem 0.2rem; border-radius: 10px;" for="dz_no">
|
||||||
|
<i class="bi bi-x-circle me-1"></i>{% translate "Non" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<!-- Qualité marquage -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label small fw-bold text-muted mb-1">{% translate "Qualité du marquage au sol" %}</label>
|
||||||
|
<div class="row g-1">
|
||||||
|
<div class="col-6">
|
||||||
|
<input type="radio" class="btn-check" name="verified_marking_quality" id="mq_good" value="good">
|
||||||
|
<label class="btn btn-outline-success btn-sm w-100 text-truncate"
|
||||||
|
style="padding: 0.5rem 0.2rem; border-radius: 10px;" for="mq_good">
|
||||||
|
<i class="bi bi-emoji-smile me-1"></i>{% translate "Bon état" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<input type="radio" class="btn-check" name="verified_marking_quality" id="mq_medium" value="medium">
|
||||||
|
<label class="btn btn-outline-warning btn-sm w-100 text-truncate"
|
||||||
|
style="padding: 0.5rem 0.2rem; border-radius: 10px;" for="mq_medium">
|
||||||
|
<i class="bi bi-emoji-neutral me-1"></i>{% translate "Moyen" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 mt-2">
|
||||||
|
<input type="radio" class="btn-check" name="verified_marking_quality" id="mq_damaged" value="damaged">
|
||||||
|
<label class="btn btn-outline-danger btn-sm w-100 text-truncate"
|
||||||
|
style="padding: 0.5rem 0.2rem; border-radius: 10px;" for="mq_damaged">
|
||||||
|
<i class="bi bi-emoji-frown me-1"></i>{% translate "Dégradé" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 mt-2">
|
||||||
|
<input type="radio" class="btn-check" name="verified_marking_quality" id="mq_missing" value="missing">
|
||||||
|
<label class="btn btn-outline-dark btn-sm w-100 text-truncate"
|
||||||
|
style="padding: 0.5rem 0.2rem; border-radius: 10px;" for="mq_missing">
|
||||||
|
<i class="bi bi-x-circle me-1"></i>{% translate "Absent" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if show_apply_physical_counts %}
|
||||||
|
<div class="form-check form-switch mt-2 bg-light p-2 rounded ps-4">
|
||||||
|
<input class="form-check-input ms-0 me-2" type="checkbox" name="apply_physical_counts_to_asset" value="true"
|
||||||
|
id="insp_apply_physical_counts" checked>
|
||||||
|
<label class="form-check-label small fw-semibold" for="insp_apply_physical_counts">
|
||||||
|
{% translate "Mettre à jour la capacité / les comptages de l'emplacement vélos" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
@ -87,6 +87,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if show_create_intervention %}
|
||||||
<!-- Option Création automatique d'une intervention -->
|
<!-- Option Création automatique d'une intervention -->
|
||||||
<div class="form-check form-switch mb-3 bg-light p-2 rounded ps-4" id="createInterventionSwitchContainer">
|
<div class="form-check form-switch mb-3 bg-light p-2 rounded ps-4" id="createInterventionSwitchContainer">
|
||||||
<input class="form-check-input ms-0 me-2" type="checkbox" name="create_intervention" value="true" id="createInterventionSwitch">
|
<input class="form-check-input ms-0 me-2" type="checkbox" name="create_intervention" value="true" id="createInterventionSwitch">
|
||||||
|
|
@ -94,6 +95,7 @@
|
||||||
{% translate "Créer automatiquement une intervention en cas d'anomalie / dégât" %}
|
{% translate "Créer automatiquement une intervention en cas d'anomalie / dégât" %}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Remarques -->
|
<!-- Remarques -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,12 @@
|
||||||
<span class="badge bg-info text-dark rounded-pill fs-6">{{ leaf.verified_cargo_stand_count }}</span>
|
<span class="badge bg-info text-dark rounded-pill fs-6">{{ leaf.verified_cargo_stand_count }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if leaf.verified_drop_off_stand_count is not None %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
|
||||||
|
<span class="text-muted"><i class="bi bi-scooter me-1 text-warning"></i>{% translate "Arceaux drop-off constatés" %}</span>
|
||||||
|
<span class="badge bg-warning text-dark rounded-pill fs-6">{{ leaf.verified_drop_off_stand_count }}</span>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% if leaf.verified_marking_quality %}
|
{% if leaf.verified_marking_quality %}
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
|
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
|
||||||
<span class="text-muted"><i class="bi bi-paint-bucket me-1"></i>{% translate "Qualité du marquage" %}</span>
|
<span class="text-muted"><i class="bi bi-paint-bucket me-1"></i>{% translate "Qualité du marquage" %}</span>
|
||||||
|
|
|
||||||
|
|
@ -299,12 +299,22 @@ def create_inspection_api(request):
|
||||||
inspection.verified_standard_stand_count = int(request.POST['verified_standard_stand_count'])
|
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'] != '':
|
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'])
|
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'] != '':
|
if 'verified_stand_type' in request.POST and request.POST['verified_stand_type'] != '':
|
||||||
inspection.verified_stand_type = request.POST['verified_stand_type']
|
inspection.verified_stand_type = request.POST['verified_stand_type']
|
||||||
if 'verified_has_scooter_drop_zone' in request.POST:
|
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'] != '':
|
if 'verified_marking_quality' in request.POST and request.POST['verified_marking_quality'] != '':
|
||||||
inspection.verified_marking_quality = 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()
|
inspection.save()
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -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:2665
|
||||||
#: interventions/templates/interventions/guided_operations/inspection_rain_water.html:2843
|
#: interventions/templates/interventions/guided_operations/inspection_rain_water.html:2843
|
||||||
msgid "Bon"
|
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:488
|
||||||
#: interventions/templates/interventions/guided_operations/inspection_rain_water.html:1183
|
#: interventions/templates/interventions/guided_operations/inspection_rain_water.html:1183
|
||||||
|
|
|
||||||
|
|
@ -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"),
|
||||||
|
),
|
||||||
|
]
|
||||||
Loading…
Reference in a new issue