diff --git a/loko/inspections/admin.py b/loko/inspections/admin.py index 68a764e..ecad04a 100644 --- a/loko/inspections/admin.py +++ b/loko/inspections/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.contrib.gis import admin as gis_admin -from .models import Inspection, InspectionZone, ParkingSpotInspection, GiepInspection, InspectionDocument +from .models import Inspection, InspectionZone, ParkingSpotInspection, GiepInspection, HotBoxInspection, InspectionDocument @admin.register(InspectionZone) @@ -83,6 +83,23 @@ class GiepInspectionAdmin(admin.ModelAdmin): actions = [make_cancelled, make_compliant] +@admin.register(HotBoxInspection) +class HotBoxInspectionAdmin(admin.ModelAdmin): + list_display = ( + "id", "asset", "inspector", "inspection_date", "result_status", + "joint_level", "joint_sealing", "cracking", "water_drainage", + "checked_intervention" + ) + list_filter = ( + "result_status", "joint_level", "joint_sealing", + "cracking", "water_drainage", "inspection_date" + ) + search_fields = ("notes", "inspector__username", "inspector__first_name", "inspector__last_name", "checked_intervention__code") + raw_id_fields = ("inspector", "mission_intervention", "checked_intervention", "zone") + inlines = [InspectionDocumentInline] + actions = [make_cancelled, make_compliant] + + @admin.register(InspectionDocument) class InspectionDocumentAdmin(admin.ModelAdmin): list_display = ("id", "inspection", "field_key", "filename", "created_at") diff --git a/loko/inspections/migrations/0010_hotboxinspection.py b/loko/inspections/migrations/0010_hotboxinspection.py new file mode 100644 index 0000000..8bca0fb --- /dev/null +++ b/loko/inspections/migrations/0010_hotboxinspection.py @@ -0,0 +1,29 @@ +# Generated by Django 6.0.7 on 2026-09-18 09:24 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inspections', '0009_inspection_non_compliance_notified_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='HotBoxInspection', + fields=[ + ('inspection_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='inspections.inspection')), + ('joint_level', models.CharField(blank=True, choices=[('flush', 'Bien à niveau / Arasé'), ('slight_bump_hollow', 'Léger décalage / Creux ou bosse'), ('step', 'Ressaut important (> 1-2 cm)')], default='flush', max_length=50, null=True, verbose_name='Planéité / Arasement')), + ('joint_sealing', models.CharField(blank=True, choices=[('good', 'Joints bien fermés / scellés'), ('cracked', 'Joints fissurés ou abîmés'), ('open', 'Joints ouverts ou décollés')], default='good', max_length=50, null=True, verbose_name='Joints de raccordement')), + ('cracking', models.CharField(blank=True, choices=[('none', 'Aucune fissure'), ('minor', 'Petites fissures / Faïençage'), ('major', 'Grosses fissures / Fissures ouvertes')], default='none', max_length=50, null=True, verbose_name='Fissuration')), + ('water_drainage', models.CharField(blank=True, choices=[('good', 'Bon écoulement / Pas de flaque'), ('stagnation', "Eau stagnante / Flaque d'eau")], default='good', max_length=50, null=True, verbose_name='Évacuation des eaux pluviales')), + ], + options={ + 'verbose_name': 'Inspection de réparation Hot Box', + 'verbose_name_plural': 'Inspections de réparations Hot Box', + }, + bases=('inspections.inspection',), + ), + ] diff --git a/loko/inspections/models.py b/loko/inspections/models.py index efc0b7d..69c76df 100644 --- a/loko/inspections/models.py +++ b/loko/inspections/models.py @@ -567,15 +567,39 @@ class Inspection(models.Model): return th.icon return 'bi-geo-alt' + @property + def is_hotbox_related(self): + """Détermine si l'inspection concerne une réparation Hot Box (asphalte à chaud).""" + if hasattr(self, 'hotboxinspection'): + return True + interv = self.related_intervention + if interv and getattr(interv, 'sub_type', None): + st = interv.sub_type + code = (st.code or '').lower() + name = (st.name_fr or '').lower() + if any(k in code or k in name for k in ('hotbox', 'hot_box', 'hot box')): + return True + if self.nature == 'post_intervention' and 'hot box' in (self.notes or '').lower(): + return True + return False + @property def detailed_inspection(self): - """Downcast à la sous-classe spécialisée si disponible (ex: ParkingSpotInspection, GiepInspection, SignPanelInspection).""" + """Downcast à la sous-classe spécialisée si disponible (ex: ParkingSpotInspection, GiepInspection, SignPanelInspection, HotBoxInspection).""" if hasattr(self, 'parkingspotinspection'): return self.parkingspotinspection if hasattr(self, 'giepinspection'): return self.giepinspection if hasattr(self, 'signpanelinspection'): return self.signpanelinspection + if hasattr(self, 'hotboxinspection'): + return self.hotboxinspection + if self.is_hotbox_related and self.pk: + try: + hb, _ = HotBoxInspection.objects.get_or_create(inspection_ptr=self) + return hb + except Exception: + pass return self @property @@ -615,6 +639,17 @@ class Inspection(models.Model): if getattr(leaf, 'date_derniere_pluie', None) and leaf.date_derniere_pluie != 'null': parts.append(str(_("Pluie : %s") % leaf.get_date_derniere_pluie_display())) + # Hot Box summary + if hasattr(leaf, 'joint_level') and hasattr(leaf, 'joint_sealing'): + if leaf.joint_level: + parts.append(f"{_('Planéité')} : {leaf.get_joint_level_display()}") + if leaf.joint_sealing: + parts.append(f"{_('Joints')} : {leaf.get_joint_sealing_display()}") + if leaf.cracking: + parts.append(f"{_('Fissuration')} : {leaf.get_cracking_display()}") + if leaf.water_drainage: + parts.append(f"{_('Évacuation')} : {leaf.get_water_drainage_display()}") + if leaf.verified_obsolescence: parts.append(str(_("Obsolescence : %s") % leaf.verified_obsolescence)) @@ -1010,6 +1045,91 @@ class SignPanelInspection(Inspection): target_asset.save() +class HotBoxInspection(Inspection): + """ + Spécialisation de l'inspection pour les réparations d'enrobé à chaud (Hot Box). + Formulaire visuel simplifié adapté aux observateurs non experts : + - Planéité (Arasement) + - Joints (Raccordement) + - Fissuration + - Évacuation des eaux pluviales + """ + JOINT_LEVEL_CHOICES = ( + ('flush', _("Bien à niveau / Arasé")), + ('slight_bump_hollow', _("Léger décalage / Creux ou bosse")), + ('step', _("Ressaut important (> 1-2 cm)")), + ) + joint_level = models.CharField( + max_length=50, + choices=JOINT_LEVEL_CHOICES, + default='flush', + null=True, + blank=True, + verbose_name=_("Planéité / Arasement") + ) + + JOINT_SEALING_CHOICES = ( + ('good', _("Joints bien fermés / scellés")), + ('cracked', _("Joints fissurés ou abîmés")), + ('open', _("Joints ouverts ou décollés")), + ) + joint_sealing = models.CharField( + max_length=50, + choices=JOINT_SEALING_CHOICES, + default='good', + null=True, + blank=True, + verbose_name=_("Joints de raccordement") + ) + + CRACKING_CHOICES = ( + ('none', _("Aucune fissure")), + ('minor', _("Petites fissures / Faïençage")), + ('major', _("Grosses fissures / Fissures ouvertes")), + ) + cracking = models.CharField( + max_length=50, + choices=CRACKING_CHOICES, + default='none', + null=True, + blank=True, + verbose_name=_("Fissuration") + ) + + WATER_DRAINAGE_CHOICES = ( + ('good', _("Bon écoulement / Pas de flaque")), + ('stagnation', _("Eau stagnante / Flaque d'eau")), + ) + water_drainage = models.CharField( + max_length=50, + choices=WATER_DRAINAGE_CHOICES, + default='good', + null=True, + blank=True, + verbose_name=_("Évacuation des eaux pluviales") + ) + + class Meta: + verbose_name = _("Inspection de réparation Hot Box") + verbose_name_plural = _("Inspections de réparations Hot Box") + + def compute_status(self): + """ + Calcule automatiquement le statut de conformité ('compliant', 'non_compliant', 'damaged') + selon la sévérité des constats visuels. + """ + if self.joint_level == 'step' or self.joint_sealing == 'open' or self.cracking == 'major': + return 'damaged' + if ( + self.joint_level == 'slight_bump_hollow' or + self.joint_sealing == 'cracked' or + self.cracking == 'minor' or + self.water_drainage == 'stagnation' + ): + return 'non_compliant' + return 'compliant' + + def inspection_document_upload_path(instance, filename): now = timezone.now() year = now.strftime("%Y") diff --git a/loko/inspections/registry.py b/loko/inspections/registry.py index 66739ca..17ea211 100644 --- a/loko/inspections/registry.py +++ b/loko/inspections/registry.py @@ -38,7 +38,9 @@ def get_inspection_detail_partial_template(insp_or_model_or_name): if not insp_or_model_or_name: return None - if hasattr(insp_or_model_or_name, 'asset') and insp_or_model_or_name.asset: + if hasattr(insp_or_model_or_name, 'is_hotbox_related') and insp_or_model_or_name.is_hotbox_related: + model_name = 'hotbox' + elif hasattr(insp_or_model_or_name, 'asset') and insp_or_model_or_name.asset: model_name = insp_or_model_or_name.asset._meta.model_name.lower() elif hasattr(insp_or_model_or_name, '_meta'): model_name = insp_or_model_or_name._meta.model_name.lower() @@ -51,6 +53,8 @@ def get_inspection_detail_partial_template(insp_or_model_or_name): model_name = 'parkingspot' elif model_name in ('signpanel', 'signpanelinspection'): model_name = 'signpanel' + elif model_name in ('hotbox', 'hotboxinspection'): + model_name = 'hotbox' template_path = f"inspections/components/asset_details/_{model_name}.html" try: diff --git a/loko/inspections/services.py b/loko/inspections/services.py index 8a13218..4438525 100644 --- a/loko/inspections/services.py +++ b/loko/inspections/services.py @@ -204,9 +204,14 @@ def schedule_post_intervention_inspection(intervention): if len(zone_inspectors) == 1: assigned_inspector = zone_inspectors[0] + from .models import Inspection, InspectionZone, HotBoxInspection + + is_hotbox = any(k in (sub_type.code or '').lower() or k in (sub_type.name_fr or '').lower() for k in ('hotbox', 'hot_box', 'hot box')) + inspection_cls = HotBoxInspection if is_hotbox else Inspection + # Création de l'inspection planifiée notes = f"Inspection de durabilité planifiée pour {sub_type.get_name()} (Intervention {intervention.code})." - inspection = Inspection.objects.create( + inspection = inspection_cls.objects.create( asset_content_type=target_ct, asset_object_id=target_id, checked_intervention=intervention, diff --git a/loko/inspections/templates/inspections/components/asset_details/_hotbox.html b/loko/inspections/templates/inspections/components/asset_details/_hotbox.html new file mode 100644 index 0000000..6f5049f --- /dev/null +++ b/loko/inspections/templates/inspections/components/asset_details/_hotbox.html @@ -0,0 +1,113 @@ +{% load i18n %} + +{% with leaf=insp.detailed_inspection %} +{% if leaf %} +
+
+ {% translate "Contrôles visuels Hot Box (Enrobé à chaud)" %} +
+ + +
+{% endif %} +{% endwith %} diff --git a/loko/inspections/templates/inspections/components/asset_fields/_hotbox.html b/loko/inspections/templates/inspections/components/asset_fields/_hotbox.html new file mode 100644 index 0000000..6715b47 --- /dev/null +++ b/loko/inspections/templates/inspections/components/asset_fields/_hotbox.html @@ -0,0 +1,182 @@ +{% load i18n %} + + +
+
+ + {% translate "Contrôle visuel de réparation Hot Box" %} + + + {% translate "Suggéré : Conforme" %} + +
+ +

+ + {% translate "Évaluez visuellement la zone réparée sur les 4 critères simples ci-dessous :" %} +

+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+
+
+ + diff --git a/loko/inspections/templates/inspections/components/edit_inspection_modal.html b/loko/inspections/templates/inspections/components/edit_inspection_modal.html index 7794144..91cffa2 100644 --- a/loko/inspections/templates/inspections/components/edit_inspection_modal.html +++ b/loko/inspections/templates/inspections/components/edit_inspection_modal.html @@ -143,6 +143,135 @@ {% include "inspections/components/asset_fields/_naturerwiz.html" %} + +
+
+
+ {% translate "Contrôles visuels Hot Box" %} +
+ + {% translate "Suggéré : Conforme" %} + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+ + +
+
+
+
+
@@ -630,6 +759,34 @@ document.addEventListener('DOMContentLoaded', function() { if (giepFieldsContainer) giepFieldsContainer.classList.add('d-none'); } + // Affichage et préremplissage des champs spécifiques Hot Box (HotBoxInspection) + const hotboxFieldsContainer = document.getElementById('editHotBoxSpecificFields'); + if (data.is_hotbox) { + if (hotboxFieldsContainer) hotboxFieldsContainer.classList.remove('d-none'); + const jl = data.joint_level || 'flush'; + const js = data.joint_sealing || 'good'; + const cr = data.cracking || 'none'; + const wd = data.water_drainage || 'good'; + + const radLevel = document.getElementById('edit_hb_level_' + (jl === 'slight_bump_hollow' ? 'slight' : jl)); + if (radLevel) radLevel.checked = true; + + const radSealing = document.getElementById('edit_hb_sealing_' + js); + if (radSealing) radSealing.checked = true; + + const radCracking = document.getElementById('edit_hb_cracking_' + cr); + if (radCracking) radCracking.checked = true; + + const radDrainage = document.getElementById('edit_hb_drainage_' + wd); + if (radDrainage) radDrainage.checked = true; + + if (typeof updateEditHotBoxRecommendation === 'function') { + updateEditHotBoxRecommendation(false); + } + } else { + if (hotboxFieldsContainer) hotboxFieldsContainer.classList.add('d-none'); + } + // Rendu des photos / documents existants const existingDocsContainer = document.getElementById('editInspectionExistingDocsContainer'); @@ -678,6 +835,47 @@ document.addEventListener('DOMContentLoaded', function() { }); }; + function updateEditHotBoxRecommendation(syncRadio = true) { + const level = document.querySelector('input[name="joint_level"]:checked')?.value || 'flush'; + const sealing = document.querySelector('input[name="joint_sealing"]:checked')?.value || 'good'; + const cracking = document.querySelector('input[name="cracking"]:checked')?.value || 'none'; + const drainage = document.querySelector('input[name="water_drainage"]:checked')?.value || 'good'; + + let recommended = 'compliant'; + let badgeText = '{% translate "Suggéré : Conforme" %}'; + let badgeClass = 'bg-success-subtle text-success border border-success-subtle'; + let icon = 'bi-check-circle-fill'; + + if (level === 'step' || sealing === 'open' || cracking === 'major') { + recommended = 'damaged'; + badgeText = '{% translate "Suggéré : Dégradé" %}'; + badgeClass = 'bg-danger-subtle text-danger border border-danger-subtle'; + icon = 'bi-exclamation-triangle-fill'; + } else if (level === 'slight_bump_hollow' || sealing === 'cracked' || cracking === 'minor' || drainage === 'stagnation') { + recommended = 'non_compliant'; + badgeText = '{% translate "Suggéré : Non conforme" %}'; + badgeClass = 'bg-warning-subtle text-warning-emphasis border border-warning-subtle'; + icon = 'bi-exclamation-circle-fill'; + } + + const badge = document.getElementById('editHotboxRecommendedBadge'); + if (badge) { + badge.className = `badge ${badgeClass} px-2.5 py-1 rounded-pill small`; + badge.innerHTML = `${badgeText}`; + } + + if (syncRadio) { + const rad = document.getElementById('edit_res_' + recommended); + if (rad) { + rad.checked = true; + } + } + } + + document.querySelectorAll('.edit-hotbox-input').forEach(input => { + input.addEventListener('change', () => updateEditHotBoxRecommendation(true)); + }); + window.deleteInspectionDoc = function(docId) { if (!docId) return; if (!confirm("{% translate 'Êtes-vous sûr de vouloir supprimer ce document ?' %}")) return; diff --git a/loko/inspections/tests.py b/loko/inspections/tests.py index 4d9866f..eb267a3 100644 --- a/loko/inspections/tests.py +++ b/loko/inspections/tests.py @@ -7,7 +7,7 @@ from django.utils import timezone from assets.models import NatureRWIZ, NatureRWIAsset, ParkingSpot from interventions.models import Intervention from common.models import Thematic -from .models import Inspection, ParkingSpotInspection, GiepInspection, InspectionDocument +from .models import Inspection, ParkingSpotInspection, GiepInspection, HotBoxInspection, InspectionDocument from .registry import get_inspection_partial_template User = get_user_model() @@ -1849,5 +1849,120 @@ class PostInterventionDurabilityInspectionTestCase(TestCase): self.assertContains(resp, f"inspection_id={insp.id}") self.assertContains(resp, "Inspections de terrain") + def test_hotbox_inspection_model_and_compute_status(self): + """Vérifie le modèle HotBoxInspection, le calcul de statut automatique et details_summary.""" + intervention = self._create_finished_intervention() + interv_ct = ContentType.objects.get_for_model(Intervention) + hb = HotBoxInspection.objects.create( + asset_content_type=interv_ct, + asset_object_id=intervention.pk, + inspector=self.user, + inspection_date=timezone.now(), + joint_level='flush', + joint_sealing='good', + cracking='none', + water_drainage='good', + ) + self.assertEqual(hb.compute_status(), 'compliant') + self.assertTrue(hb.is_hotbox_related) + self.assertEqual(hb.detailed_inspection, hb) + + # Vérification details_summary + summary = hb.details_summary + self.assertIn("Planéité", summary) + self.assertIn("Joints", summary) + self.assertIn("Fissuration", summary) + self.assertIn("Évacuation", summary) + + # Problème mineur -> non_compliant + hb.water_drainage = 'stagnation' + self.assertEqual(hb.compute_status(), 'non_compliant') + hb.water_drainage = 'good' + hb.cracking = 'minor' + self.assertEqual(hb.compute_status(), 'non_compliant') + + # Problème majeur -> damaged + hb.cracking = 'major' + self.assertEqual(hb.compute_status(), 'damaged') + hb.cracking = 'none' + hb.joint_level = 'step' + self.assertEqual(hb.compute_status(), 'damaged') + hb.joint_level = 'flush' + hb.joint_sealing = 'open' + self.assertEqual(hb.compute_status(), 'damaged') + + def test_hotbox_inspection_apis(self): + """Vérifie la création, la lecture des détails et la mise à jour via les endpoints API.""" + self.client.force_login(self.user) + intervention = self._create_finished_intervention() + interv_ct = ContentType.objects.get_for_model(Intervention) + + # 1. Création via create_inspection_api + create_url = reverse('inspections:create_inspection_api') + post_data = { + 'content_type_id': interv_ct.id, + 'object_id': intervention.pk, + 'result_status': 'compliant', + 'nature': 'post_intervention', + 'is_hotbox': 'true', + 'joint_level': 'flush', + 'joint_sealing': 'good', + 'cracking': 'minor', + 'water_drainage': 'good', + 'notes': 'Contrôle réparation enrobé', + } + resp = self.client.post(create_url, post_data) + self.assertEqual(resp.status_code, 200) + data = resp.json() + self.assertTrue(data['success']) + insp_id = data['inspection_id'] + + # Vérifier que l'instance créée est bien un HotBoxInspection + hb_obj = HotBoxInspection.objects.get(pk=insp_id) + self.assertEqual(hb_obj.joint_level, 'flush') + self.assertEqual(hb_obj.cracking, 'minor') + self.assertEqual(hb_obj.water_drainage, 'good') + + # 2. Détail via get_inspection_detail_api + detail_url = reverse('inspections:get_inspection_detail_api', kwargs={'inspection_id': insp_id}) + resp_detail = self.client.get(detail_url) + self.assertEqual(resp_detail.status_code, 200) + detail_data = resp_detail.json() + self.assertTrue(detail_data['success']) + self.assertTrue(detail_data.get('is_hotbox')) + self.assertEqual(detail_data['joint_level'], 'flush') + self.assertEqual(detail_data['cracking'], 'minor') + self.assertIn("Contrôles visuels Hot Box", detail_data.get('details_html', '')) + + # 3. Mise à jour via update_inspection_api + update_url = reverse('inspections:update_inspection_api', kwargs={'inspection_id': insp_id}) + update_data = { + 'result_status': 'non_compliant', + 'joint_level': 'slight_bump_hollow', + 'joint_sealing': 'cracked', + 'cracking': 'minor', + 'water_drainage': 'stagnation', + 'notes': 'Flaque résiduelle observée après pluie', + } + resp_update = self.client.post(update_url, update_data) + self.assertEqual(resp_update.status_code, 200) + self.assertTrue(resp_update.json()['success']) + + hb_obj.refresh_from_db() + self.assertEqual(hb_obj.result_status, 'non_compliant') + self.assertEqual(hb_obj.joint_level, 'slight_bump_hollow') + self.assertEqual(hb_obj.joint_sealing, 'cracked') + self.assertEqual(hb_obj.water_drainage, 'stagnation') + self.assertEqual(hb_obj.notes, 'Flaque résiduelle observée après pluie') + + def test_schedule_post_intervention_inspection_creates_hotbox_inspection(self): + """Vérifie que schedule_post_intervention_inspection pour un sous-type Hot Box instancie directement un HotBoxInspection.""" + from inspections.services import schedule_post_intervention_inspection + intervention = self._create_finished_intervention(sub_type=self.hotbox_subtype) + insp = schedule_post_intervention_inspection(intervention) + self.assertIsNotNone(insp) + self.assertIsInstance(insp.detailed_inspection, HotBoxInspection) + self.assertTrue(insp.is_hotbox_related) + diff --git a/loko/inspections/views.py b/loko/inspections/views.py index 2055bc1..8a02273 100644 --- a/loko/inspections/views.py +++ b/loko/inspections/views.py @@ -12,7 +12,7 @@ from common.models import Thematic from common.utils import get_short_name from assets.models import AssetCategory from .models import ( - Inspection, InspectionZone, ParkingSpotInspection, GiepInspection, SignPanelInspection, InspectionDocument, + Inspection, InspectionZone, ParkingSpotInspection, GiepInspection, SignPanelInspection, HotBoxInspection, InspectionDocument, INSPECTION_RESULT_CHOICES, INSPECTION_NATURE_CHOICES, INSPECTOR_ROLE_CHOICES ) @@ -579,6 +579,18 @@ def create_inspection_api(request): is_parking_spot = (content_type.model == 'parkingspot') is_giep = (content_type.model == 'naturerwiz') is_signpanel = (content_type.model == 'signpanel') + is_hotbox = ( + request.POST.get('is_hotbox') == 'true' or + request.POST.get('inspection_type') == 'hotbox' or + content_type.model in ('hotbox', 'hotboxinspection') + ) + if not is_hotbox and checked_intervention_id: + from interventions.models import Intervention + interv = Intervention.objects.filter(pk=checked_intervention_id).first() + if interv and getattr(interv, 'sub_type', None): + st = interv.sub_type + if any(k in (st.code or '').lower() or k in (st.name_fr or '').lower() for k in ('hotbox', 'hot_box', 'hot box')): + is_hotbox = True if is_giep and result_status == 'missing': return JsonResponse({ @@ -715,6 +727,30 @@ def create_inspection_api(request): ai_confidence=ai_conf, apply_sign_updates_to_asset=apply_changes, ) + elif is_hotbox: + joint_level = request.POST.get('joint_level') or 'flush' + joint_sealing = request.POST.get('joint_sealing') or 'good' + cracking = request.POST.get('cracking') or 'none' + water_drainage = request.POST.get('water_drainage') or 'good' + + inspection = HotBoxInspection( + asset_content_type=content_type, + asset_object_id=target_obj.pk, + inspector=request.user, + inspection_date=timezone.now(), + result_status=result_status, + nature=nature, + inspector_role=inspector_role, + mission_intervention_id=mission_intervention_id, + checked_intervention_id=checked_intervention_id, + verified_obsolescence=verified_obsolescence, + notes=notes, + apply_changes_to_asset=apply_changes, + joint_level=joint_level, + joint_sealing=joint_sealing, + cracking=cracking, + water_drainage=water_drainage, + ) inspection.save() else: inspection = Inspection.objects.create( @@ -916,6 +952,17 @@ def get_inspection_detail_api(request, inspection_id): data['ai_confidence'] = leaf.ai_confidence data['apply_sign_updates_to_asset'] = leaf.apply_sign_updates_to_asset + elif isinstance(leaf, HotBoxInspection) or getattr(leaf, 'is_hotbox_related', False): + data['is_hotbox'] = True + data['joint_level'] = getattr(leaf, 'joint_level', 'flush') + data['joint_level_display'] = str(leaf.get_joint_level_display()) if hasattr(leaf, 'get_joint_level_display') else '' + data['joint_sealing'] = getattr(leaf, 'joint_sealing', 'good') + data['joint_sealing_display'] = str(leaf.get_joint_sealing_display()) if hasattr(leaf, 'get_joint_sealing_display') else '' + data['cracking'] = getattr(leaf, 'cracking', 'none') + data['cracking_display'] = str(leaf.get_cracking_display()) if hasattr(leaf, 'get_cracking_display') else '' + data['water_drainage'] = getattr(leaf, 'water_drainage', 'good') + data['water_drainage_display'] = str(leaf.get_water_drainage_display()) if hasattr(leaf, 'get_water_drainage_display') else '' + return JsonResponse(data) @@ -1015,6 +1062,15 @@ def update_inspection_api(request, inspection_id): inspection.verified_cleanliness = request.POST.get('verified_cleanliness') if 'apply_sign_updates_to_asset' in request.POST: inspection.apply_sign_updates_to_asset = (request.POST.get('apply_sign_updates_to_asset') == 'true') + elif isinstance(inspection, HotBoxInspection): + if 'joint_level' in request.POST: + inspection.joint_level = request.POST['joint_level'] + if 'joint_sealing' in request.POST: + inspection.joint_sealing = request.POST['joint_sealing'] + if 'cracking' in request.POST: + inspection.cracking = request.POST['cracking'] + if 'water_drainage' in request.POST: + inspection.water_drainage = request.POST['water_drainage'] # Traitement des nouvelles pièces jointes / photos uploaded_files = request.FILES.getlist('photos') + request.FILES.getlist('documents') + request.FILES.getlist('files')