feat: make inspection result status mandatory and improve GIEP form scoping

This commit is contained in:
kdeterme 2026-09-14 14:49:34 +02:00
parent fe535683a1
commit bfebdcf3dd
7 changed files with 176 additions and 87 deletions

View file

@ -1438,6 +1438,11 @@
.catch(err => console.error('Error loading inspection fields partial:', err)); .catch(err => console.error('Error loading inspection fields partial:', err));
} }
// Réinitialisation des boutons de résultat de contrôle (aucun présélectionné par défaut)
modalEl.querySelectorAll('input[type="radio"][name="result_status"]').forEach(r => {
r.checked = false;
});
// Réinitialisation de l'accumulateur de photos/fichiers // Réinitialisation de l'accumulateur de photos/fichiers
accumulatedInspectionFiles = []; accumulatedInspectionFiles = [];
window.accumulatedInspectionFiles = accumulatedInspectionFiles; window.accumulatedInspectionFiles = accumulatedInspectionFiles;
@ -2142,6 +2147,16 @@
const formData = new FormData(form); const formData = new FormData(form);
if (!formData.get('result_status')) {
const missingStatusMsg = window.gettext ? window.gettext("Veuillez sélectionner le résultat de l'inspection.") : "Veuillez sélectionner le résultat de l'inspection.";
alertDiv.textContent = missingStatusMsg;
alertDiv.classList.remove('d-none');
submitBtn.disabled = false;
if (spinner) spinner.classList.add('d-none');
if (icon) icon.classList.remove('d-none');
return;
}
// Ajout des fichiers accumulés en mode continu // Ajout des fichiers accumulés en mode continu
accumulatedInspectionFiles.forEach(file => { accumulatedInspectionFiles.forEach(file => {
formData.append('photos', file); formData.append('photos', file);

View file

@ -42,7 +42,7 @@
<div class="d-flex flex-column gap-2" role="group" aria-label="{% translate 'Résultat' %}"> <div class="d-flex flex-column gap-2" role="group" aria-label="{% translate 'Résultat' %}">
<div class="row g-2"> <div class="row g-2">
<div class="col-6"> <div class="col-6">
<input type="radio" class="btn-check" name="result_status" id="res_compliant" value="compliant" checked> <input type="radio" class="btn-check" name="result_status" id="res_compliant" value="compliant">
<label class="btn btn-outline-success w-100 py-2 d-flex align-items-center justify-content-center gap-1" for="res_compliant"> <label class="btn btn-outline-success w-100 py-2 d-flex align-items-center justify-content-center gap-1" for="res_compliant">
<i class="bi bi-check-circle-fill"></i> <i class="bi bi-check-circle-fill"></i>
<span>{% translate "Conforme" %}</span> <span>{% translate "Conforme" %}</span>

View file

@ -1,7 +1,7 @@
{% load i18n static %} {% load i18n static %}
<!-- Component Formulaire d'Inspection GIEP Natif Mobile-First (Recommandations avec 5 choix) --> <!-- Component Formulaire d'Inspection GIEP Natif Mobile-First (Recommandations avec 5 choix) -->
<div id="giepSpecificFields" class="border rounded-4 p-3 mb-3 bg-white shadow-sm" data-location-id="{% if asset.location_id %}{{ asset.location_id }}{% elif asset.id %}{{ asset.id }}{% elif location.id %}{{ location.id }}{% endif %}" data-asset-id="{% if asset.id %}{{ asset.id }}{% endif %}"> <div id="giepSpecificFields" class="giep-specific-fields border rounded-4 p-3 mb-3 bg-white shadow-sm" data-location-id="{% if asset.location_id %}{{ asset.location_id }}{% elif asset.id %}{{ asset.id }}{% elif location.id %}{{ location.id }}{% endif %}" data-asset-id="{% if asset.id %}{{ asset.id }}{% endif %}">
<!-- En-tête --> <!-- En-tête -->
<div class="d-flex align-items-center justify-content-between mb-3 border-bottom pb-2"> <div class="d-flex align-items-center justify-content-between mb-3 border-bottom pb-2">
@ -16,7 +16,7 @@
<input type="hidden" name="giep_data" id="giep_data_serialized" value="{}"> <input type="hidden" name="giep_data" id="giep_data_serialized" value="{}">
<!-- Bandeau de statut automatique GIEP --> <!-- Bandeau de statut automatique GIEP -->
<div id="giep_status_auto_banner" class="alert alert-info border border-info-subtle rounded-3 p-2.5 mb-3 shadow-sm d-none"></div> <div id="giep_status_auto_banner" class="giep-status-auto-banner alert alert-info border border-info-subtle rounded-3 p-2.5 mb-3 shadow-sm d-none"></div>
<!-- 1. Conditions météorologiques --> <!-- 1. Conditions météorologiques -->
<div class="card border-primary-subtle bg-light-subtle mb-3 rounded-4 shadow-sm"> <div class="card border-primary-subtle bg-light-subtle mb-3 rounded-4 shadow-sm">
@ -56,18 +56,18 @@
<h6 class="fw-bold mb-0 small text-uppercase text-secondary"> <h6 class="fw-bold mb-0 small text-uppercase text-secondary">
<i class="bi bi-list-check me-2"></i>{% translate "Ouvrages inspectés" %} <i class="bi bi-list-check me-2"></i>{% translate "Ouvrages inspectés" %}
</h6> </h6>
<button type="button" class="btn btn-outline-success btn-sm py-1.5 px-3 fw-semibold rounded-pill shadow-sm" id="giep_add_ouvrage_btn"> <button type="button" class="btn btn-outline-success btn-sm py-1.5 px-3 fw-semibold rounded-pill shadow-sm giep-add-ouvrage-btn" id="giep_add_ouvrage_btn">
<i class="bi bi-plus-circle me-1"></i>{% translate "Ajouter un ouvrage" %} <i class="bi bi-plus-circle me-1"></i>{% translate "Ajouter un ouvrage" %}
</button> </button>
</div> </div>
<!-- Dynamic Container for Ouvrages --> <!-- Dynamic Container for Ouvrages -->
<div id="giep_ouvrages_container" class="d-flex flex-column gap-3"></div> <div id="giep_ouvrages_container" class="giep-ouvrages-container d-flex flex-column gap-3"></div>
</div> </div>
</div> </div>
<!-- Modal pour Sélection des Équipements GIEP sur Carte --> <!-- Modal pour Sélection des Équipements GIEP sur Carte -->
<div class="modal fade" id="giepAssetMapModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="giepAssetMapModalLabel" aria-hidden="true" style="z-index: 1090;"> <div class="modal fade giep-asset-map-modal" id="giepAssetMapModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="giepAssetMapModalLabel" aria-hidden="true" style="z-index: 1090;">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable"> <div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content rounded-4 shadow-lg border-0"> <div class="modal-content rounded-4 shadow-lg border-0">
<div class="modal-header bg-primary text-white py-2.5 px-3 rounded-top-4"> <div class="modal-header bg-primary text-white py-2.5 px-3 rounded-top-4">
@ -81,11 +81,11 @@
<div class="col-12 col-md-7"> <div class="col-12 col-md-7">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<span class="input-group-text bg-white"><i class="bi bi-search text-muted"></i></span> <span class="input-group-text bg-white"><i class="bi bi-search text-muted"></i></span>
<input type="text" id="giep_modal_search_input" class="form-control" placeholder="{% translate "Filtrer par code ou type d'ouvrage..." %}"> <input type="text" id="giep_modal_search_input" class="form-control giep-modal-search-input" placeholder="{% translate "Filtrer par code ou type d'ouvrage..." %}">
</div> </div>
</div> </div>
<div class="col-12 col-md-5"> <div class="col-12 col-md-5">
<select id="giep_modal_type_filter" class="form-select form-select-sm"> <select id="giep_modal_type_filter" class="form-select form-select-sm giep-modal-type-filter">
<option value="">{% translate "Tous les types d'équipements" %}</option> <option value="">{% translate "Tous les types d'équipements" %}</option>
<option value="evc">{% translate "Espace vert creux (EVC)" %}</option> <option value="evc">{% translate "Espace vert creux (EVC)" %}</option>
<option value="oed">{% translate "Massif drainant (OED)" %}</option> <option value="oed">{% translate "Massif drainant (OED)" %}</option>
@ -156,9 +156,21 @@
<script> <script>
(function() { (function() {
const currentScript = document.currentScript;
const rootModal = (currentScript && currentScript.closest('.modal')) ||
(currentScript && currentScript.closest('form')) ||
document.querySelector('#quickInspectionModal.show, #editInspectionModal.show') ||
document.getElementById('quickInspectionModal') ||
document;
const giepFields = rootModal.querySelector('.giep-specific-fields') || rootModal.querySelector('#giepSpecificFields');
const container = rootModal.querySelector('.giep-ouvrages-container') || rootModal.querySelector('#giep_ouvrages_container');
const serializedInput = rootModal.querySelector('input[name="giep_data"]');
const addBtn = rootModal.querySelector('.giep-add-ouvrage-btn') || rootModal.querySelector('#giep_add_ouvrage_btn');
const banner = rootModal.querySelector('.giep-status-auto-banner') || rootModal.querySelector('#giep_status_auto_banner');
if (typeof window.toggleMissingResultChoice === 'function') { if (typeof window.toggleMissingResultChoice === 'function') {
const container = document.getElementById('giepSpecificFields'); const isAssetSpecific = giepFields && giepFields.dataset.assetId && giepFields.dataset.assetId !== '';
const isAssetSpecific = container && container.dataset.assetId && container.dataset.assetId !== '';
if (!isAssetSpecific) { if (!isAssetSpecific) {
window.toggleMissingResultChoice(true); window.toggleMissingResultChoice(true);
} }
@ -241,13 +253,8 @@
let modalMapInstance = null; let modalMapInstance = null;
let modalMapMarkers = []; let modalMapMarkers = [];
const container = document.getElementById('giep_ouvrages_container');
const serializedInput = document.getElementById('giep_data_serialized');
const addBtn = document.getElementById('giep_add_ouvrage_btn');
function resolveCurrentLocationId() { function resolveCurrentLocationId() {
// 1. Check data-location-id on container // 1. Check data-location-id on container
const giepFields = document.getElementById('giepSpecificFields');
if (giepFields && giepFields.getAttribute('data-location-id')) { if (giepFields && giepFields.getAttribute('data-location-id')) {
return giepFields.getAttribute('data-location-id'); return giepFields.getAttribute('data-location-id');
} }
@ -256,17 +263,17 @@
return window.currentGiepLocationId; return window.currentGiepLocationId;
} }
// 3. Check quick inspection modal insp_object_id // 3. Check quick inspection modal insp_object_id
const inspObjIdEl = document.getElementById('insp_object_id'); const inspObjIdEl = rootModal.querySelector('#insp_object_id, [name="object_id"]');
if (inspObjIdEl && inspObjIdEl.value) { if (inspObjIdEl && inspObjIdEl.value) {
return inspObjIdEl.value; return inspObjIdEl.value;
} }
// 4. Check edit inspection modal edit_insp_object_id // 4. Check edit inspection modal edit_insp_object_id
const editInspObjIdEl = document.getElementById('edit_insp_object_id'); const editInspObjIdEl = rootModal.querySelector('#edit_insp_object_id');
if (editInspObjIdEl && editInspObjIdEl.value) { if (editInspObjIdEl && editInspObjIdEl.value) {
return editInspObjIdEl.value; return editInspObjIdEl.value;
} }
// 5. Check trigger with data-object-id // 5. Check trigger with data-object-id
const modalTrigger = document.querySelector('[data-object-id]'); const modalTrigger = rootModal.querySelector('[data-object-id]') || document.querySelector('[data-object-id]');
if (modalTrigger && modalTrigger.getAttribute('data-object-id')) { if (modalTrigger && modalTrigger.getAttribute('data-object-id')) {
return modalTrigger.getAttribute('data-object-id'); return modalTrigger.getAttribute('data-object-id');
} }
@ -504,8 +511,10 @@
} }
function updateSerializedGiepData() { function updateSerializedGiepData() {
const pluieVal = document.getElementById('giep_date_derniere_pluie')?.value || 'null'; const dateRainEl = rootModal.querySelector('#giep_date_derniere_pluie, [name="date_derniere_pluie"]');
const orageVal = document.getElementById('giep_orage_moins_une_heure')?.value || 'null'; const stormEl = rootModal.querySelector('#giep_orage_moins_une_heure, [name="orage_moins_une_heure"]');
const pluieVal = dateRainEl?.value || 'null';
const orageVal = stormEl?.value || 'null';
const payload = { const payload = {
ouvrages: giepOuvrages, ouvrages: giepOuvrages,
@ -597,12 +606,29 @@
function getStatusLabel(status) { function getStatusLabel(status) {
if (status === 'damaged') return '{% filter escapejs %}{% translate "Endommagé" %}{% endfilter %}'; if (status === 'damaged') return '{% filter escapejs %}{% translate "Endommagé" %}{% endfilter %}';
if (status === 'non_compliant') return '{% filter escapejs %}{% translate "Non conforme" %}{% endfilter %}'; if (status === 'non_compliant') return '{% filter escapejs %}{% translate "Non conforme" %}{% endfilter %}';
return '{% filter escapejs %}{% translate "Conforme" %}{% endfilter %}'; if (status === 'compliant') return '{% filter escapejs %}{% translate "Conforme" %}{% endfilter %}';
return '{% filter escapejs %}{% translate "Non déterminé" %}{% endfilter %}';
}
function hasAnyGiepAnswer(ouvrages) {
if (!Array.isArray(ouvrages) || ouvrages.length === 0) return false;
for (const ouv of ouvrages) {
if (!ouv || !ouv.questions) continue;
for (const [k, v] of Object.entries(ouv.questions)) {
if (v !== null && v !== undefined && v !== 'null' && v !== '' && v !== false) {
return true;
}
}
}
return false;
} }
function evaluateGiepInspectionStatus(ouvrages) { function evaluateGiepInspectionStatus(ouvrages) {
if (!Array.isArray(ouvrages) || ouvrages.length === 0) { if (!Array.isArray(ouvrages) || ouvrages.length === 0) {
return 'compliant'; return null;
}
if (!hasAnyGiepAnswer(ouvrages)) {
return null;
} }
let hasDamaged = false; let hasDamaged = false;
@ -768,60 +794,67 @@
} }
function getAllStatusRadios() { function getAllStatusRadios() {
return document.querySelectorAll('input[type="radio"][name="result_status"]'); return rootModal.querySelectorAll('input[type="radio"][name="result_status"]');
} }
function getActiveStatusRadio(status) { function getStatusRadio(status) {
const editRad = document.getElementById('edit_res_' + status); return rootModal.querySelector('input[type="radio"][name="result_status"][value="' + status + '"]');
if (editRad && editRad.closest('.modal.show, form')) return editRad;
const createRad = document.getElementById('res_' + status);
if (createRad && createRad.closest('.modal.show, form')) return createRad;
return createRad || editRad || null;
} }
function syncGiepCalculatedStatus() { function syncGiepCalculatedStatus() {
initStatusRadioListeners(); initStatusRadioListeners();
const calculated = evaluateGiepInspectionStatus(giepOuvrages); const calculated = evaluateGiepInspectionStatus(giepOuvrages);
const banner = document.getElementById('giep_status_auto_banner'); if (!banner) return;
if (!isGiepStatusManuallyOverridden) { if (!isGiepStatusManuallyOverridden) {
const rad = getActiveStatusRadio(calculated); if (calculated) {
if (rad && !rad.checked) { const rad = getStatusRadio(calculated);
rad.checked = true; if (rad && !rad.checked) {
rad.dispatchEvent(new Event('change', { bubbles: true })); rad.checked = true;
rad.dispatchEvent(new Event('change', { bubbles: true }));
}
} }
} }
if (banner) { banner.classList.remove('d-none');
banner.classList.remove('d-none');
const calcLabel = getStatusLabel(calculated); if (isGiepStatusManuallyOverridden) {
let badgeClass = 'bg-success'; let checkedVal = '';
getAllStatusRadios().forEach(r => { if (r.checked) checkedVal = r.value; });
const currentLabel = getStatusLabel(checkedVal);
const calcLabel = calculated ? getStatusLabel(calculated) : '{% filter escapejs %}{% translate "Non déterminé" %}{% endfilter %}';
let badgeClass = 'bg-secondary';
if (calculated === 'damaged') badgeClass = 'bg-danger'; if (calculated === 'damaged') badgeClass = 'bg-danger';
else if (calculated === 'non_compliant') badgeClass = 'bg-warning text-dark'; else if (calculated === 'non_compliant') badgeClass = 'bg-warning text-dark';
else if (calculated === 'compliant') badgeClass = 'bg-success';
if (isGiepStatusManuallyOverridden) { banner.className = 'alert alert-warning border border-warning-subtle rounded-3 p-2.5 mb-3 shadow-sm d-flex align-items-center justify-content-between flex-wrap gap-2 giep-status-auto-banner';
let checkedVal = ''; banner.innerHTML = `
getAllStatusRadios().forEach(r => { if (r.checked) checkedVal = r.value; }); <div class="small">
const currentLabel = getStatusLabel(checkedVal); <i class="bi bi-pencil-square me-1"></i>
<strong>{% filter escapejs %}{% translate "Statut modifié manuellement" %}{% endfilter %} :</strong>
<span class="badge bg-secondary me-1">${currentLabel}</span>
<span class="text-muted">({% filter escapejs %}{% translate "Calculé" %}{% endfilter %} : <span class="badge ${badgeClass}">${calcLabel}</span>)</span>
</div>
<button type="button" class="btn btn-sm btn-outline-dark py-0 px-2 fw-semibold btn-reset-giep-auto-status" id="btn_reset_giep_auto_status">
<i class="bi bi-arrow-counterclockwise me-1"></i>{% filter escapejs %}{% translate "Rétablir le calcul auto" %}{% endfilter %}
</button>
`;
banner.querySelector('.btn-reset-giep-auto-status')?.addEventListener('click', () => {
isGiepStatusManuallyOverridden = false;
if (!calculated) {
getAllStatusRadios().forEach(r => { r.checked = false; });
}
syncGiepCalculatedStatus();
});
} else {
if (calculated) {
const calcLabel = getStatusLabel(calculated);
let badgeClass = 'bg-success';
if (calculated === 'damaged') badgeClass = 'bg-danger';
else if (calculated === 'non_compliant') badgeClass = 'bg-warning text-dark';
banner.className = 'alert alert-warning border border-warning-subtle rounded-3 p-2.5 mb-3 shadow-sm d-flex align-items-center justify-content-between flex-wrap gap-2'; banner.className = 'alert alert-light border border-info-subtle bg-info-subtle text-info-emphasis rounded-3 p-2.5 mb-3 shadow-sm d-flex align-items-center justify-content-between flex-wrap gap-2 giep-status-auto-banner';
banner.innerHTML = `
<div class="small">
<i class="bi bi-pencil-square me-1"></i>
<strong>{% filter escapejs %}{% translate "Statut modifié manuellement" %}{% endfilter %} :</strong>
<span class="badge bg-secondary me-1">${currentLabel}</span>
<span class="text-muted">({% filter escapejs %}{% translate "Calculé" %}{% endfilter %} : <span class="badge ${badgeClass}">${calcLabel}</span>)</span>
</div>
<button type="button" class="btn btn-sm btn-outline-dark py-0 px-2 fw-semibold" id="btn_reset_giep_auto_status">
<i class="bi bi-arrow-counterclockwise me-1"></i>{% filter escapejs %}{% translate "Rétablir le calcul auto" %}{% endfilter %}
</button>
`;
document.getElementById('btn_reset_giep_auto_status')?.addEventListener('click', () => {
isGiepStatusManuallyOverridden = false;
syncGiepCalculatedStatus();
});
} else {
banner.className = 'alert alert-light border border-info-subtle bg-info-subtle text-info-emphasis rounded-3 p-2.5 mb-3 shadow-sm d-flex align-items-center justify-content-between flex-wrap gap-2';
banner.innerHTML = ` banner.innerHTML = `
<div class="small"> <div class="small">
<i class="bi bi-magic me-1 text-primary"></i> <i class="bi bi-magic me-1 text-primary"></i>
@ -829,24 +862,37 @@
<span class="badge ${badgeClass} ms-1">${calcLabel}</span> <span class="badge ${badgeClass} ms-1">${calcLabel}</span>
</div> </div>
`; `;
} else {
banner.className = 'alert alert-light border border-secondary-subtle bg-light text-secondary rounded-3 p-2.5 mb-3 shadow-sm d-flex align-items-center justify-content-between flex-wrap gap-2 giep-status-auto-banner';
banner.innerHTML = `
<div class="small">
<i class="bi bi-magic me-1 text-secondary"></i>
<strong>{% filter escapejs %}{% translate "Statut calculé automatiquement" %}{% endfilter %} :</strong>
<span class="badge bg-secondary ms-1">{% filter escapejs %}{% translate "En attente des réponses" %}{% endfilter %}</span>
</div>
`;
} }
} }
} }
function initStatusRadioListeners() { function initStatusRadioListeners() {
getAllStatusRadios().forEach(radio => { const onRadioClick = (e) => {
if (radio.dataset.giepListenerAttached) return; const radio = e.currentTarget;
radio.dataset.giepListenerAttached = 'true'; const calculated = evaluateGiepInspectionStatus(giepOuvrages);
if (radio.value !== calculated) {
isGiepStatusManuallyOverridden = true;
} else {
isGiepStatusManuallyOverridden = false;
}
syncGiepCalculatedStatus();
};
radio.addEventListener('click', () => { getAllStatusRadios().forEach(radio => {
const calculated = evaluateGiepInspectionStatus(giepOuvrages); if (radio._giepRadioClickHandler) {
if (radio.value !== calculated) { radio.removeEventListener('click', radio._giepRadioClickHandler);
isGiepStatusManuallyOverridden = true; }
} else { radio._giepRadioClickHandler = onRadioClick;
isGiepStatusManuallyOverridden = false; radio.addEventListener('click', onRadioClick);
}
syncGiepCalculatedStatus();
});
}); });
} }
@ -1033,7 +1079,7 @@
} }
function initOrUpdateModalMap() { function initOrUpdateModalMap() {
const containerEl = document.getElementById('giepModalMap'); const containerEl = rootModal.querySelector('#giepModalMap') || document.getElementById('giepModalMap');
if (!containerEl || typeof maplibregl === 'undefined') return; if (!containerEl || typeof maplibregl === 'undefined') return;
const style = window.MAP_STYLE_LIGHT || 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'; const style = window.MAP_STYLE_LIGHT || 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';
@ -1071,7 +1117,7 @@
function openMapModal(idx) { function openMapModal(idx) {
activeOuvrageIdxForMap = idx; activeOuvrageIdxForMap = idx;
fetchLocationAssets().then(assets => { fetchLocationAssets().then(assets => {
const modalEl = document.getElementById('giepAssetMapModal'); const modalEl = rootModal.querySelector('.giep-asset-map-modal') || document.getElementById('giepAssetMapModal');
let modalInstance = bootstrap.Modal.getInstance(modalEl); let modalInstance = bootstrap.Modal.getInstance(modalEl);
if (!modalInstance) { if (!modalInstance) {
modalInstance = new bootstrap.Modal(modalEl); modalInstance = new bootstrap.Modal(modalEl);
@ -1095,7 +1141,7 @@
</button> </button>
</div> </div>
`; `;
document.getElementById('giep_add_first_ouvrage_btn')?.addEventListener('click', () => { container.querySelector('#giep_add_first_ouvrage_btn')?.addEventListener('click', () => {
giepOuvrages.push(createEmptyOuvrage(0, "evc")); giepOuvrages.push(createEmptyOuvrage(0, "evc"));
renderOuvrages(); renderOuvrages();
}); });
@ -1580,28 +1626,31 @@
} }
// Modal filter & shown listeners // Modal filter & shown listeners
const modalEl = document.getElementById('giepAssetMapModal'); const modalEl = rootModal.querySelector('.giep-asset-map-modal') || document.getElementById('giepAssetMapModal');
if (modalEl) { if (modalEl) {
modalEl.addEventListener('shown.bs.modal', () => { modalEl.addEventListener('shown.bs.modal', () => {
initOrUpdateModalMap(); initOrUpdateModalMap();
}); });
} }
document.getElementById('giep_modal_search_input')?.addEventListener('input', (e) => { const searchInput = rootModal.querySelector('#giep_modal_search_input, .giep-modal-search-input');
const typeFilter = rootModal.querySelector('#giep_modal_type_filter, .giep-modal-type-filter');
searchInput?.addEventListener('input', (e) => {
const text = e.target.value; const text = e.target.value;
const type = document.getElementById('giep_modal_type_filter')?.value || ''; const type = typeFilter?.value || '';
populateModalAssetsList(text, type); populateModalAssetsList(text, type);
renderModalMapFeatures(false); renderModalMapFeatures(false);
}); });
document.getElementById('giep_modal_type_filter')?.addEventListener('change', (e) => { typeFilter?.addEventListener('change', (e) => {
const type = e.target.value; const type = e.target.value;
const text = document.getElementById('giep_modal_search_input')?.value || ''; const text = searchInput?.value || '';
populateModalAssetsList(text, type); populateModalAssetsList(text, type);
renderModalMapFeatures(false); renderModalMapFeatures(false);
}); });
document.querySelectorAll('.giep-input').forEach(el => { rootModal.querySelectorAll('.giep-input').forEach(el => {
el.addEventListener('change', updateSerializedGiepData); el.addEventListener('change', updateSerializedGiepData);
}); });
@ -1611,11 +1660,11 @@
window.currentGiepLocationId = data.location_id; window.currentGiepLocationId = data.location_id;
locationGiepAssets = []; locationGiepAssets = [];
} }
const dateRain = document.getElementById('giep_date_derniere_pluie'); const dateRain = rootModal.querySelector('#giep_date_derniere_pluie, [name="date_derniere_pluie"]');
if (dateRain) { if (dateRain) {
dateRain.value = (data.date_derniere_pluie !== undefined && data.date_derniere_pluie !== null) ? String(data.date_derniere_pluie) : 'null'; dateRain.value = (data.date_derniere_pluie !== undefined && data.date_derniere_pluie !== null) ? String(data.date_derniere_pluie) : 'null';
} }
const storm = document.getElementById('giep_orage_moins_une_heure'); const storm = rootModal.querySelector('#giep_orage_moins_une_heure, [name="orage_moins_une_heure"]');
if (storm) { if (storm) {
storm.value = (data.orage_moins_une_heure !== undefined && data.orage_moins_une_heure !== null) ? String(data.orage_moins_une_heure) : 'null'; storm.value = (data.orage_moins_une_heure !== undefined && data.orage_moins_une_heure !== null) ? String(data.orage_moins_une_heure) : 'null';
} }
@ -1627,7 +1676,9 @@
// Si le statut existant diffère du statut calculé automatiquement, marquer comme surchargé // Si le statut existant diffère du statut calculé automatiquement, marquer comme surchargé
const autoStatus = evaluateGiepInspectionStatus(giepOuvrages); const autoStatus = evaluateGiepInspectionStatus(giepOuvrages);
if (data.result_status && data.result_status !== autoStatus) { if (data.result_status && autoStatus && data.result_status !== autoStatus) {
isGiepStatusManuallyOverridden = true;
} else if (data.result_status && !autoStatus) {
isGiepStatusManuallyOverridden = true; isGiepStatusManuallyOverridden = true;
} else { } else {
isGiepStatusManuallyOverridden = false; isGiepStatusManuallyOverridden = false;
@ -1637,6 +1688,10 @@
updateSerializedGiepData(); updateSerializedGiepData();
}; };
if (rootModal) {
rootModal.setGiepFormData = window.setGiepFormData;
}
initStatusRadioListeners(); initStatusRadioListeners();
renderOuvrages(); renderOuvrages();
})(); })();

View file

@ -594,7 +594,10 @@ document.addEventListener('DOMContentLoaded', function() {
const giepFieldsContainer = document.getElementById('editGiepSpecificFieldsContainer'); const giepFieldsContainer = document.getElementById('editGiepSpecificFieldsContainer');
if (data.is_giep) { if (data.is_giep) {
if (giepFieldsContainer) giepFieldsContainer.classList.remove('d-none'); if (giepFieldsContainer) giepFieldsContainer.classList.remove('d-none');
if (typeof window.setGiepFormData === 'function') { const editModal = document.getElementById('editInspectionModal');
if (editModal && typeof editModal.setGiepFormData === 'function') {
editModal.setGiepFormData(data);
} else if (typeof window.setGiepFormData === 'function') {
window.setGiepFormData(data); window.setGiepFormData(data);
} }
} else { } else {

View file

@ -44,7 +44,7 @@
{% for choice in INSPECTION_RESULT_CHOICES_CONFIG %} {% for choice in INSPECTION_RESULT_CHOICES_CONFIG %}
{% if choice.code != 'cancelled' %} {% if choice.code != 'cancelled' %}
<div class="col-6 mb-1" id="res_container_{{ choice.code }}"> <div class="col-6 mb-1" id="res_container_{{ choice.code }}">
<input type="radio" class="btn-check" name="result_status" id="res_{{ choice.code }}" value="{{ choice.code }}" {% if forloop.first %}checked{% endif %}> <input type="radio" class="btn-check" name="result_status" id="res_{{ choice.code }}" value="{{ choice.code }}">
<label class="btn {{ choice.btn_class }} w-100 py-2 d-flex align-items-center justify-content-center gap-1" for="res_{{ choice.code }}" id="res_label_{{ choice.code }}"> <label class="btn {{ choice.btn_class }} w-100 py-2 d-flex align-items-center justify-content-center gap-1" for="res_{{ choice.code }}" id="res_label_{{ choice.code }}">
<i class="bi {{ choice.icon }}"></i> <i class="bi {{ choice.icon }}"></i>
<span>{{ choice.label }}</span> <span>{{ choice.label }}</span>

View file

@ -204,6 +204,20 @@ class InspectionModelAndViewsTestCase(TestCase):
self.assertFalse(data['success']) self.assertFalse(data['success'])
self.assertIn("Absent / Retiré", data['error']) self.assertIn("Absent / Retiré", data['error'])
def test_create_inspection_api_requires_result_status(self):
self.client.login(username='inspector_user', password='password123')
url = reverse('inspections:create_inspection_api')
response = self.client.post(url, {
'content_type_id': self.spot_ct.id,
'object_id': self.spot.id,
'nature': 'visual',
'inspector_role': 'provider',
})
self.assertEqual(response.status_code, 400)
data = response.json()
self.assertFalse(data['success'])
self.assertIn("Veuillez sélectionner le résultat", data['error'])
def test_update_giep_inspection_api_rejects_missing(self): def test_update_giep_inspection_api_rejects_missing(self):
self.client.login(username='inspector_user', password='password123') self.client.login(username='inspector_user', password='password123')
insp = GiepInspection.objects.create( insp = GiepInspection.objects.create(

View file

@ -482,7 +482,9 @@ def create_inspection_api(request):
""" """
content_type_id = request.POST.get('content_type_id') content_type_id = request.POST.get('content_type_id')
object_id = request.POST.get('object_id') object_id = request.POST.get('object_id')
result_status = request.POST.get('result_status', 'compliant') result_status = request.POST.get('result_status')
if not result_status:
return JsonResponse({'success': False, 'error': _("Veuillez sélectionner le résultat de l'inspection.")}, status=400)
nature = request.POST.get('nature', 'visual') nature = request.POST.get('nature', 'visual')
inspector_role = request.POST.get('inspector_role', 'observer') inspector_role = request.POST.get('inspector_role', 'observer')
notes = request.POST.get('notes', '').strip() notes = request.POST.get('notes', '').strip()