feat: require all four hotbox inspection questions to be answered and update validation logic
This commit is contained in:
parent
010466e26f
commit
ecb3bbddd1
6 changed files with 232 additions and 78 deletions
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 6.0.7 on 2026-09-18 10:10
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inspections', '0010_hotboxinspection'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='hotboxinspection',
|
||||
name='cracking',
|
||||
field=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'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hotboxinspection',
|
||||
name='joint_level',
|
||||
field=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=None, max_length=50, null=True, verbose_name='Planéité / Arasement'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hotboxinspection',
|
||||
name='joint_sealing',
|
||||
field=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=None, max_length=50, null=True, verbose_name='Joints de raccordement'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hotboxinspection',
|
||||
name='water_drainage',
|
||||
field=models.CharField(blank=True, choices=[('good', 'Bon écoulement / Pas de flaque'), ('stagnation', "Eau stagnante / Flaque d'eau")], default=None, max_length=50, null=True, verbose_name='Évacuation des eaux pluviales'),
|
||||
),
|
||||
]
|
||||
|
|
@ -586,6 +586,8 @@ class Inspection(models.Model):
|
|||
@property
|
||||
def detailed_inspection(self):
|
||||
"""Downcast à la sous-classe spécialisée si disponible (ex: ParkingSpotInspection, GiepInspection, SignPanelInspection, HotBoxInspection)."""
|
||||
if self.__class__ is not Inspection:
|
||||
return self
|
||||
if hasattr(self, 'parkingspotinspection'):
|
||||
return self.parkingspotinspection
|
||||
if hasattr(self, 'giepinspection'):
|
||||
|
|
@ -1062,7 +1064,7 @@ class HotBoxInspection(Inspection):
|
|||
joint_level = models.CharField(
|
||||
max_length=50,
|
||||
choices=JOINT_LEVEL_CHOICES,
|
||||
default='flush',
|
||||
default=None,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("Planéité / Arasement")
|
||||
|
|
@ -1076,7 +1078,7 @@ class HotBoxInspection(Inspection):
|
|||
joint_sealing = models.CharField(
|
||||
max_length=50,
|
||||
choices=JOINT_SEALING_CHOICES,
|
||||
default='good',
|
||||
default=None,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("Joints de raccordement")
|
||||
|
|
@ -1090,7 +1092,7 @@ class HotBoxInspection(Inspection):
|
|||
cracking = models.CharField(
|
||||
max_length=50,
|
||||
choices=CRACKING_CHOICES,
|
||||
default='none',
|
||||
default=None,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("Fissuration")
|
||||
|
|
@ -1103,7 +1105,7 @@ class HotBoxInspection(Inspection):
|
|||
water_drainage = models.CharField(
|
||||
max_length=50,
|
||||
choices=WATER_DRAINAGE_CHOICES,
|
||||
default='good',
|
||||
default=None,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("Évacuation des eaux pluviales")
|
||||
|
|
@ -1116,7 +1118,7 @@ class HotBoxInspection(Inspection):
|
|||
def compute_status(self):
|
||||
"""
|
||||
Calcule automatiquement le statut de conformité ('compliant', 'non_compliant', 'damaged')
|
||||
selon la sévérité des constats visuels.
|
||||
selon la sévérité des constats visuels, ou None si l'évaluation n'est pas complète.
|
||||
"""
|
||||
if self.joint_level == 'step' or self.joint_sealing == 'open' or self.cracking == 'major':
|
||||
return 'damaged'
|
||||
|
|
@ -1127,7 +1129,14 @@ class HotBoxInspection(Inspection):
|
|||
self.water_drainage == 'stagnation'
|
||||
):
|
||||
return 'non_compliant'
|
||||
return 'compliant'
|
||||
if self.joint_level and self.joint_sealing and self.cracking and self.water_drainage:
|
||||
return 'compliant'
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_fully_evaluated(self):
|
||||
"""Indique si les 4 questions de l'inspection ont été renseignées."""
|
||||
return bool(self.joint_level and self.joint_sealing and self.cracking and self.water_drainage)
|
||||
|
||||
|
||||
def inspection_document_upload_path(instance, filename):
|
||||
|
|
|
|||
|
|
@ -6,25 +6,25 @@
|
|||
<span class="small fw-bold text-primary text-uppercase d-flex align-items-center">
|
||||
<i class="bi bi-fire text-danger me-2 fs-6"></i>{% translate "Contrôle visuel de réparation Hot Box" %}
|
||||
</span>
|
||||
<span id="hotboxRecommendedBadge" class="badge bg-success-subtle text-success border border-success-subtle px-2.5 py-1 rounded-pill small">
|
||||
<i class="bi bi-check-circle-fill me-1"></i>{% translate "Suggéré : Conforme" %}
|
||||
<span id="hotboxRecommendedBadge" class="badge bg-secondary-subtle text-secondary border border-secondary-subtle px-2.5 py-1 rounded-pill small">
|
||||
<i class="bi bi-hourglass-split me-1"></i>{% translate "4 questions à évaluer" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="text-muted small mb-3">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
{% translate "Évaluez visuellement la zone réparée sur les 4 critères simples ci-dessous :" %}
|
||||
{% translate "Évaluez visuellement la zone réparée sur les 4 critères ci-dessous (sans valeur par défaut) :" %}
|
||||
</p>
|
||||
|
||||
<!-- 1. Planéité / Arasement -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-secondary mb-1 d-flex align-items-center">
|
||||
<i class="bi bi-distribute-vertical me-1 text-primary"></i>
|
||||
1. {% translate "Planéité / Arasement" %}
|
||||
1. {% translate "Planéité / Arasement" %} <span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-4">
|
||||
<input type="radio" class="btn-check hotbox-input" name="joint_level" id="hb_level_flush" value="flush" checked>
|
||||
<input type="radio" class="btn-check hotbox-input" name="joint_level" id="hb_level_flush" value="flush">
|
||||
<label class="btn btn-outline-success btn-sm w-100 py-2 d-flex align-items-center justify-content-center gap-1 rounded-3" for="hb_level_flush">
|
||||
<i class="bi bi-check-circle"></i>
|
||||
<span>{% translate "Bien à niveau / Arasé" %}</span>
|
||||
|
|
@ -51,11 +51,11 @@
|
|||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-secondary mb-1 d-flex align-items-center">
|
||||
<i class="bi bi-bounding-box me-1 text-info"></i>
|
||||
2. {% translate "Joints de raccordement" %}
|
||||
2. {% translate "Joints de raccordement" %} <span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-4">
|
||||
<input type="radio" class="btn-check hotbox-input" name="joint_sealing" id="hb_sealing_good" value="good" checked>
|
||||
<input type="radio" class="btn-check hotbox-input" name="joint_sealing" id="hb_sealing_good" value="good">
|
||||
<label class="btn btn-outline-success btn-sm w-100 py-2 d-flex align-items-center justify-content-center gap-1 rounded-3" for="hb_sealing_good">
|
||||
<i class="bi bi-check-circle"></i>
|
||||
<span>{% translate "Bien fermés / scellés" %}</span>
|
||||
|
|
@ -82,11 +82,11 @@
|
|||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-secondary mb-1 d-flex align-items-center">
|
||||
<i class="bi bi-activity me-1 text-warning"></i>
|
||||
3. {% translate "Fissuration" %}
|
||||
3. {% translate "Fissuration" %} <span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-4">
|
||||
<input type="radio" class="btn-check hotbox-input" name="cracking" id="hb_cracking_none" value="none" checked>
|
||||
<input type="radio" class="btn-check hotbox-input" name="cracking" id="hb_cracking_none" value="none">
|
||||
<label class="btn btn-outline-success btn-sm w-100 py-2 d-flex align-items-center justify-content-center gap-1 rounded-3" for="hb_cracking_none">
|
||||
<i class="bi bi-check-circle"></i>
|
||||
<span>{% translate "Aucune fissure" %}</span>
|
||||
|
|
@ -113,11 +113,11 @@
|
|||
<div class="mb-2">
|
||||
<label class="form-label small fw-bold text-secondary mb-1 d-flex align-items-center">
|
||||
<i class="bi bi-droplet-half me-1 text-primary"></i>
|
||||
4. {% translate "Évacuation des eaux pluviales" %}
|
||||
4. {% translate "Évacuation des eaux pluviales" %} <span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-6">
|
||||
<input type="radio" class="btn-check hotbox-input" name="water_drainage" id="hb_drainage_good" value="good" checked>
|
||||
<input type="radio" class="btn-check hotbox-input" name="water_drainage" id="hb_drainage_good" value="good">
|
||||
<label class="btn btn-outline-success btn-sm w-100 py-2 d-flex align-items-center justify-content-center gap-1 rounded-3" for="hb_drainage_good">
|
||||
<i class="bi bi-check-circle"></i>
|
||||
<span>{% translate "Bon écoulement / Pas de flaque" %}</span>
|
||||
|
|
@ -137,10 +137,21 @@
|
|||
<script>
|
||||
(function() {
|
||||
function computeHotBoxStatus() {
|
||||
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';
|
||||
const level = document.querySelector('input[name="joint_level"]:checked')?.value;
|
||||
const sealing = document.querySelector('input[name="joint_sealing"]:checked')?.value;
|
||||
const cracking = document.querySelector('input[name="cracking"]:checked')?.value;
|
||||
const drainage = document.querySelector('input[name="water_drainage"]:checked')?.value;
|
||||
|
||||
const count = [level, sealing, cracking, drainage].filter(Boolean).length;
|
||||
const badge = document.getElementById('hotboxRecommendedBadge');
|
||||
|
||||
if (count < 4) {
|
||||
if (badge) {
|
||||
badge.className = 'badge bg-secondary-subtle text-secondary border border-secondary-subtle px-2.5 py-1 rounded-pill small';
|
||||
badge.innerHTML = `<i class="bi bi-hourglass-split me-1"></i>${count}/4 {% translate "questions renseignées" %}`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let recommended = 'compliant';
|
||||
let badgeText = '{% translate "Suggéré : Conforme" %}';
|
||||
|
|
@ -159,7 +170,6 @@
|
|||
icon = 'bi-exclamation-circle-fill';
|
||||
}
|
||||
|
||||
const badge = document.getElementById('hotboxRecommendedBadge');
|
||||
if (badge) {
|
||||
badge.className = `badge ${badgeClass} px-2.5 py-1 rounded-pill small`;
|
||||
badge.innerHTML = `<i class="bi ${icon} me-1"></i>${badgeText}`;
|
||||
|
|
|
|||
|
|
@ -149,8 +149,8 @@
|
|||
<h6 class="fw-bold small text-primary mb-0">
|
||||
<i class="bi bi-fire text-danger me-1"></i>{% translate "Contrôles visuels Hot Box" %}
|
||||
</h6>
|
||||
<span id="editHotboxRecommendedBadge" class="badge bg-success-subtle text-success border border-success-subtle px-2.5 py-1 rounded-pill small">
|
||||
<i class="bi bi-check-circle-fill me-1"></i>{% translate "Suggéré : Conforme" %}
|
||||
<span id="editHotboxRecommendedBadge" class="badge bg-secondary-subtle text-secondary border border-secondary-subtle px-2.5 py-1 rounded-pill small">
|
||||
<i class="bi bi-hourglass-split me-1"></i>{% translate "4 questions à évaluer" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -158,7 +158,7 @@
|
|||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold text-secondary mb-1 d-flex align-items-center">
|
||||
<i class="bi bi-distribute-vertical me-1 text-primary"></i>
|
||||
1. {% translate "Planéité / Arasement" %}
|
||||
1. {% translate "Planéité / Arasement" %} <span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-4">
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold text-secondary mb-1 d-flex align-items-center">
|
||||
<i class="bi bi-bounding-box me-1 text-info"></i>
|
||||
2. {% translate "Joints de raccordement" %}
|
||||
2. {% translate "Joints de raccordement" %} <span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-4">
|
||||
|
|
@ -220,7 +220,7 @@
|
|||
<div class="mb-3">
|
||||
<label class="form-label small fw-semibold text-secondary mb-1 d-flex align-items-center">
|
||||
<i class="bi bi-activity me-1 text-warning"></i>
|
||||
3. {% translate "Fissuration" %}
|
||||
3. {% translate "Fissuration" %} <span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-4">
|
||||
|
|
@ -251,7 +251,7 @@
|
|||
<div class="mb-2">
|
||||
<label class="form-label small fw-semibold text-secondary mb-1 d-flex align-items-center">
|
||||
<i class="bi bi-droplet-half me-1 text-primary"></i>
|
||||
4. {% translate "Évacuation des eaux pluviales" %}
|
||||
4. {% translate "Évacuation des eaux pluviales" %} <span class="text-danger ms-1">*</span>
|
||||
</label>
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-6">
|
||||
|
|
@ -692,18 +692,28 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
window.toggleEditMissingResultChoice(Boolean(data.is_zone_rwiz));
|
||||
}
|
||||
|
||||
// Reset all result_status radios first
|
||||
document.querySelectorAll('input[name="result_status"]').forEach(r => { r.checked = false; });
|
||||
|
||||
// Pre-check result_status radio
|
||||
let targetRadioCode = data.result_status;
|
||||
if (data.is_scheduled && targetRadioCode === 'pending') {
|
||||
targetRadioCode = 'compliant';
|
||||
}
|
||||
const rad = document.getElementById('edit_res_' + targetRadioCode);
|
||||
if (rad) {
|
||||
if (data.is_zone_rwiz && targetRadioCode === 'missing') {
|
||||
const compRad = document.getElementById('edit_res_compliant');
|
||||
if (compRad) compRad.checked = true;
|
||||
// Pour les réparations Hot Box : ne pas présélectionner de résultat par défaut pour obliger l'évaluation
|
||||
if (data.is_hotbox) {
|
||||
targetRadioCode = null;
|
||||
} else {
|
||||
rad.checked = true;
|
||||
targetRadioCode = 'compliant';
|
||||
}
|
||||
}
|
||||
if (targetRadioCode) {
|
||||
const rad = document.getElementById('edit_res_' + targetRadioCode);
|
||||
if (rad) {
|
||||
if (data.is_zone_rwiz && targetRadioCode === 'missing') {
|
||||
const compRad = document.getElementById('edit_res_compliant');
|
||||
if (compRad) compRad.checked = true;
|
||||
} else {
|
||||
rad.checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -763,22 +773,30 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
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';
|
||||
// Réinitialiser les boutons radio Hot Box (pas de valeur par défaut)
|
||||
document.querySelectorAll('.edit-hotbox-input').forEach(r => { r.checked = false; });
|
||||
|
||||
const radLevel = document.getElementById('edit_hb_level_' + (jl === 'slight_bump_hollow' ? 'slight' : jl));
|
||||
if (radLevel) radLevel.checked = true;
|
||||
const jl = data.joint_level;
|
||||
const js = data.joint_sealing;
|
||||
const cr = data.cracking;
|
||||
const wd = data.water_drainage;
|
||||
|
||||
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 (jl) {
|
||||
const radLevel = document.getElementById('edit_hb_level_' + (jl === 'slight_bump_hollow' ? 'slight' : jl));
|
||||
if (radLevel) radLevel.checked = true;
|
||||
}
|
||||
if (js) {
|
||||
const radSealing = document.getElementById('edit_hb_sealing_' + js);
|
||||
if (radSealing) radSealing.checked = true;
|
||||
}
|
||||
if (cr) {
|
||||
const radCracking = document.getElementById('edit_hb_cracking_' + cr);
|
||||
if (radCracking) radCracking.checked = true;
|
||||
}
|
||||
if (wd) {
|
||||
const radDrainage = document.getElementById('edit_hb_drainage_' + wd);
|
||||
if (radDrainage) radDrainage.checked = true;
|
||||
}
|
||||
|
||||
if (typeof updateEditHotBoxRecommendation === 'function') {
|
||||
updateEditHotBoxRecommendation(false);
|
||||
|
|
@ -836,10 +854,21 @@ 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';
|
||||
const level = document.querySelector('#editHotBoxSpecificFields input[name="joint_level"]:checked')?.value;
|
||||
const sealing = document.querySelector('#editHotBoxSpecificFields input[name="joint_sealing"]:checked')?.value;
|
||||
const cracking = document.querySelector('#editHotBoxSpecificFields input[name="cracking"]:checked')?.value;
|
||||
const drainage = document.querySelector('#editHotBoxSpecificFields input[name="water_drainage"]:checked')?.value;
|
||||
|
||||
const count = [level, sealing, cracking, drainage].filter(Boolean).length;
|
||||
const badge = document.getElementById('editHotboxRecommendedBadge');
|
||||
|
||||
if (count < 4) {
|
||||
if (badge) {
|
||||
badge.className = 'badge bg-secondary-subtle text-secondary border border-secondary-subtle px-2.5 py-1 rounded-pill small';
|
||||
badge.innerHTML = `<i class="bi bi-hourglass-split me-1"></i>${count}/4 {% translate "questions renseignées" %}`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let recommended = 'compliant';
|
||||
let badgeText = '{% translate "Suggéré : Conforme" %}';
|
||||
|
|
@ -916,6 +945,34 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
|
||||
alertDiv.classList.add('d-none');
|
||||
successDiv.classList.add('d-none');
|
||||
|
||||
// Vérification pour Hot Box : obliger à répondre consciemment aux 4 questions
|
||||
const hotboxContainer = document.getElementById('editHotBoxSpecificFields');
|
||||
if (hotboxContainer && !hotboxContainer.classList.contains('d-none')) {
|
||||
const resSelected = editForm.querySelector('input[name="result_status"]:checked')?.value;
|
||||
if (resSelected !== 'cancelled') {
|
||||
const level = editForm.querySelector('input[name="joint_level"]:checked')?.value;
|
||||
const sealing = editForm.querySelector('input[name="joint_sealing"]:checked')?.value;
|
||||
const cracking = editForm.querySelector('input[name="cracking"]:checked')?.value;
|
||||
const drainage = editForm.querySelector('input[name="water_drainage"]:checked')?.value;
|
||||
|
||||
if (!level || !sealing || !cracking || !drainage) {
|
||||
alertDiv.textContent = "{% translate 'Veuillez répondre à chacun des 4 contrôles de la réparation Hot Box (Planéité, Joints, Fissuration, Évacuation des eaux).' %}";
|
||||
alertDiv.classList.remove('d-none');
|
||||
alertDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resultStatus = editForm.querySelector('input[name="result_status"]:checked')?.value;
|
||||
if (!resultStatus || resultStatus === 'pending') {
|
||||
alertDiv.textContent = "{% translate 'Veuillez sélectionner le résultat de l’inspection.' %}";
|
||||
alertDiv.classList.remove('d-none');
|
||||
alertDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
return;
|
||||
}
|
||||
|
||||
submitBtn.disabled = true;
|
||||
spinner.classList.remove('d-none');
|
||||
icon.classList.add('d-none');
|
||||
|
|
|
|||
|
|
@ -1850,19 +1850,30 @@ class PostInterventionDurabilityInspectionTestCase(TestCase):
|
|||
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."""
|
||||
"""Vérifie le modèle HotBoxInspection sans valeur par défaut, le calcul de statut et details_summary."""
|
||||
intervention = self._create_finished_intervention()
|
||||
interv_ct = ContentType.objects.get_for_model(Intervention)
|
||||
|
||||
# Création sans valeur par défaut
|
||||
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.assertIsNone(hb.joint_level)
|
||||
self.assertIsNone(hb.joint_sealing)
|
||||
self.assertIsNone(hb.cracking)
|
||||
self.assertIsNone(hb.water_drainage)
|
||||
self.assertFalse(hb.is_fully_evaluated)
|
||||
self.assertIsNone(hb.compute_status())
|
||||
|
||||
# Renseignement complet conforme
|
||||
hb.joint_level = 'flush'
|
||||
hb.joint_sealing = 'good'
|
||||
hb.cracking = 'none'
|
||||
hb.water_drainage = 'good'
|
||||
self.assertTrue(hb.is_fully_evaluated)
|
||||
self.assertEqual(hb.compute_status(), 'compliant')
|
||||
self.assertTrue(hb.is_hotbox_related)
|
||||
self.assertEqual(hb.detailed_inspection, hb)
|
||||
|
|
@ -1897,8 +1908,22 @@ class PostInterventionDurabilityInspectionTestCase(TestCase):
|
|||
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')
|
||||
|
||||
# 1. Tentative de finalisation sans renseigner les 4 questions -> erreur 400
|
||||
invalid_post = {
|
||||
'content_type_id': interv_ct.id,
|
||||
'object_id': intervention.pk,
|
||||
'result_status': 'compliant',
|
||||
'is_hotbox': 'true',
|
||||
'joint_level': 'flush',
|
||||
# joint_sealing, cracking, water_drainage manquants
|
||||
}
|
||||
resp_invalid = self.client.post(create_url, invalid_post)
|
||||
self.assertEqual(resp_invalid.status_code, 400)
|
||||
self.assertIn("Veuillez répondre à chacun des 4 contrôles", resp_invalid.json()['error'])
|
||||
|
||||
# 2. Création réussie avec les 4 réponses
|
||||
post_data = {
|
||||
'content_type_id': interv_ct.id,
|
||||
'object_id': intervention.pk,
|
||||
|
|
@ -1923,7 +1948,7 @@ class PostInterventionDurabilityInspectionTestCase(TestCase):
|
|||
self.assertEqual(hb_obj.cracking, 'minor')
|
||||
self.assertEqual(hb_obj.water_drainage, 'good')
|
||||
|
||||
# 2. Détail via get_inspection_detail_api
|
||||
# 3. 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)
|
||||
|
|
@ -1934,9 +1959,10 @@ class PostInterventionDurabilityInspectionTestCase(TestCase):
|
|||
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
|
||||
# 4. Mise à jour via update_inspection_api
|
||||
update_url = reverse('inspections:update_inspection_api', kwargs={'inspection_id': insp_id})
|
||||
update_data = {
|
||||
'status': 'completed',
|
||||
'result_status': 'non_compliant',
|
||||
'joint_level': 'slight_bump_hollow',
|
||||
'joint_sealing': 'cracked',
|
||||
|
|
|
|||
|
|
@ -728,10 +728,16 @@ def create_inspection_api(request):
|
|||
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'
|
||||
joint_level = request.POST.get('joint_level') or None
|
||||
joint_sealing = request.POST.get('joint_sealing') or None
|
||||
cracking = request.POST.get('cracking') or None
|
||||
water_drainage = request.POST.get('water_drainage') or None
|
||||
|
||||
if result_status not in ('cancelled', 'pending') and not all([joint_level, joint_sealing, cracking, water_drainage]):
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': _("Veuillez répondre à chacun des 4 contrôles visuels de la réparation Hot Box (Planéité, Joints, Fissuration, Évacuation des eaux).")
|
||||
}, status=400)
|
||||
|
||||
inspection = HotBoxInspection(
|
||||
asset_content_type=content_type,
|
||||
|
|
@ -954,14 +960,19 @@ def get_inspection_detail_api(request, inspection_id):
|
|||
|
||||
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 ''
|
||||
jl = getattr(leaf, 'joint_level', None)
|
||||
js = getattr(leaf, 'joint_sealing', None)
|
||||
cr = getattr(leaf, 'cracking', None)
|
||||
wd = getattr(leaf, 'water_drainage', None)
|
||||
data['joint_level'] = jl or ''
|
||||
data['joint_level_display'] = str(leaf.get_joint_level_display()) if jl and hasattr(leaf, 'get_joint_level_display') else ''
|
||||
data['joint_sealing'] = js or ''
|
||||
data['joint_sealing_display'] = str(leaf.get_joint_sealing_display()) if js and hasattr(leaf, 'get_joint_sealing_display') else ''
|
||||
data['cracking'] = cr or ''
|
||||
data['cracking_display'] = str(leaf.get_cracking_display()) if cr and hasattr(leaf, 'get_cracking_display') else ''
|
||||
data['water_drainage'] = wd or ''
|
||||
data['water_drainage_display'] = str(leaf.get_water_drainage_display()) if wd and hasattr(leaf, 'get_water_drainage_display') else ''
|
||||
data['is_fully_evaluated'] = bool(jl and js and cr and wd)
|
||||
|
||||
return JsonResponse(data)
|
||||
|
||||
|
|
@ -1064,13 +1075,21 @@ def update_inspection_api(request, inspection_id):
|
|||
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']
|
||||
inspection.joint_level = request.POST.get('joint_level') or None
|
||||
if 'joint_sealing' in request.POST:
|
||||
inspection.joint_sealing = request.POST['joint_sealing']
|
||||
inspection.joint_sealing = request.POST.get('joint_sealing') or None
|
||||
if 'cracking' in request.POST:
|
||||
inspection.cracking = request.POST['cracking']
|
||||
inspection.cracking = request.POST.get('cracking') or None
|
||||
if 'water_drainage' in request.POST:
|
||||
inspection.water_drainage = request.POST['water_drainage']
|
||||
inspection.water_drainage = request.POST.get('water_drainage') or None
|
||||
|
||||
# Si l'inspection est marquée réalisée / complétée, exiger la réponse aux 4 questions
|
||||
if inspection.status == 'completed' and inspection.result_status not in ('cancelled', 'pending'):
|
||||
if not all([inspection.joint_level, inspection.joint_sealing, inspection.cracking, inspection.water_drainage]):
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': _("Veuillez répondre à chacun des 4 contrôles visuels de la réparation Hot Box (Planéité, Joints, Fissuration, Évacuation des eaux).")
|
||||
}, status=400)
|
||||
|
||||
# Traitement des nouvelles pièces jointes / photos
|
||||
uploaded_files = request.FILES.getlist('photos') + request.FILES.getlist('documents') + request.FILES.getlist('files')
|
||||
|
|
|
|||
Loading…
Reference in a new issue