diff --git a/ecole/public/js/projet-form-rc.js b/ecole/public/js/projet-form-rc.js index a98cc738..65488c25 100644 --- a/ecole/public/js/projet-form-rc.js +++ b/ecole/public/js/projet-form-rc.js @@ -11,6 +11,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio let modRCActRCC, modRCMar, modRCZone, modRCActCompl, modRCGarAdd; let hasSavedGrilleData = false; // évite d'écraser une grille déjà enregistrée + let rcProjetGuard = null; // Initialisation des tag pour select var tagAnimauxVivants = false; @@ -57,10 +58,44 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio loadModulateurs(); // Appel des différentes fonctions d'initialisation - setupEventListeners(); - populateFormData(); - setupTarifImpactListeners(); + try { + setupEventListeners(); + } catch (error) { + console.error('Erreur lors de setupEventListeners:', error); + } + + try { + populateFormData(); + } catch (error) { + console.error('Erreur lors de populateFormData:', error); + } + + let impactListenersReady = false; + try { + setupTarifImpactListeners(); + impactListenersReady = true; + } catch (error) { + console.error('Erreur lors de setupTarifImpactListeners:', error); + } + + // Retry léger: certains contextes chargent des blocs dynamiques juste après init. + if (!impactListenersReady) { + setTimeout(() => { + try { + setupTarifImpactListeners(); + } catch (error) { + console.error('Nouvel échec setupTarifImpactListeners (retry):', error); + } + }, 700); + } + updatePrimeReferenceRow(); + setupRCSafeValidation(); updateSubmitButtonState('projetForm'); + + setTimeout(() => { + updatePrimeReferenceRow(); + if (rcProjetGuard) rcProjetGuard.refresh(); + }, 350); } let tarifOriginalData = null; @@ -92,6 +127,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio window.rc = rc; window.projet = projet; window.tarif = tarif; + updatePrimeReferenceRow(); return true; } catch (error) { console.warn('Impossible de recharger le contexte RC depuis la session:', error); @@ -112,6 +148,251 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio return Boolean(tarifOriginalData); } + function parseValidationNumber(rawValue) { + if (window.RCValidationUtils && typeof window.RCValidationUtils.parseLooseNumber === 'function') { + return window.RCValidationUtils.parseLooseNumber(rawValue); + } + const normalized = String(rawValue ?? '') + .trim() + .replace(/\s/g, '') + .replace(/[€$£¥]/g, '') + .replace(',', '.'); + const parsed = Number(normalized); + return Number.isFinite(parsed) ? parsed : NaN; + } + + function formatReferenceAmount(value) { + const number = Number(value); + if (!Number.isFinite(number)) return '0,00 €'; + return number.toLocaleString('fr-FR', { + minimumFractionDigits: 2, + maximumFractionDigits: 2 + }) + ' €'; + } + + function normalizeFranchiseChoice(rawChoice) { + const choice = String(rawChoice || '').trim().toLowerCase(); + if (!choice) return null; + if (choice === '250' || choice.includes('250')) return '250'; + if (choice === '400' || choice.includes('400')) return '400'; + if (choice === 'mini300' || choice.includes('mini 300') || choice.includes('10%')) return 'mini300'; + return null; + } + + function resolveTarifSourceForReference() { + if (tarif && typeof tarif === 'object') return tarif; + if (rc?.["@expand"]?.tarifRC && typeof rc["@expand"].tarifRC === 'object') return rc["@expand"].tarifRC; + if (rc?.tarifRC && typeof rc.tarifRC === 'object') return rc.tarifRC; + return null; + } + + function getPrimeReferenceState() { + const tarifSource = resolveTarifSourceForReference(); + if (!tarifSource) { + return { + valid: false, + reason: 'Aucun tarif RC sélectionné n\'est rattaché à ce dossier.' + }; + } + + const franchiseKey = normalizeFranchiseChoice(tarifSource.franchiseChoisie); + if (!franchiseKey) { + return { + valid: false, + reason: 'Aucune franchise choisie n\'est disponible dans le tarif RC.' + }; + } + + const primeFieldByFranchise = { + '250': 'primeTotal_250', + '400': 'primeTotal_400', + 'mini300': 'primeTotal_2000' + }; + + const primeField = primeFieldByFranchise[franchiseKey]; + const primeValue = parseValidationNumber(tarifSource[primeField]); + if (!Number.isFinite(primeValue) || primeValue <= 0) { + return { + valid: false, + reason: 'La prime HT de référence du tarif choisi est invalide (0.00, vide ou incohérente).' + }; + } + + return { + valid: true, + franchiseKey, + primeValue + }; + } + + function updatePrimeReferenceRow() { + const primeInput = document.getElementById('primeRefHT'); + const primeError = document.getElementById('primeRefHT-error'); + if (!primeInput || !primeError) return; + + const state = getPrimeReferenceState(); + if (state.valid) { + primeInput.value = formatReferenceAmount(state.primeValue); + primeError.textContent = ''; + primeError.style.display = 'none'; + return; + } + + primeInput.value = 'Non disponible'; + primeError.textContent = state.reason; + primeError.style.display = 'block'; + } + + function setupRCSafeValidation() { + if (!window.RCValidationUtils || typeof window.RCValidationUtils.createGuard !== 'function') { + return; + } + + rcProjetGuard = window.RCValidationUtils.createGuard({ + summaryId: 'rcProjetBlockingSummary', + summaryTitle: 'Impossible d\'enregistrer ou de continuer car :', + blockTargets: ['#projetFormBtn', '#generateDeclinaison', '#generateProject'] + }); + + rcProjetGuard.registerField('#CA', { + profile: 'numeric', + label: "Chiffre d'affaires", + min: 0, + positive: true, + requiredWhen: function () { + return document.querySelector('input[name="cotisation"]:checked')?.value === 'revisable'; + } + }); + + rcProjetGuard.registerField('#cotisationIrreductible', { + profile: 'numeric', + label: 'Cotisation minimale irréductible', + min: 0 + }); + + ['#tauxRCCHT', '#tauxRCCTTC', '#tauxRCEHT', '#tauxRCETTC', '#tauxTotalHT', '#tauxTotalTTC'].forEach((selector) => { + rcProjetGuard.registerField(selector, { + profile: 'numeric', + label: 'Taux de cotisation', + min: 0, + decimals: 3, + activeWhen: function (field) { + return field && field.offsetParent !== null; + } + }); + }); + + ['#cotRCCHT', '#cotRCCTTC', '#cotRCEHT', '#cotRCETTC', '#cotPJHT', '#cotPJTTC', '#cotFraisHT', '#cotFraisTTC', '#cotTotalHT', '#cotTotalTTC'].forEach((selector) => { + rcProjetGuard.registerField(selector, { + profile: 'numeric', + label: 'Cotisation', + min: 0, + activeWhen: function (field) { + return field && field.offsetParent !== null; + } + }); + }); + + rcProjetGuard.registerField('#nomAdditionnel', { + profile: 'text', + label: 'Nom assuré additionnel' + }); + rcProjetGuard.registerField('#adresseAditionnel', { + profile: 'text', + label: 'Adresse assuré additionnel' + }); + rcProjetGuard.registerField('#siretAdditionnel', { + profile: 'integer', + label: 'SIRET assuré additionnel', + min: 0 + }); + + rcProjetGuard.registerField('#marqueVehicule', { + profile: 'text', + label: 'Marque véhicule' + }); + rcProjetGuard.registerField('#genreVehicule', { + profile: 'text', + label: 'Genre véhicule' + }); + rcProjetGuard.registerField('#typeVehicule', { + profile: 'text', + label: 'Type véhicule' + }); + rcProjetGuard.registerField('#immatVehicule', { + profile: 'immat', + label: 'Immatriculation véhicule' + }); + rcProjetGuard.registerField('#capitalVehicule', { + profile: 'numeric', + label: 'Capital véhicule', + min: 0, + positive: true + }); + + rcProjetGuard.observe('#selected-activities input[type="text"]', { + profile: 'numeric', + label: 'Capital à assurer activité', + min: 0, + positive: true, + required: true, + activeWhen: function (field) { + return field && field.offsetParent !== null; + } + }); + + rcProjetGuard.observe('#empTableAdditionnel input[name="nom"]', { + profile: 'text', + label: 'Nom assuré additionnel' + }); + rcProjetGuard.observe('#empTableAdditionnel input[name="adresse"]', { + profile: 'text', + label: 'Adresse assuré additionnel' + }); + rcProjetGuard.observe('#empTableAdditionnel input[name="siret"]', { + profile: 'integer', + label: 'SIRET assuré additionnel', + min: 0 + }); + + rcProjetGuard.observe('#empTableVehicules input[name="marque"], #empTableVehicules input[name="genre"], #empTableVehicules input[name="type"]', { + profile: 'text', + label: 'Désignation véhicule' + }); + rcProjetGuard.observe('#empTableVehicules input[name="immat"]', { + profile: 'immat', + label: 'Immatriculation véhicule' + }); + rcProjetGuard.observe('#empTableVehicules input[name="capital"]', { + profile: 'numeric', + label: 'Capital véhicule', + min: 0, + positive: true + }); + + rcProjetGuard.observe('#tabAdvaloTerrestre input[type="text"], #tabAdvaloMultimodal input[type="text"], #tabAdvaloAerien input[type="text"]', { + profile: 'number_or_consulter', + label: 'Valeur ad valorem', + min: 0, + positive: true, + decimals: 3, + required: false + }); + + rcProjetGuard.registerExternal('prime-reference', function () { + const state = getPrimeReferenceState(); + if (state.valid) { + return { valid: true }; + } + return { + valid: false, + message: state.reason + }; + }); + + rcProjetGuard.refresh(); + } + async function loadModulateurs() { try { const response = await fetch('/rc/modulo/activiteRCC'); @@ -676,6 +957,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio calcAddTaxe('cotRCEHT', 0.09, 'cotRCETTC'); calcCotIrreductible(); calcCotTotal(); + if (rcProjetGuard) rcProjetGuard.refresh(); } function captureRCEState() { @@ -2857,7 +3139,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio if (projet && projet.cotFraisTTC) { document.getElementById("cotFraisTTC").value = projet.cotFraisTTC }; // Populate tableau vehicule - if (!rc || !projet.designationVehicule || Object.keys(projet.designationVehicule).length === 0) { + if (!rc || !projet || !projet.designationVehicule || Object.keys(projet.designationVehicule).length === 0) { console.log("Le JSON est vide, pas de véhicules à pré-remplir."); } else { for (let i = 0; i < projet.designationVehicule.length; i++) { @@ -2907,6 +3189,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio document.getElementById('divAdvaloMultimodal').style.display = "none"; }; } + + updatePrimeReferenceRow(); + if (rcProjetGuard) rcProjetGuard.refresh(); } function populateGrAdvalo(jsonData, tableID) { @@ -3444,6 +3729,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio document.getElementById("cotTotalHT").value = (cotRCCHT + cotRCEHT + cotPJHT + cotFraisHT).toFixed(2); document.getElementById("cotTotalTTC").value = (cotRCCTTC + cotRCETTC + cotPJTTC + cotFraisTTC).toFixed(2); + if (rcProjetGuard) rcProjetGuard.refresh(); } function calcTauxTotal() { @@ -3454,6 +3740,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio document.getElementById("tauxTotalHT").value = (tauxRCCHT + tauxRCEHT).toFixed(3); document.getElementById("tauxTotalTTC").value = (tauxRCCTTC + tauxRCETTC).toFixed(3); + if (rcProjetGuard) rcProjetGuard.refresh(); } function calcCotFromTauxCA(idTaux, idCot) { @@ -3493,6 +3780,12 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Gérer la soumission du formulaire async function handleSubmitForm(event) { event.preventDefault(); + updatePrimeReferenceRow(); + + if (rcProjetGuard && rcProjetGuard.refresh().length > 0) { + M.toast({ html: 'Corrigez les erreurs du formulaire avant de continuer.', classes: 'red' }); + return; + } // Étape 1: Créer d'abord un enregistrement dans projetRC const grilleMultimodal = extractGrilleAdvalo('tabAdvaloMultimodal'); @@ -3807,6 +4100,11 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Fonction exposée pour sauvegarder le projet sans générer le document async function saveProjetRC() { try { + updatePrimeReferenceRow(); + if (rcProjetGuard && rcProjetGuard.refresh().length > 0) { + return { valid: false, message: 'Le formulaire contient encore des erreurs.' }; + } + // Étape 1: Créer d'abord un enregistrement dans projetRC const grilleMultimodal = extractGrilleAdvalo('tabAdvaloMultimodal'); const grilleTerrestre = extractGrilleAdvalo('tabAdvaloTerrestre'); diff --git a/ecole/views/projetformrc.ejs b/ecole/views/projetformrc.ejs index a9cab874..f9dd277b 100644 --- a/ecole/views/projetformrc.ejs +++ b/ecole/views/projetformrc.ejs @@ -1,4 +1,5 @@