diff --git a/ecole/public/js/nav-parcours.js b/ecole/public/js/nav-parcours.js index 0deb1071..fbed7d67 100644 --- a/ecole/public/js/nav-parcours.js +++ b/ecole/public/js/nav-parcours.js @@ -115,23 +115,26 @@ document.addEventListener('DOMContentLoaded', function() { const script = document.createElement('script'); script.src = scriptSrc script.className = 'dynamic-script'; - document.body.appendChild(script); - - if (submenu === "tarif" || submenu == "projet") { - //RELOAD PARCOURS & CONTRAT - loadParcours(numParcours); - const parcours = JSON.parse(sessionStorage.getItem('parcours')); - if (parcours) { - const contratId = parcours?.["@expand"]?.contrat?.id || null; - loadContrat(contratId); + script.onload = async function() { + if (submenu === "tarif" || submenu == "projet") { + try { + // Recharger les donnees avant d'initialiser le script du formulaire. + await loadParcours(numParcours); + const latestParcours = JSON.parse(sessionStorage.getItem('parcours')); + const contratId = latestParcours?.["@expand"]?.contrat?.id || null; + if (contratId) { + await loadContrat(contratId); + } + } catch (error) { + console.error('Erreur lors du rafraichissement parcours/contrat:', error); + } } - } - script.onload = function() { if (typeof window.initSubmenuForm === 'function') { window.initSubmenuForm(); } }; + document.body.appendChild(script); document.querySelectorAll('input').forEach((input) => { input.addEventListener('input', function () { @@ -334,4 +337,4 @@ document.addEventListener('DOMContentLoaded', function() { // Exécutez init et gérez les erreurs potentielles init().catch(error => console.error('Error initializing the form:', error)); -}); \ No newline at end of file +}); diff --git a/ecole/public/js/projet-form-rc.js b/ecole/public/js/projet-form-rc.js index fc26dced..8d9307fd 100644 --- a/ecole/public/js/projet-form-rc.js +++ b/ecole/public/js/projet-form-rc.js @@ -50,9 +50,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio window.projet = projet; console.log("Initialisation pour formulaire projet :", parcours); - console.log("📊 RC:", rc); - console.log("📊 Tarif:", tarif); - console.log("📊 Projet:", projet); + console.log("RC:", rc); + console.log("Tarif:", tarif); + console.log("Projet:", projet); loadModulateurs(); @@ -160,29 +160,12 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio checkRCE: rc?.checkRCE || false, garantiesRCC: garantiesRCC, ca: rc?.chiffreAffaires || tarif?.ca || '', - pj: tarif?.checkPJ || false + pj: tarif?.checkPJ || false, + typeExtConfies: getCurrentTypeExtConfies(), + grilleAdvalorem: collectEffectiveAdvaloremGrid(getCurrentZoneData()) }; } - function checkTarifImpact(fieldType, fieldValue) { - if (!tarif || !tarif.id || !tarifOriginalData) return false; - - switch(fieldType) { - case 'activity': - return checkActivityImpact(fieldValue); - case 'marchandise': - return checkMarchandiseImpact(fieldValue); - case 'zone': - return checkZoneImpact(fieldValue); - case 'activiteCompl': - return checkActiviteComplImpact(fieldValue); - case 'typeCotisation': - return fieldValue === 'forfaitaire' && tarifOriginalData.typeCotisation === 'forfaitaire'; - default: - return false; - } - } - function checkActivityImpact(activityData) { if (!modRCActRCC) return false; @@ -216,42 +199,41 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio return false; } - function checkZoneImpact(zoneData) { - if (!modRCZone) return false; - - function getMaxZoneCoefficient(zones) { - let maxRCC = 1; - let maxRCE = 1; - - const zoneLabels = [ - "France Métropolitaine et pays limitrophes", - "Union Européenne", - "Autres pays européens sauf Russie et Ukraine (y compris UK et Norvège)", - "Pays du Maghreb et Amérique du Nord ( USA / Canada / Mexique )", - "Amérique Centrale et Sud / Caraïbes, Asie et Océanie", - "Afrique Hors Maghreb / Proche Orient / Moyen Orient" - ]; - - for (let i = 1; i <= 6; i++) { - if (zones[`zone${i}`]) { - const zoneKey = zoneLabels[i - 1]; - if (modRCZone[zoneKey]) { - if (typeof modRCZone[zoneKey].modRCC === "number") { - maxRCC = Math.max(maxRCC, modRCZone[zoneKey].modRCC); - } - if (typeof modRCZone[zoneKey].modRCE === "number") { - maxRCE = Math.max(maxRCE, modRCZone[zoneKey].modRCE); - } + function getMaxZoneCoefficient(zones) { + let maxRCC = 1; + let maxRCE = 1; + if (!modRCZone) return { maxRCC, maxRCE }; + + const zoneLabels = [ + "France Métropolitaine et pays limitrophes", + "Union Européenne", + "Autres pays européens sauf Russie et Ukraine (y compris UK et Norvège)", + "Pays du Maghreb et Amérique du Nord ( USA / Canada / Mexique )", + "Amérique Centrale et Sud / Caraïbes, Asie et Océanie", + "Afrique Hors Maghreb / Proche Orient / Moyen Orient" + ]; + + for (let i = 1; i <= 6; i++) { + if (zones[`zone${i}`]) { + const zoneKey = zoneLabels[i - 1]; + if (modRCZone[zoneKey]) { + if (typeof modRCZone[zoneKey].modRCC === "number") { + maxRCC = Math.max(maxRCC, modRCZone[zoneKey].modRCC); + } + if (typeof modRCZone[zoneKey].modRCE === "number") { + maxRCE = Math.max(maxRCE, modRCZone[zoneKey].modRCE); } } } - - return { maxRCC, maxRCE }; } - + + return { maxRCC, maxRCE }; + } + + function checkZoneImpact(zoneData) { + if (!modRCZone) return false; const originalMax = getMaxZoneCoefficient(tarifOriginalData); const currentMax = getMaxZoneCoefficient(zoneData); - return originalMax.maxRCC !== currentMax.maxRCC || originalMax.maxRCE !== currentMax.maxRCE; } @@ -289,64 +271,563 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio return activites; } - let lastChangedField = null; - let lastChangedValue = null; - let isRestoringValue = false; + function getCurrentTypeExtConfies() { + if (document.getElementById('AdValorem')?.checked) return 'ADVALOREM'; + return 'VALEUR DECLAREE'; + } - function showTarifImpactModal(callback, fieldElement, originalValue) { + function captureZoneState() { + const state = {}; + for (let i = 1; i <= 6; i++) { + const zoneEl = document.getElementById(`zone${i}`); + state[`zone${i}`] = { + checked: Boolean(zoneEl?.checked), + disabled: Boolean(zoneEl?.disabled) + }; + } + return state; + } + + function restoreZoneState(state) { + if (!state) return; + isRestoringValue = true; + try { + for (let i = 1; i <= 6; i++) { + const zoneEl = document.getElementById(`zone${i}`); + if (!zoneEl) continue; + const zoneState = state[`zone${i}`] || {}; + zoneEl.checked = Boolean(zoneState.checked); + zoneEl.disabled = Boolean(zoneState.disabled); + } + handleGrAdvalo(); + } finally { + setTimeout(() => { + isRestoringValue = false; + }, 0); + } + } + + function normalizeImpactValue(value) { + if (Array.isArray(value)) { + return value.map((item) => normalizeImpactValue(item)).sort((a, b) => String(a).localeCompare(String(b), 'fr')); + } + if (value && typeof value === 'object') { + const normalized = {}; + Object.keys(value).sort().forEach((key) => { + normalized[key] = normalizeImpactValue(value[key]); + }); + return normalized; + } + if (typeof value === 'string') { + const trimmed = value.trim(); + if (!trimmed) return ''; + const normalizedNumeric = trimmed.replace(/\s/g, '').replace(',', '.'); + const parsed = Number(normalizedNumeric); + if (Number.isFinite(parsed)) return parsed; + return trimmed.toLowerCase(); + } + if (value == null) return null; + return value; + } + + function collectEffectiveAdvaloremGrid(zoneData) { + const activeZones = []; + for (let i = 1; i <= 6; i++) { + if (zoneData?.[`zone${i}`]) activeZones.push(i); + } + + const readGrid = (tableId) => { + const rows = extractGrilleAdvalo(tableId); + const filtered = rows.map((row) => { + const entry = { name: row.name }; + activeZones.forEach((zone) => { + const key = `zone${zone}`; + entry[key] = row[key] || 'Nous consulter'; + }); + return entry; + }); + return filtered; + }; + + return { + terrestre: readGrid('tabAdvaloTerrestre'), + multimodal: readGrid('tabAdvaloMultimodal'), + aerien: readGrid('tabAdvaloAerien') + }; + } + + function checkCotisationImpact() { + const currentCotisation = document.querySelector('input[name="cotisation"]:checked')?.value || null; + return currentCotisation !== (tarifOriginalData?.typeCotisation || null); + } + + function checkCAImpact() { + const currentCA = normalizeImpactValue(document.getElementById('CA')?.value || ''); + const originalCA = normalizeImpactValue(tarifOriginalData?.ca || ''); + return currentCA !== originalCA; + } + + function checkPJImpact() { + const currentPJ = Boolean(document.getElementById('switchPJ')?.checked); + return currentPJ !== Boolean(tarifOriginalData?.pj); + } + + function checkRCEImpact() { + const currentRCE = Boolean(document.getElementById('choixRCE')?.checked); + return currentRCE !== Boolean(tarifOriginalData?.checkRCE); + } + + function checkTypeExtConfiesImpact() { + const currentGaranties = getCurrentGarantieRCCData(); + const originalGaranties = Array.isArray(tarifOriginalData?.garantiesRCC) ? tarifOriginalData.garantiesRCC : []; + const relevantNow = currentGaranties.includes('contenant-confie'); + const relevantBefore = originalGaranties.includes('contenant-confie'); + if (!relevantNow && !relevantBefore) return false; + + return getCurrentTypeExtConfies() !== (tarifOriginalData?.typeExtConfies || null); + } + + function parseCapitalNumber(value) { + const normalized = String(value ?? '') + .trim() + .replace(/\s/g, '') + .replace(',', '.'); + const parsed = Number(normalized); + return Number.isFinite(parsed) ? parsed : 0; + } + + function checkCapitalImpactByField(fieldId, fieldValue) { + if (!fieldId || !tarifOriginalData) return false; + + // Cas spécifique demandé: Voiturier/Loueur restent modifiables sur Projet sans renvoi Tarif. + if (fieldId === 'voiturier' || fieldId === 'loueur') return false; + + const currentValue = parseCapitalNumber(fieldValue); + let originalValue = null; + + switch (fieldId) { + case 'commissionnaire-multimodal': + originalValue = tarifOriginalData.capitalCommissionnaire; + break; + case 'demenageur-particulier': + case 'demenageur-particulier-dommage': + case 'demenageur-particulier-advalorem': + case 'demenageur-entreprise': + case 'demenageur-interne': + originalValue = tarifOriginalData.capitalDemenageur; + break; + case 'entrepositaire-depositaire': + case 'prestataire-logistique': + originalValue = tarifOriginalData.capitalLogistique; + break; + case 'autocariste': + originalValue = tarifOriginalData.capitalAutocariste; + break; + case 'autres': + originalValue = tarifOriginalData.capitalAutres; + break; + default: + return checkActivityImpact(getCurrentActivityData()); + } + + const expected = parseCapitalNumber(originalValue); + return Math.abs(currentValue - expected) > 0.01; + } + + function collectProjetDataForTarifPrefill() { + const getNumberValue = (id) => { + const element = document.getElementById(id); + if (!element) return null; + const raw = String(element.value || '').trim(); + if (!raw || raw.toLowerCase() === 'nous consulter') return null; + const parsed = Number(raw.replace(/\s/g, '').replace(',', '.')); + return Number.isFinite(parsed) ? parsed : null; + }; + + const hasChip = (chipId) => Boolean(document.getElementById(chipId)); + + const zone1El = document.getElementById('zone1'); + const zone2El = document.getElementById('zone2'); + + const payload = { + typeCot: (typeof extractTypeCot === 'function') ? extractTypeCot() : document.querySelector('input[name="typeCot"]:checked')?.value || null, + ca: document.getElementById('CA')?.value || '', + autresRC: Boolean(document.getElementById('choixRCE')?.checked), + pj: Boolean(document.getElementById('switchPJ')?.checked), + + // Zones (zone1/zone2 peuvent etre forcees en disabled) + zone1: Boolean(zone1El && (zone1El.checked || zone1El.disabled)), + zone2: Boolean(zone2El && (zone2El.checked || zone2El.disabled)), + zone3: Boolean(document.getElementById('zone3')?.checked), + zone4: Boolean(document.getElementById('zone4')?.checked), + zone5: Boolean(document.getElementById('zone5')?.checked), + zone6: Boolean(document.getElementById('zone6')?.checked), + + // Activites projet + actVoiturier: hasChip('voiturier-chip'), + actLoueur: hasChip('loueur-chip'), + actMultimodal: hasChip('commissionnaire-multimodal-chip'), + actDemPar: hasChip('demenageur-particulier-chip'), + actDemEntr: hasChip('demenageur-entreprise-chip'), + actDemInterne: hasChip('demenageur-interne-chip'), + actEntDep: hasChip('entrepositaire-depositaire-chip'), + actPrestaLog: hasChip('prestataire-logistique-chip'), + actAutocariste: hasChip('autocariste-chip'), + actAutres: hasChip('autres-chip'), + + // Capitaux associes + valueActVoiturier: getNumberValue('voiturier'), + valueActLoueur: getNumberValue('loueur'), + valueActMultimodal: getNumberValue('commissionnaire-multimodal'), + valueActDemPar: getNumberValue('demenageur-particulier'), + valueActDemEntr: getNumberValue('demenageur-entreprise'), + valueActDemInterne: getNumberValue('demenageur-interne'), + valueActEntDep: getNumberValue('entrepositaire-depositaire'), + valueActPrestaLog: getNumberValue('prestataire-logistique'), + valueActAutocariste: getNumberValue('autocariste'), + valueActAutres: getNumberValue('autres'), + + // Marchandises (globales sur le projet) + marOrdinaire: hasChip('ordinaire-chip'), + marRoulant: hasChip('roulant-chip'), + marEngins: hasChip('engins-chantier-agricole-chip'), + marRoulantDem: hasChip('roulant-demenagement-chip'), + marMobilerUsag: hasChip('mobilier-usages-chip'), + marPerissable: hasChip('perissable-temperature-dirigee-chip'), + marAnimaux: hasChip('animaux-vivant-chip'), + marCiterne: hasChip('citerne-chip'), + marBeton: hasChip('beton-chip'), + marExceptionnels: hasChip('exceptionnels-chip'), + marVrac: hasChip('vrac-chip'), + + // Activites complementaires + activitesVoiturier: getActivitesComplFromForm('actComplVoiturier/Loueur'), + activitesCommissionnaire: getActivitesComplFromForm('actComplCommissionnaire de Transport'), + activitesDemenageur: getActivitesComplFromForm('actComplDéménageur'), + activitesLogistique: getActivitesComplFromForm('actComplLogistique') + }; + + const vehiculeRows = Array.from(document.querySelectorAll('#empTableVehicules tr')).slice(2); + if (vehiculeRows.length > 0) { + payload.nombreVehicules = vehiculeRows.length; + } + + return payload; + } + + function storeProjetDataForTarifPrefill() { + try { + const payload = collectProjetDataForTarifPrefill(); + sessionStorage.setItem('rc_projet_data', JSON.stringify(payload)); + } catch (error) { + console.error('Impossible de stocker les donnees projet pour le tarif:', error); + } + } + + let isRestoringValue = false; + let isTarifImpactModalOpen = false; + + function runWithRestoreGuard(callback) { + isRestoringValue = true; + try { + callback(); + } finally { + setTimeout(() => { + isRestoringValue = false; + }, 0); + } + } + + function getDisplayedStyle(id) { + const element = document.getElementById(id); + return element ? element.style.display : ''; + } + + function setDisplayedStyle(id, value) { + const element = document.getElementById(id); + if (element) { + element.style.display = value ?? ''; + } + } + + function readInputValue(id) { + const element = document.getElementById(id); + return element ? element.value : ''; + } + + function writeInputValue(id, value) { + const element = document.getElementById(id); + if (element) { + element.value = value ?? ''; + } + } + + function applyCotisationLayout(value) { + if (value === "forfaitaire") { + document.getElementById("checkVehicules").style.display = 'block'; + document.getElementById("colTauxAjustement").style.display = 'none'; + document.getElementById("colCA").style.display = 'none'; + document.getElementById("colCotMini").style.display = 'none'; + document.getElementById("colTypeCot").classList.remove('s4'); + document.getElementById("colTypeCot").classList.add('s6'); + document.getElementById("colTypeCot").style.margin = "0 auto"; + document.getElementById("colTypeCot").style.float = "none"; + document.getElementById("colDetailCot").classList.remove('s6'); + document.getElementById("colDetailCot").classList.add('s12'); + } else if (value === "revisable") { + document.getElementById("checkVehicules").style.display = 'none'; + document.getElementById("colTauxAjustement").style.display = 'block'; + document.getElementById("colCA").style.display = 'block'; + document.getElementById("colCotMini").style.display = 'block'; + document.getElementById("colTypeCot").classList.remove('s6'); + document.getElementById("colTypeCot").classList.add('s4'); + document.getElementById("colTypeCot").style.margin = ""; + document.getElementById("colTypeCot").style.float = "left"; + document.getElementById("colDetailCot").classList.remove('s12'); + document.getElementById("colDetailCot").classList.add('s6'); + } + } + + function recalculateAfterCAChange() { + validateField('CA', true); + updateSubmitButtonState('projetForm'); + calcCotFromTauxCA('tauxRCCHT', 'cotRCCHT'); + calcAddTaxe('cotRCCHT', 0, 'cotRCCTTC'); + calcCotFromTauxCA('tauxRCEHT', 'cotRCEHT'); + calcAddTaxe('cotRCEHT', 0.09, 'cotRCETTC'); + calcCotIrreductible(); + calcCotTotal(); + } + + function captureRCEState() { + return { + checked: Boolean(document.getElementById('choixRCE')?.checked), + garantieRCEDisplay: getDisplayedStyle('garantieRCE'), + rce1Display: getDisplayedStyle('RCE1'), + rce2Display: getDisplayedStyle('RCE2'), + cotRCEHT: readInputValue('cotRCEHT'), + cotRCETTC: readInputValue('cotRCETTC'), + tauxRCEHT: readInputValue('tauxRCEHT'), + tauxRCETTC: readInputValue('tauxRCETTC') + }; + } + + function restoreRCEState(state) { + if (!state) return; + runWithRestoreGuard(() => { + const checkbox = document.getElementById('choixRCE'); + if (checkbox) { + checkbox.checked = Boolean(state.checked); + } + + setDisplayedStyle('garantieRCE', state.garantieRCEDisplay); + setDisplayedStyle('RCE1', state.rce1Display); + setDisplayedStyle('RCE2', state.rce2Display); + writeInputValue('cotRCEHT', state.cotRCEHT); + writeInputValue('cotRCETTC', state.cotRCETTC); + writeInputValue('tauxRCEHT', state.tauxRCEHT); + writeInputValue('tauxRCETTC', state.tauxRCETTC); + calcCotTotal(); + }); + } + + function capturePJState() { + return { + checked: Boolean(document.getElementById('switchPJ')?.checked), + pj1Display: getDisplayedStyle('PJ1'), + pj2Display: getDisplayedStyle('PJ2'), + cotPJHT: readInputValue('cotPJHT'), + cotPJTTC: readInputValue('cotPJTTC') + }; + } + + function restorePJState(state) { + if (!state) return; + runWithRestoreGuard(() => { + const checkbox = document.getElementById('switchPJ'); + if (checkbox) { + checkbox.checked = Boolean(state.checked); + } + + setDisplayedStyle('PJ1', state.pj1Display); + setDisplayedStyle('PJ2', state.pj2Display); + writeInputValue('cotPJHT', state.cotPJHT); + writeInputValue('cotPJTTC', state.cotPJTTC); + calcCotTotal(); + }); + } + + function captureCotisationState() { + return { + value: document.querySelector('input[name="cotisation"]:checked')?.value || null + }; + } + + function restoreCotisationState(state) { + if (!state) return; + runWithRestoreGuard(() => { + if (state.value != null) { + const radios = document.querySelectorAll('input[name="cotisation"]'); + radios.forEach((radio) => { + radio.checked = radio.value === String(state.value); + }); + applyCotisationLayout(String(state.value)); + } + }); + } + + function captureCAState() { + return { + value: document.getElementById('CA')?.value || '' + }; + } + + function restoreCAState(state) { + if (!state) return; + runWithRestoreGuard(() => { + const caInput = document.getElementById('CA'); + if (caInput) { + caInput.value = state.value ?? ''; + } + recalculateAfterCAChange(); + }); + } + + function restoreChangedFieldValue(changedElement, previousValue) { + if (!changedElement) return; + + runWithRestoreGuard(() => { + if (typeof changedElement.restore === 'function') { + changedElement.restore(previousValue); + return; + } + + if (changedElement.tagName === 'SELECT') { + if (changedElement.multiple) { + const values = Array.isArray(previousValue) + ? previousValue.map((item) => String(item)) + : []; + Array.from(changedElement.options).forEach((option) => { + option.selected = values.includes(option.value); + }); + } else { + changedElement.value = previousValue ?? ''; + } + + if (window.M && window.M.FormSelect) { + const instance = window.M.FormSelect.getInstance(changedElement); + if (instance) instance.destroy(); + window.M.FormSelect.init(changedElement); + } + + changedElement.dispatchEvent(new Event('change', { bubbles: true })); + return; + } + + if (changedElement.type === 'radio') { + const radioName = changedElement.name; + if (radioName && previousValue != null) { + const radios = document.querySelectorAll(`input[name="${radioName}"]`); + radios.forEach((radio) => { + radio.checked = radio.value === String(previousValue); + }); + const selectedRadio = document.querySelector(`input[name="${radioName}"]:checked`); + if (selectedRadio) { + selectedRadio.dispatchEvent(new Event('change', { bubbles: true })); + } + } else { + changedElement.checked = Boolean(previousValue); + changedElement.dispatchEvent(new Event('change', { bubbles: true })); + } + return; + } + + if (changedElement.type === 'checkbox') { + changedElement.checked = Boolean(previousValue); + changedElement.dispatchEvent(new Event('change', { bubbles: true })); + return; + } + + changedElement.value = previousValue ?? ''; + changedElement.dispatchEvent(new Event('input', { bubbles: true })); + changedElement.dispatchEvent(new Event('change', { bubbles: true })); + }); + } + + function redirectToTarifFromModal() { + storeProjetDataForTarifPrefill(); + const target = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; + window.location.href = target; + } + + function showTarifImpactModal(options = {}) { const modal = document.getElementById('modalModif'); - if (!modal) return; - - const instance = M.Modal.getInstance(modal); - - lastChangedField = fieldElement; - lastChangedValue = originalValue; - + if (!modal || isTarifImpactModalOpen) return false; + + let instance = M.Modal.getInstance(modal); + if (!instance) { + instance = M.Modal.init(modal); + } + const okBtn = document.getElementById('modif-OK'); const noBtn = document.getElementById('modif-NO'); - - if (okBtn) { - okBtn.onclick = function() { - instance.close(); - if (callback) callback(true); - }; - } - - if (noBtn) { - noBtn.onclick = function() { - instance.close(); - isRestoringValue = true; - - if (lastChangedField && lastChangedValue !== null) { - if (lastChangedField.tagName === 'INPUT') { - lastChangedField.value = lastChangedValue; - } else if (lastChangedField.tagName === 'SELECT') { - if (lastChangedField.multiple && Array.isArray(lastChangedValue)) { - Array.from(lastChangedField.options).forEach(opt => { - opt.selected = lastChangedValue.includes(opt.value); - }); - } else { - lastChangedField.value = lastChangedValue; - } - M.FormSelect.init(lastChangedField); - - if (lastChangedField.id === 'activity-selector') { - handleActivitySelection(); - } - } else if (lastChangedField.type === 'checkbox' || lastChangedField.type === 'radio') { - lastChangedField.checked = lastChangedValue; - } - } - - setTimeout(() => { - isRestoringValue = false; - }, 100); - - if (callback) callback(false); - }; - } - + if (!okBtn || !noBtn) return false; + + isTarifImpactModalOpen = true; + instance.options.onCloseEnd = () => { + isTarifImpactModalOpen = false; + }; + + okBtn.onclick = (event) => { + event?.preventDefault?.(); + instance.close(); + if (typeof options.onConfirm === 'function') { + options.onConfirm(); + } + }; + + noBtn.onclick = (event) => { + event?.preventDefault?.(); + instance.close(); + if (typeof options.onCancel === 'function') { + options.onCancel(); + } + }; + instance.open(); + return true; + } + + function promptTarifImpactIfNeeded(context = {}) { + if (isRestoringValue || isTarifImpactModalOpen || !tarif || !tarif.id || !tarifOriginalData) return false; + + const isImpacting = typeof context.isImpacting === 'function' + ? Boolean(context.isImpacting()) + : false; + if (!isImpacting) { + if (typeof context.onNoImpact === 'function') { + context.onNoImpact(); + } + return false; + } + + return showTarifImpactModal({ + onConfirm: () => { + if (typeof context.onConfirm === 'function') { + context.onConfirm(); + return; + } + redirectToTarifFromModal(); + }, + onCancel: () => { + if (typeof context.restore === 'function') { + context.restore(); + } + if (typeof context.onCancel === 'function') { + context.onCancel(); + } + } + }); } function setupTarifImpactListeners() { @@ -356,31 +837,49 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio saveOriginalTarifData(); }, 500); + const registerBeforeActionSnapshot = (element, captureSnapshot) => { + if (!element || typeof captureSnapshot !== 'function') return; + + const save = () => { + if (isRestoringValue || isTarifImpactModalOpen) return; + captureSnapshot(); + }; + + element.addEventListener('pointerdown', save, true); + element.addEventListener('focus', save, true); + element.addEventListener('keydown', function(event) { + if (event.key === ' ' || event.key === 'Spacebar' || event.key === 'Enter') { + save(); + } + }, true); + }; + const activitySelector = document.getElementById('activity-selector'); if (activitySelector) { + let lastActivitySelection = Array.from(activitySelector.selectedOptions).map(opt => opt.value); + + registerBeforeActionSnapshot(activitySelector, () => { + lastActivitySelection = Array.from(activitySelector.selectedOptions).map(opt => opt.value); + }); + activitySelector.addEventListener('change', function(e) { - if (isRestoringValue) return; + if (isRestoringValue) { + handleActivitySelection(); + return; + } setTimeout(() => { if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; - const currentData = getCurrentActivityData(); - if (checkTarifImpact('activity', currentData)) { + const originalSelection = [...lastActivitySelection]; + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkActivityImpact(getCurrentActivityData()), + restore: () => restoreChangedFieldValue(this, originalSelection), + onNoImpact: () => { + lastActivitySelection = Array.from(this.selectedOptions).map(opt => opt.value); + } + })) { e.stopImmediatePropagation(); e.preventDefault(); - const originalSelection = Array.from(this.options).filter(opt => { - const wasSelected = tarifOriginalData.checkVoiturier && (opt.value === 'voiturier' || opt.value === 'loueur') || - tarifOriginalData.checkCommissionnaire && opt.value === 'commissionnaire-multimodal' || - tarifOriginalData.checkDemenageur && (opt.value === 'demenageur-particulier' || opt.value === 'demenageur-entreprise' || opt.value === 'demenageur-interne') || - tarifOriginalData.checkLogistique && (opt.value === 'entrepositaire-depositaire' || opt.value === 'prestataire-logistique') || - tarifOriginalData.checkAutocariste && opt.value === 'autocariste' || - tarifOriginalData.checkAutres && opt.value === 'autres'; - return wasSelected; - }).map(opt => opt.value); - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalSelection); return false; } }, 200); @@ -391,11 +890,9 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio if (marchandiseSelector) { let lastMarchandiseSelection = Array.from(marchandiseSelector.selectedOptions).map(opt => opt.value); - marchandiseSelector.addEventListener('mousedown', function() { - if (!isRestoringValue) { - lastMarchandiseSelection = Array.from(this.selectedOptions).map(opt => opt.value); - } - }, true); + registerBeforeActionSnapshot(marchandiseSelector, () => { + lastMarchandiseSelection = Array.from(marchandiseSelector.selectedOptions).map(opt => opt.value); + }); marchandiseSelector.addEventListener('change', function(e) { if (isRestoringValue) { @@ -407,44 +904,52 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio setTimeout(() => { if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; - const currentData = getCurrentMarchandiseData(); - if (checkTarifImpact('marchandise', currentData)) { + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkMarchandiseImpact(getCurrentMarchandiseData()), + restore: () => restoreChangedFieldValue(this, originalSelection), + onNoImpact: () => { + lastMarchandiseSelection = Array.from(this.selectedOptions).map(opt => opt.value); + handleMarchandiseSelection(); + } + })) { e.stopImmediatePropagation(); e.preventDefault(); - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalSelection); - } else { - lastMarchandiseSelection = Array.from(this.selectedOptions).map(opt => opt.value); - handleMarchandiseSelection(); + return; } }, 200); }, true); } + const zoneStateBeforeChange = {}; for (let i = 1; i <= 6; i++) { const zoneCheckbox = document.getElementById(`zone${i}`); if (zoneCheckbox) { + registerBeforeActionSnapshot(zoneCheckbox, () => { + zoneStateBeforeChange[zoneCheckbox.id] = captureZoneState(); + }); + zoneCheckbox.addEventListener('change', function(e) { if (isRestoringValue) return; - - const checkboxId = this.id; - const originalChecked = tarifOriginalData[checkboxId] || false; - + + let previousZones = zoneStateBeforeChange[this.id]; + // Fallback robuste: certains clics passent par le label et ne déclenchent pas le snapshot pointer/focus. + if (!previousZones || previousZones?.[this.id]?.checked === this.checked) { + previousZones = captureZoneState(); + if (previousZones[this.id]) { + previousZones[this.id].checked = !this.checked; + } + } + setTimeout(() => { if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; - const currentData = getCurrentZoneData(); - if (checkTarifImpact('zone', currentData)) { + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkZoneImpact(getCurrentZoneData()), + restore: () => restoreZoneState(previousZones) + })) { e.stopImmediatePropagation(); e.preventDefault(); - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalChecked); } + zoneStateBeforeChange[this.id] = null; }, 200); }, true); } @@ -452,20 +957,28 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio const garantieRCCSelector = document.getElementById('garantieRCC-selector'); if (garantieRCCSelector) { + let lastGarantieRCCSelection = Array.from(garantieRCCSelector.selectedOptions).map(opt => opt.value); + + registerBeforeActionSnapshot(garantieRCCSelector, () => { + lastGarantieRCCSelection = Array.from(garantieRCCSelector.selectedOptions).map(opt => opt.value); + }); + garantieRCCSelector.addEventListener('change', function(e) { if (isRestoringValue) return; setTimeout(() => { if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; - const currentData = getCurrentGarantieRCCData(); - if (checkGarantieRCCImpact(currentData)) { + const originalSelection = [...lastGarantieRCCSelection]; + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkGarantieRCCImpact(getCurrentGarantieRCCData()), + restore: () => restoreChangedFieldValue(this, originalSelection), + onNoImpact: () => { + lastGarantieRCCSelection = Array.from(this.selectedOptions).map(opt => opt.value); + } + })) { e.stopImmediatePropagation(); - const originalSelection = Array.from(this.selectedOptions).map(opt => opt.value); - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalSelection); + e.preventDefault(); + return; } }, 200); }, true); @@ -483,21 +996,23 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio if (container) { const checkboxes = container.querySelectorAll('input[type="checkbox"]'); checkboxes.forEach(checkbox => { + registerBeforeActionSnapshot(checkbox, () => { + checkbox.dataset.tarifPrevChecked = String(checkbox.checked); + }); + checkbox.addEventListener('change', function(e) { if (isRestoringValue) return; - const originalChecked = !this.checked; + const originalChecked = this.dataset.tarifPrevChecked === 'true'; setTimeout(() => { if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; - const currentData = getCurrentActiviteComplData(); - if (checkTarifImpact('activiteCompl', currentData)) { + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkActiviteComplImpact(getCurrentActiviteComplData()), + restore: () => restoreChangedFieldValue(this, originalChecked) + })) { e.stopImmediatePropagation(); - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalChecked); + e.preventDefault(); } }, 200); }, true); @@ -506,20 +1021,27 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio }); const radioButtonsCot = document.getElementsByName('cotisation'); + let previousCotisationState = captureCotisationState(); for (let i = 0; i < radioButtonsCot.length; i++) { + registerBeforeActionSnapshot(radioButtonsCot[i], () => { + previousCotisationState = captureCotisationState(); + }); + radioButtonsCot[i].addEventListener('change', function(e) { if (isRestoringValue) return; setTimeout(() => { if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; - const originalValue = tarifOriginalData.typeCotisation; - if (this.value !== originalValue) { + const stateToRestore = previousCotisationState; + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkCotisationImpact(), + restore: () => restoreCotisationState(stateToRestore), + onNoImpact: () => { + previousCotisationState = captureCotisationState(); + } + })) { e.stopImmediatePropagation(); - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalValue); + e.preventDefault(); } }, 200); }, true); @@ -529,59 +1051,46 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio 'demenageur-particulier-dommage', 'demenageur-particulier-advalorem', 'demenageur-entreprise', 'demenageur-interne', 'entrepositaire-depositaire', 'prestataire-logistique', 'autocariste', 'autres']; - - capitalFields.forEach(fieldId => { - const field = document.getElementById(fieldId); - if (field) { - let originalValue = field.value || field.dataset.defaultValue || ''; - field.addEventListener('input', function(e) { - if (isRestoringValue) return; - - setTimeout(() => { - if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; - const currentData = getCurrentActivityData(); - if (checkTarifImpact('activity', currentData)) { - const currentValue = this.value; - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalValue); - } else { + + const bindCapitalFieldImpact = (field) => { + if (!field || field.hasAttribute('data-tarif-listener')) return; + field.setAttribute('data-tarif-listener', 'true'); + let originalValue = field.value || field.dataset.defaultValue || ''; + + field.addEventListener('focus', function() { + if (isRestoringValue) return; + originalValue = this.value; + }, true); + + field.addEventListener('input', function(e) { + if (isRestoringValue) return; + + setTimeout(() => { + if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkCapitalImpactByField(this.id, this.value), + restore: () => restoreChangedFieldValue(this, originalValue), + onNoImpact: () => { originalValue = this.value; } - }, 200); - }); - } + })) { + e.stopImmediatePropagation(); + e.preventDefault(); + return; + } + }, 200); + }); + }; + + capitalFields.forEach((fieldId) => { + bindCapitalFieldImpact(document.getElementById(fieldId)); }); const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.addedNodes.length) { capitalFields.forEach(fieldId => { - const field = document.getElementById(fieldId); - if (field && !field.hasAttribute('data-tarif-listener')) { - field.setAttribute('data-tarif-listener', 'true'); - let originalValue = field.value || field.dataset.defaultValue || ''; - field.addEventListener('input', function(e) { - if (isRestoringValue) return; - - setTimeout(() => { - if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; - const currentData = getCurrentActivityData(); - if (checkTarifImpact('activity', currentData)) { - const currentValue = this.value; - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalValue); - } else { - originalValue = this.value; - } - }, 200); - }); - } + bindCapitalFieldImpact(document.getElementById(fieldId)); }); } }); @@ -594,25 +1103,94 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio const switchPJ = document.getElementById('switchPJ'); if (switchPJ) { + let previousPJState = capturePJState(); + registerBeforeActionSnapshot(switchPJ, () => { + previousPJState = capturePJState(); + }); + switchPJ.addEventListener('change', function(e) { if (isRestoringValue) return; - - const originalChecked = tarifOriginalData.pj || false; - + + let stateToRestore = previousPJState; + if (!stateToRestore || stateToRestore.checked === this.checked) { + stateToRestore = capturePJState(); + stateToRestore.checked = !this.checked; + } + setTimeout(() => { if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; - if (this.checked !== originalChecked) { + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkPJImpact(), + restore: () => restorePJState(stateToRestore), + onNoImpact: () => { + previousPJState = capturePJState(); + } + })) { e.stopImmediatePropagation(); e.preventDefault(); - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalChecked); } }, 200); }, true); } + + const choixRCE = document.getElementById('choixRCE'); + if (choixRCE) { + let previousRCEState = captureRCEState(); + registerBeforeActionSnapshot(choixRCE, () => { + previousRCEState = captureRCEState(); + }); + + choixRCE.addEventListener('change', function(e) { + if (isRestoringValue) return; + + let stateToRestore = previousRCEState; + if (!stateToRestore || stateToRestore.checked === this.checked) { + stateToRestore = captureRCEState(); + stateToRestore.checked = !this.checked; + } + + setTimeout(() => { + if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkRCEImpact(), + restore: () => restoreRCEState(stateToRestore), + onNoImpact: () => { + previousRCEState = captureRCEState(); + } + })) { + e.stopImmediatePropagation(); + e.preventDefault(); + } + }, 200); + }, true); + } + + ['AdValorem', 'ValeurDeclaree'].forEach((id) => { + const radio = document.getElementById(id); + if (!radio) return; + + registerBeforeActionSnapshot(radio, () => { + radio.dataset.tarifPrevType = document.querySelector('input[name="type"]:checked')?.value || ''; + }); + + radio.addEventListener('change', function(e) { + if (isRestoringValue) return; + + setTimeout(() => { + if (!tarif || !tarif.id || !tarifOriginalData || isRestoringValue) return; + const previousType = this.dataset.tarifPrevType || ''; + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkTypeExtConfiesImpact(), + restore: () => restoreChangedFieldValue(this, previousType) + })) { + e.stopImmediatePropagation(); + e.preventDefault(); + } + }, 200); + }, true); + }); + + // Décision produit: pas de modal sur les zones textuelles de grille ad valorem. } function checkGarantieRCCImpact(garantieData) { @@ -681,6 +1259,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio function getCurrentGarantieRCCData() { const selector = document.getElementById('garantieRCC-selector'); + if (!selector) return []; return Array.from(selector.selectedOptions).map(opt => opt.value); } @@ -691,11 +1270,14 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio function prefillFromTarif() { if (!tarif || !rc) { - console.log('⚠️ Pas de données tarif/rc pour pré-remplir'); + console.log('Pas de donnees tarif/rc pour pre-remplir'); return; } - console.log('📝 Pré-remplissage depuis tarif...', { rc, tarif }); + // Geler la détection le temps du pré-remplissage auto. + isRestoringValue = true; + + console.log('Pre-remplissage depuis tarif...', { rc, tarif }); function parseArray(value) { if (Array.isArray(value)) return value; @@ -716,7 +1298,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Voiturier if (rc.checkVoiturier && !projet?.actVoiturier && activitySelector) { - console.log(' ✓ Sélection Voiturier, capital:', rc.capitalVoiturier); + console.log(' Selection Voiturier, capital:', rc.capitalVoiturier); const voiturierOption = activitySelector.querySelector('option[value="voiturier"]'); if (voiturierOption) { voiturierOption.selected = true; @@ -728,7 +1310,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Loueur (souvent coché avec Voiturier) if (rc.checkVoiturier && !projet?.actLoueur && activitySelector) { - console.log(' ✓ Sélection Loueur (car Voiturier coché)'); + console.log(' Selection Loueur (car Voiturier coche)'); const loueurOption = activitySelector.querySelector('option[value="loueur"]'); if (loueurOption) { loueurOption.selected = true; @@ -737,7 +1319,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Commissionnaire → Commissionnaire multimodal if (rc.checkCommissionnaire && !projet?.actMultimodal && activitySelector) { - console.log(' ✓ Sélection Commissionnaire multimodal, capital:', rc.capitalCommissionnaire); + console.log(' Selection Commissionnaire multimodal, capital:', rc.capitalCommissionnaire); const multimodalOption = activitySelector.querySelector('option[value="commissionnaire-multimodal"]'); if (multimodalOption) { multimodalOption.selected = true; @@ -749,7 +1331,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Déménageur → Déménageur d'entreprises (pas particuliers) if (rc.checkDemenageur && !projet?.actDemEntr && activitySelector) { - console.log(' ✓ Sélection Déménageur d\'entreprises, capital:', rc.capitalDemenageur); + console.log(' Selection Demenageur d\'entreprises, capital:', rc.capitalDemenageur); const demenageurOption = activitySelector.querySelector('option[value="demenageur-entreprise"]'); if (demenageurOption) { demenageurOption.selected = true; @@ -761,7 +1343,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Logistique → Prestataire logistique if (rc.checkLogistique && !projet?.actPrestaLog && activitySelector) { - console.log(' ✓ Sélection Prestataire logistique, capital:', rc.capitalLogistique); + console.log(' Selection Prestataire logistique, capital:', rc.capitalLogistique); const logistiqueOption = activitySelector.querySelector('option[value="prestataire-logistique"]'); if (logistiqueOption) { logistiqueOption.selected = true; @@ -773,7 +1355,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Autocariste if (rc.checkAutocariste && activitySelector) { - console.log(' ✓ Sélection Autocariste, capital:', rc.capitalAutocariste); + console.log(' Selection Autocariste, capital:', rc.capitalAutocariste); const autocaristeOption = activitySelector.querySelector('option[value="autocariste"]'); if (autocaristeOption) { autocaristeOption.selected = true; @@ -785,7 +1367,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Autres if (rc.checkAutres && activitySelector) { - console.log(' ✓ Sélection Autres activités, capital:', rc.capitalAutres); + console.log(' Selection Autres activites, capital:', rc.capitalAutres); const autresOption = activitySelector.querySelector('option[value="autres"]'); if (autresOption) { autresOption.selected = true; @@ -797,7 +1379,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // IMPORTANT : Trigger change event pour que Materialize mette à jour l'affichage if (activitySelector) { - console.log(' 🔄 Trigger change event sur activity-selector'); + console.log(' Trigger change event sur activity-selector'); const changeEvent = new Event('change', { bubbles: true }); activitySelector.dispatchEvent(changeEvent); @@ -805,7 +1387,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio setTimeout(() => { if (window.M && window.M.FormSelect) { window.M.FormSelect.init(activitySelector); - console.log(' ✅ Materialize FormSelect réinitialisé'); + console.log(' Materialize FormSelect reinitialise'); } }, 100); } @@ -823,7 +1405,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio ]; const uniqueMarchandises = [...new Set(allMarchandises)]; - console.log(' 📦 Marchandises:', uniqueMarchandises); + console.log(' Marchandises:', uniqueMarchandises); const marchandiseMapping = { 'Marchandises ordinaires': 'ordinaire', @@ -878,42 +1460,42 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio const zone1El = document.getElementById("zone1"); if (zone1El) { zone1El.checked = rc.zone1; - console.log(' ✓ Zone 1:', rc.zone1); + console.log(' Zone 1:', rc.zone1); } } if (rc.zone2 !== undefined && !projet?.zone2) { const zone2El = document.getElementById("zone2"); if (zone2El) { zone2El.checked = rc.zone2; - console.log(' ✓ Zone 2:', rc.zone2); + console.log(' Zone 2:', rc.zone2); } } if (rc.zone3 !== undefined && !projet?.zone3) { const zone3El = document.getElementById("zone3"); if (zone3El) { zone3El.checked = rc.zone3; - console.log(' ✓ Zone 3:', rc.zone3); + console.log(' Zone 3:', rc.zone3); } } if (rc.zone4 !== undefined && !projet?.zone4) { const zone4El = document.getElementById("zone4"); if (zone4El) { zone4El.checked = rc.zone4; - console.log(' ✓ Zone 4:', rc.zone4); + console.log(' Zone 4:', rc.zone4); } } if (rc.zone5 !== undefined && !projet?.zone5) { const zone5El = document.getElementById("zone5"); if (zone5El) { zone5El.checked = rc.zone5; - console.log(' ✓ Zone 5:', rc.zone5); + console.log(' Zone 5:', rc.zone5); } } if (rc.zone6 !== undefined && !projet?.zone6) { const zone6El = document.getElementById("zone6"); if (zone6El) { zone6El.checked = rc.zone6; - console.log(' ✓ Zone 6:', rc.zone6); + console.log(' Zone 6:', rc.zone6); } } @@ -923,7 +1505,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio const radioCot = document.querySelector(`input[name="typeCot"][value="${rc.typeCotisation}"]`); if (radioCot) { radioCot.checked = true; - console.log(' ✓ Type cotisation:', rc.typeCotisation); + console.log(' Type cotisation:', rc.typeCotisation); } } @@ -933,7 +1515,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio const caEl = document.getElementById("CA"); if (caEl) { caEl.value = rc.chiffreAffaires; - console.log(' ✓ CA:', rc.chiffreAffaires); + console.log(' CA:', rc.chiffreAffaires); } } @@ -942,7 +1524,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio const hasRCEFromRC = rc && rc.checkRCE; const hasRCEFromTarif = tarif && tarif.checkRCE; if (hasRCEFromRC || hasRCEFromTarif) { - console.log(' ✓ RCE activée (checkRCE)'); + console.log(' RCE activee (checkRCE)'); const choixRCEEl = document.getElementById("choixRCE"); if (choixRCEEl) { choixRCEEl.checked = true; @@ -958,7 +1540,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // ===== PROTECTION JURIDIQUE ===== if (tarif && tarif.checkPJ && !projet?.pj) { - console.log(' ✓ Protection Juridique activée'); + console.log(' Protection Juridique activee'); const switchPJEl = document.getElementById("switchPJ"); if (switchPJEl) { switchPJEl.checked = true; @@ -989,7 +1571,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Appliquer les capitaux APRÈS trigger (les inputs sont créés dynamiquement) setTimeout(() => { - console.log(' 💰 Application des capitaux sur les inputs créés...'); + console.log(' Application des capitaux sur les inputs crees...'); // Mapping des activités vers leurs capitaux const capitalMapping = { @@ -1000,18 +1582,33 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio 'autocariste': rc.capitalAutocariste, 'autres': rc.capitalAutres }; + // Champs qui doivent toujours refléter la dernière modif faite côté Tarif. + const forceTarifSyncFields = new Set([ + 'commissionnaire-multimodal', + 'demenageur-entreprise', + 'prestataire-logistique', + 'autocariste', + 'autres' + ]); for (const [activity, capital] of Object.entries(capitalMapping)) { - if (capital) { - const input = document.getElementById(activity); - if (input && !input.value) { - input.value = capital; - console.log(` ├─ ${activity}: ${capital}`); - } + if (capital == null || capital === '') continue; + + const input = document.getElementById(activity); + const shouldForce = forceTarifSyncFields.has(activity); + if (input && (shouldForce || !input.value)) { + input.value = capital; + console.log(` ├─ ${activity}: ${capital}`); + } + + // Garde aussi la valeur par défaut sur le conteneur, utile si l'input est recréé ensuite. + const selectCarrier = document.getElementById(`select-${activity}`); + if (selectCarrier && (shouldForce || !selectCarrier.dataset.defaultValue)) { + selectCarrier.dataset.defaultValue = String(capital); } } - console.log(' ✅ Capitaux appliqués'); + console.log(' Capitaux appliques'); }, 300); // Autres champs (garanties RCC, etc.) @@ -1119,6 +1716,11 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio if (marchandiseSelector) { marchandiseSelector.dispatchEvent(new Event('change')); } + + // Réactiver la détection juste après les dispatchs pour conserver le contrôle. + setTimeout(() => { + isRestoringValue = false; + }, 0); } // Configuration des écouteurs d'événements @@ -1212,18 +1814,8 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio calcCotTotal(); }); - function restoreZonesFromOriginal() { - const orig = tarifOriginalData || {}; - ['zone1','zone2','zone3','zone4','zone5','zone6'].forEach(z => { - const el = document.getElementById(z); - if (el) { - el.disabled = false; - el.checked = Boolean(orig[z]); - } - }); - } - document.getElementById('btnMondeEntier').addEventListener('click', function () { + const previousZones = captureZoneState(); document.getElementById('zone1').checked = true; document.getElementById('zone1').disabled = true; document.getElementById('zone2').checked = true; @@ -1233,24 +1825,15 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio document.getElementById('zone5').checked = true; document.getElementById('zone6').checked = true; - // Détection impact tarif : zones - if (tarif && tarif.id && tarifOriginalData) { - const currentData = getCurrentZoneData(); - if (checkTarifImpact('zone', currentData)) { - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, null, null); - // Revenir à l'état original visuel - restoreZonesFromOriginal(); - } - } - handleGrAdvalo(); + promptTarifImpactIfNeeded({ + isImpacting: () => checkZoneImpact(getCurrentZoneData()), + restore: () => restoreZoneState(previousZones) + }); }); document.getElementById('btnReset').addEventListener('click', function () { + const previousZones = captureZoneState(); document.getElementById('zone1').checked = false; document.getElementById('zone1').disabled = false; document.getElementById('zone2').checked = false; @@ -1260,21 +1843,11 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio document.getElementById('zone5').checked = false; document.getElementById('zone6').checked = false; - // Détection impact tarif : zones - if (tarif && tarif.id && tarifOriginalData) { - const currentData = getCurrentZoneData(); - if (checkTarifImpact('zone', currentData)) { - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, null, null); - // Revenir à l'état original visuel - restoreZonesFromOriginal(); - } - } - handleGrAdvalo(); + promptTarifImpactIfNeeded({ + isImpacting: () => checkZoneImpact(getCurrentZoneData()), + restore: () => restoreZoneState(previousZones) + }); }); document.getElementById('btnZone1').addEventListener('click', function () { @@ -1321,18 +1894,6 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio document.getElementById('zone1').addEventListener('click', function () { handleGrAdvalo(); - - if (tarif && tarif.id && tarifOriginalData) { - const currentData = getCurrentZoneData(); - if (checkTarifImpact('zone', currentData)) { - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, null, null); - restoreZonesFromOriginal(); - } - } }); document.getElementById('zone2').addEventListener('click', function () { @@ -1345,18 +1906,6 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio } handleGrAdvalo(); - - if (tarif && tarif.id && tarifOriginalData) { - const currentData = getCurrentZoneData(); - if (checkTarifImpact('zone', currentData)) { - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, null, null); - restoreZonesFromOriginal(); - } - } }); document.getElementById('zone3').addEventListener('click', function () { @@ -1373,94 +1922,24 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio } handleGrAdvalo(); - - if (tarif && tarif.id && tarifOriginalData) { - const currentData = getCurrentZoneData(); - if (checkTarifImpact('zone', currentData)) { - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, null, null); - restoreZonesFromOriginal(); - } - } }); document.getElementById('zone4').addEventListener('click', function () { handleGrAdvalo(); - - if (tarif && tarif.id && tarifOriginalData) { - const currentData = getCurrentZoneData(); - if (checkTarifImpact('zone', currentData)) { - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, null, null); - restoreZonesFromOriginal(); - } - } }); document.getElementById('zone5').addEventListener('click', function () { handleGrAdvalo(); - - if (tarif && tarif.id && tarifOriginalData) { - const currentData = getCurrentZoneData(); - if (checkTarifImpact('zone', currentData)) { - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, null, null); - restoreZonesFromOriginal(); - } - } }); document.getElementById('zone6').addEventListener('click', function () { handleGrAdvalo(); - - if (tarif && tarif.id && tarifOriginalData) { - const currentData = getCurrentZoneData(); - if (checkTarifImpact('zone', currentData)) { - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, null, null); - restoreZonesFromOriginal(); - } - } }); var radioButtonsCot = document.getElementsByName('cotisation'); for (var i = 0; i < radioButtonsCot.length; i++) { radioButtonsCot[i].addEventListener('change', function () { - if (this.value == "forfaitaire") { - document.getElementById("checkVehicules").style.display = 'block'; - document.getElementById("colTauxAjustement").style.display = 'none'; - document.getElementById("colCA").style.display = 'none'; - document.getElementById("colCotMini").style.display = 'none'; - document.getElementById("colTypeCot").classList.remove('s4'); - document.getElementById("colTypeCot").classList.add('s6'); - document.getElementById("colTypeCot").style.margin = "0 auto"; - document.getElementById("colTypeCot").style.float = "none"; - document.getElementById("colDetailCot").classList.remove('s6'); - document.getElementById("colDetailCot").classList.add('s12'); - } else if (this.value == "revisable") { - document.getElementById("checkVehicules").style.display = 'none'; - document.getElementById("colTauxAjustement").style.display = 'block'; - document.getElementById("colCA").style.display = 'block'; - document.getElementById("colCotMini").style.display = 'block'; - document.getElementById("colTypeCot").classList.remove('s6'); - document.getElementById("colTypeCot").classList.add('s4'); - document.getElementById("colTypeCot").style.margin = ""; - document.getElementById("colTypeCot").style.float = "left"; - document.getElementById("colDetailCot").classList.remove('s12'); - document.getElementById("colDetailCot").classList.add('s6'); - } + applyCotisationLayout(this.value); }); }; @@ -1550,42 +2029,33 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio updateSubmitButtonState('projetForm'); }); - document.getElementById('CA').addEventListener('input', function () { + const caInput = document.getElementById('CA'); + let previousCAState = captureCAState(); + if (caInput) { + caInput.addEventListener('focus', function () { + if (isRestoringValue) return; + previousCAState = captureCAState(); + }, true); + } + + document.getElementById('CA').addEventListener('input', function (e) { if (isRestoringValue) { - validateField('CA', true); - updateSubmitButtonState('projetForm'); - calcCotFromTauxCA('tauxRCCHT', 'cotRCCHT'); - calcAddTaxe('cotRCCHT', 0, 'cotRCCTTC'); - calcCotFromTauxCA('tauxRCEHT', 'cotRCEHT'); - calcAddTaxe('cotRCEHT', 0.09, 'cotRCETTC'); - calcCotIrreductible(); - calcCotTotal(); + recalculateAfterCAChange(); return; } - - if (tarif && tarif.id && tarifOriginalData) { - const currentValue = this.value.trim(); - const originalValue = tarifOriginalData.ca || ''; - - if (currentValue !== originalValue) { - const originalValueToRestore = originalValue; - showTarifImpactModal((confirmed) => { - if (confirmed) { - window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=tarif`; - } - }, this, originalValueToRestore); - return; + + if (promptTarifImpactIfNeeded({ + isImpacting: () => checkCAImpact(), + restore: () => restoreCAState(previousCAState), + onNoImpact: () => { + previousCAState = captureCAState(); + recalculateAfterCAChange(); } + })) { + e.stopImmediatePropagation(); + e.preventDefault(); + return; } - - validateField('CA', true); - updateSubmitButtonState('projetForm'); - calcCotFromTauxCA('tauxRCCHT', 'cotRCCHT'); - calcAddTaxe('cotRCCHT', 0, 'cotRCCTTC'); - calcCotFromTauxCA('tauxRCEHT', 'cotRCEHT'); - calcAddTaxe('cotRCEHT', 0.09, 'cotRCETTC'); - calcCotIrreductible(); - calcCotTotal(); }); document.getElementById('cotisationIrreductible').addEventListener('input', function () { @@ -1835,10 +2305,13 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio } if (currentSelection.length == 1) { - document.getElementById('activity-selector').querySelector('option[value="voiturier"]').selected = true; - document.getElementById('activity-selector').querySelector('option[value="loueur"]').selected = true; - document.getElementById('activity-selector').dispatchEvent(new Event('change')); - M.FormSelect.init(document.querySelectorAll('select')); + const isExistingDossier = Boolean(rc?.id || projet?.id || tarif?.id); + if (!isExistingDossier) { + document.getElementById('activity-selector').querySelector('option[value="voiturier"]').selected = true; + document.getElementById('activity-selector').querySelector('option[value="loueur"]').selected = true; + document.getElementById('activity-selector').dispatchEvent(new Event('change')); + M.FormSelect.init(document.querySelectorAll('select')); + } document.getElementById('mini-activity-error').style.display = "block"; } else { document.getElementById("mini-activity-error").style.display = "none"; @@ -3439,4 +3912,5 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Exposer les fonctions globalement pour y accéder depuis l'extérieur window.initSubmenuForm = init; window.saveProjetRC = saveProjetRC; -})(); \ No newline at end of file + window.collectProjetDataForTarifPrefill = collectProjetDataForTarifPrefill; +})(); diff --git a/ecole/public/js/rc-data-manager.js b/ecole/public/js/rc-data-manager.js index 283e2f54..671ce642 100644 --- a/ecole/public/js/rc-data-manager.js +++ b/ecole/public/js/rc-data-manager.js @@ -1,15 +1,5 @@ /** - * ═══════════════════════════════════════════════════════════════════════════ - * RC DATA MANAGER - * ═══════════════════════════════════════════════════════════════════════════ - * - * Ce module gère la collecte, la sauvegarde et le pré-remplissage des données - * RC entre les formulaires Tarif et Projet. - * - * @requires rc-sync-utils.js - * @author AXA Transport Team - * @version 2.0.0 - * @since 2026-02-17 + * Utilitaires de synchro RC (collecte + pré-remplissage Tarif/Projet). */ (function(window) { @@ -22,10 +12,8 @@ // ═══════════════════════════════════════════════════════════════════════ /** - * Mapping complet des champs entre Tarif et Projet. - * Permet la synchronisation bidirectionnelle. - * - * Structure: { tarifFieldId: projetFieldId } + * Mapping des champs Tarif <-> Projet. + * Format: { idTarif: idProjet }. */ const FIELD_MAPPING = { // Informations générales @@ -87,14 +75,7 @@ // ═══════════════════════════════════════════════════════════════════════ /** - * Collecte toutes les données du formulaire Tarif RC. - * Cette fonction est exhaustive et capture TOUS les champs nécessaires. - * - * @returns {Object} Objet contenant toutes les données du tarif - * - * @example - * const tarifData = collectAllTarifData(); - * console.log(tarifData.ca, tarifData.zones, tarifData.marchandises); + * Lit l'ensemble des champs utiles depuis le formulaire tarif. */ function collectAllTarifData() { // Références flexibles aux éléments @@ -211,7 +192,8 @@ // ═══ SINISTRALITÉ ═══ sinistralite: { nombre3ans: toNumber(getValue('sinistre')), - montant3ans: 0 // TODO: ajouter si champ existe + // Pas de champ montant dans le formulaire actuel. + montant3ans: 0 }, // ═══ RÉSULTATS DE CALCUL ═══ @@ -254,16 +236,12 @@ commentaire: getValue('commentaire') || '' }; - console.log('📊 Données Tarif collectées:', data); + console.log('Donnees tarif collectees:', data); return data; } /** - * Fonction helper pour collecter les activités complémentaires depuis le formulaire. - * - * @param {string} typeActivite - Type d'activité ('voiturier', 'commissionnaire', etc.) - * @returns {string} JSON array des activités cochées - * @private + * Récupère les activités complémentaires cochées pour un type d'activité. */ function collectActivitesComplJSON(typeActivite) { let name; @@ -354,7 +332,7 @@ return; } - console.log('📝 Pré-remplissage Projet depuis Tarif...'); + console.log('Pre-remplissage Projet depuis Tarif...'); try { // ═══ INFORMATIONS GÉNÉRALES ═══ @@ -362,7 +340,7 @@ // CA if (tarifData.ca) { setValue('CA', tarifData.ca); - console.log(' ✓ CA:', tarifData.ca); + console.log(' CA:', tarifData.ca); } // Type de cotisation @@ -370,14 +348,14 @@ const radio = document.querySelector(`input[name="typeCot"][value="${tarifData.typeCotisation}"]`); if (radio) { radio.checked = true; - console.log(' ✓ Type cotisation:', tarifData.typeCotisation); + console.log(' Type cotisation:', tarifData.typeCotisation); } } // Nombre de véhicules if (tarifData.nombreVehicules) { setValue('nombreVehicules', tarifData.nombreVehicules); - console.log(' ✓ Véhicules:', tarifData.nombreVehicules); + console.log(' Vehicules:', tarifData.nombreVehicules); } // ═══ ACTIVITÉS ═══ @@ -416,7 +394,7 @@ const event = new Event('change', { bubbles: true }); activitySelector.dispatchEvent(event); - console.log(' ✓ Activités:', activitesToAdd.length); + console.log(' Activites:', activitesToAdd.length); } // ═══ MARCHANDISES ═══ @@ -443,7 +421,7 @@ const event = new Event('change', { bubbles: true }); marchandiseSelector.dispatchEvent(event); - console.log(' ✓ Marchandises:', marchandisesToSelect.length); + console.log(' Marchandises:', marchandisesToSelect.length); } // ═══ ZONES GÉOGRAPHIQUES ═══ @@ -457,7 +435,7 @@ zonesCount++; } }); - console.log(' ✓ Zones:', zonesCount); + console.log(' Zones:', zonesCount); } // ═══ PROTECTION JURIDIQUE ═══ @@ -466,7 +444,7 @@ const switchPJ = document.getElementById('switchPJ'); if (switchPJ) { switchPJ.checked = true; - console.log(' ✓ PJ activée'); + console.log(' PJ activee'); // Afficher la section PJ const pjSection = document.getElementById('pj-section'); @@ -480,7 +458,7 @@ const choixRCE = document.getElementById('choixRCE'); if (choixRCE) { choixRCE.checked = true; - console.log(' ✓ RCE activée'); + console.log(' RCE activee'); // Afficher la section RCE const rceSection = document.getElementById('section-rce'); @@ -502,7 +480,7 @@ setValue('vehiculesTPPC', tarifData.garantiesAdditionnelles.tppc.vehicules); } - console.log(' ✓ TPPC'); + console.log(' TPPC'); } } @@ -512,11 +490,11 @@ if (engagements) { if (engagements.domicileImmatriculation?.checked) { setValue('checkDomImmat', true); - console.log(' ✓ Domicile immatriculation'); + console.log(' Domicile immatriculation'); } if (engagements.contenantConfie?.checked) { setValue('checkContConf', true); - console.log(' ✓ Contenant confié'); + console.log(' Contenant confie'); } } @@ -529,7 +507,7 @@ if (tarifData.sinistralite.montant3ans) { setValue('montantSinistres3ans', tarifData.sinistralite.montant3ans); } - console.log(' ✓ Sinistralité'); + console.log(' Sinistralite'); } // ═══ RÉSULTATS TARIFAIRES ═══ @@ -555,7 +533,7 @@ if (res.cotTotalHT) setValue('cotTotalHT', res.cotTotalHT); if (res.cotTotalTTC) setValue('cotTotalTTC', res.cotTotalTTC); - console.log(' ✓ Résultats tarifaires'); + console.log(' Resultats tarifaires'); } // Forcer la mise à jour des éléments Materialize @@ -567,10 +545,10 @@ window.M.updateTextFields(); } - console.log('✅ Pré-remplissage Projet terminé'); + console.log('Pre-remplissage Projet termine'); } catch (error) { - console.error('❌ Erreur lors du pré-remplissage Projet:', error); + console.error('Erreur lors du pre-remplissage Projet:', error); } } @@ -593,7 +571,7 @@ return; } - console.log('📝 Pré-remplissage Tarif depuis Projet...'); + console.log('Pre-remplissage Tarif depuis Projet...'); try { // CA @@ -624,10 +602,10 @@ setValue('checkRCE', true); } - console.log('✅ Pré-remplissage Tarif terminé'); + console.log('Pre-remplissage Tarif termine'); } catch (error) { - console.error('❌ Erreur lors du pré-remplissage Tarif:', error); + console.error('Erreur lors du pre-remplissage Tarif:', error); } } @@ -642,6 +620,6 @@ FIELD_MAPPING }; - console.log('✅ RC Data Manager loaded'); + console.log('RC Data Manager loaded'); })(window); diff --git a/ecole/public/js/rc-orchestrator.js b/ecole/public/js/rc-orchestrator.js index 43d54242..4c71a2d0 100644 --- a/ecole/public/js/rc-orchestrator.js +++ b/ecole/public/js/rc-orchestrator.js @@ -1,16 +1,5 @@ /** - * ═══════════════════════════════════════════════════════════════════════════ - * RC SYNC ORCHESTRATOR - * ═══════════════════════════════════════════════════════════════════════════ - * - * Ce module orchestre la synchronisation entre Tarif RC et Projet RC. - * Il s'intègre avec les formulaires existants sans les modifier. - * - * @requires rc-sync-utils.js - * @requires rc-data-manager.js - * @author AXA Transport Team - * @version 2.0.0 - * @since 2026-02-17 + * Orchestrateur de synchro RC entre les écrans Tarif et Projet. */ (function(window) { @@ -18,12 +7,12 @@ // Attendre que les dépendances soient chargées if (!window.RCSync || !window.RCDataManager) { - console.error('❌ Dépendances RC Sync manquantes'); + console.error('Dependances RC Sync manquantes'); return; } - const { isChangeImpactingTarif, showReturnToTarifModal } = window.RCSync; - const { collectAllTarifData, prefillProjetFromTarif, prefillTarifFromProjet } = window.RCDataManager; + const { isFieldImpactingTarif, showReturnToTarifModal } = window.RCSync; + const { collectAllTarifData, prefillTarifFromProjet } = window.RCDataManager; // ═══════════════════════════════════════════════════════════════════════ // CONFIGURATION @@ -40,9 +29,7 @@ // ═══════════════════════════════════════════════════════════════════════ /** - * Détecte la page active (tarif ou projet) depuis l'URL. - * - * @returns {'tarif'|'projet'|null} Page active ou null + * Détecte si on est sur la page tarif ou projet. */ function detectActivePage() { const params = new URLSearchParams(window.location.search); @@ -57,50 +44,52 @@ return null; } + /** + * Détecte le produit courant à partir de la session. + * + * @returns {string|null} Code produit en minuscule + */ + function detectCurrentProduct() { + try { + const contrat = JSON.parse(sessionStorage.getItem('contrat') || 'null'); + const fromContrat = contrat?.produit; + if (fromContrat) return String(fromContrat).toLowerCase(); + + const parcours = JSON.parse(sessionStorage.getItem('parcours') || 'null'); + const fromParcours = parcours?.["@expand"]?.contrat?.produit; + return fromParcours ? String(fromParcours).toLowerCase() : null; + } catch (error) { + console.warn('Impossible de lire le produit courant:', error); + return null; + } + } + // ═══════════════════════════════════════════════════════════════════════ // GESTION SESSIONSTORAGE // ═══════════════════════════════════════════════════════════════════════ /** - * Sauvegarde les données du tarif validé dans sessionStorage. - * - * @param {Object} tarifData - Données complètes du tarif + * Sauvegarde le snapshot du tarif validé en sessionStorage. */ function saveTarifDataToSession(tarifData) { try { sessionStorage.setItem(SESSION_STORAGE_KEYS.TARIF_DATA, JSON.stringify(tarifData)); sessionStorage.setItem(SESSION_STORAGE_KEYS.TARIF_ORIGINAL, JSON.stringify(tarifData)); - console.log('✅ Données tarif sauvegardées en session'); + console.log('Donnees tarif sauvegardees en session'); } catch (error) { - console.error('❌ Erreur sauvegarde session:', error); + console.error('Erreur sauvegarde session:', error); } } /** - * Récupère les données du tarif depuis sessionStorage. - * - * @returns {Object|null} Données du tarif ou null + * Lit les données tarif sauvegardées en sessionStorage. */ function getTarifDataFromSession() { try { const data = sessionStorage.getItem(SESSION_STORAGE_KEYS.TARIF_DATA); return data ? JSON.parse(data) : null; } catch (error) { - console.error('❌ Erreur lecture session:', error); - return null; - } - } - - /** - * Récupère les données originales du tarif pour comparaison. - * - * @returns {Object|null} Données originales du tarif - */ - function getTarifOriginalDataFromSession() { - try { - const data = sessionStorage.getItem(SESSION_STORAGE_KEYS.TARIF_ORIGINAL); - return data ? JSON.parse(data) : null; - } catch (error) { + console.error('Erreur lecture session:', error); return null; } } @@ -110,13 +99,10 @@ // ═══════════════════════════════════════════════════════════════════════ /** - * Hook appelé après la validation du tarif commercial. - * Collecte toutes les données et les sauvegarde en session. - * - * Cette fonction doit être appelée juste avant la redirection vers le projet. + * Hook appelé après validation tarif pour garder l'état en session. */ function onTarifValidated() { - console.log('🎯 Hook: Tarif validé, collecte des données...'); + console.log('Hook tarif valide: collecte des donnees'); try { // Collecter toutes les données du tarif @@ -125,9 +111,9 @@ // Sauvegarder en session pour le pré-remplissage projet saveTarifDataToSession(tarifData); - console.log('✅ Données tarif prêtes pour le projet'); + console.log('Donnees tarif pretes pour le projet'); } catch (error) { - console.error('❌ Erreur hook tarif validé:', error); + console.error('Erreur hook tarif valide:', error); } } @@ -136,12 +122,17 @@ // ═══════════════════════════════════════════════════════════════════════ /** - * Initialise le formulaire projet au chargement. - * Configure UNIQUEMENT la détection des changements impactants. - * Le pré-remplissage est géré par prefillFromTarif() existant dans projet-form-RC.js + * Initialisation côté projet. + * Le pré-remplissage reste géré dans projet-form-rc.js. */ function initProjetPage() { - console.log('🚀 Initialisation RC Orchestrator pour page Projet...'); + console.log('Initialisation RC Orchestrator pour la page Projet'); + + const currentProduct = detectCurrentProduct(); + if (currentProduct === 'rc') { + console.log('Projet RC detecte: la gestion modal est deja faite dans projet-form-rc.js'); + return; + } // Les données rc/tarif/projet sont DÉJÀ chargées depuis la base // par le code existant dans projet-form-RC.js @@ -153,9 +144,7 @@ } /** - * Configure la détection des changements impactants dans le projet. - * Affiche un modal si l'utilisateur modifie un champ qui impacte le tarif. - * Utilise les variables globales rc/tarif depuis projet-form-RC.js + * Fallback générique de détection d'impact sur la page projet. */ function setupProjetChangeDetection() { // Les données originales sont dans les variables globales window.tarif et window.rc @@ -164,12 +153,12 @@ const rcOriginal = window.rc; if (!tarifOriginal && !rcOriginal) { - console.log('ℹ️ Pas de tarif/rc, pas de détection'); + console.log('Pas de tarif/rc, pas de detection'); return; } - console.log('👁️ Configuration détection changements...'); - console.log('📋 Données originales:', { tarif: tarifOriginal, rc: rcOriginal }); + console.log('Configuration de la detection des changements'); + console.log('Donnees originales:', { tarif: tarifOriginal, rc: rcOriginal }); // Liste COMPLÈTE des éléments à surveiller (tous les champs impactants) const elementsToWatch = [ @@ -212,14 +201,14 @@ const fieldName = this.id; const newValue = this.type === 'checkbox' ? this.checked : this.value; - console.log(`🔍 Changement détecté: ${fieldName} = ${newValue}`); + console.log(`Changement detecte: ${fieldName} = ${newValue}`); // Vérifier si c'est un champ impactant if (isFieldImpactingTarif(fieldName)) { - console.warn(`⚠️ "${fieldName}" impacte le tarif !`); + console.warn(`"${fieldName}" impacte le tarif`); showReturnToTarifModal(fieldName); } else { - console.log(`ℹ️ "${fieldName}" n'impacte pas le tarif`); + console.log(`"${fieldName}" n'impacte pas le tarif`); } }); }); @@ -228,8 +217,8 @@ const radioTypeCot = document.querySelectorAll('input[name="typeCot"]'); radioTypeCot.forEach(radio => { radio.addEventListener('change', function() { - console.log(`🔍 Changement type cotisation: ${this.value}`); - console.warn(`⚠️ Type de cotisation impacte le tarif !`); + console.log(`Changement type cotisation: ${this.value}`); + console.warn('Type de cotisation impacte le tarif'); showReturnToTarifModal('Type de cotisation'); }); }); @@ -239,8 +228,8 @@ if (activitySelector) { activitySelector.addEventListener('change', function() { const selectedValues = Array.from(this.selectedOptions).map(opt => opt.value); - console.log(`🔍 Changement activités:`, selectedValues); - console.warn(`⚠️ Activités impactent le tarif !`); + console.log('Changement activites:', selectedValues); + console.warn('Activites impactent le tarif'); showReturnToTarifModal('Activités'); }); } @@ -250,8 +239,8 @@ if (marchandiseSelector) { marchandiseSelector.addEventListener('change', function() { const selectedValues = Array.from(this.selectedOptions).map(opt => opt.value); - console.log(`🔍 Changement marchandises:`, selectedValues); - console.warn(`⚠️ Marchandises impactent le tarif !`); + console.log('Changement marchandises:', selectedValues); + console.warn('Marchandises impactent le tarif'); showReturnToTarifModal('Marchandises'); }); } @@ -261,13 +250,13 @@ const btn = document.getElementById(btnId); if (!btn) return; btn.addEventListener('click', () => { - console.log(`🔍 Changement zones via ${btnId}`); - console.warn('⚠️ Zones géographiques impactent le tarif !'); + console.log(`Changement zones via ${btnId}`); + console.warn('Zones geographiques impactent le tarif'); showReturnToTarifModal('Zones géographiques'); }); }); - console.log('✅ Détection changements configurée sur tous les champs impactants'); + console.log('Detection des changements configuree'); } // ═══════════════════════════════════════════════════════════════════════ @@ -279,7 +268,7 @@ * Pré-remplit depuis le projet si l'utilisateur vient du projet. */ function initTarifPage() { - console.log('🚀 Initialisation page Tarif...'); + console.log('Initialisation page Tarif'); // Vérifier si on vient du projet const projetData = JSON.parse(sessionStorage.getItem(SESSION_STORAGE_KEYS.PROJET_DATA) || 'null'); @@ -287,7 +276,7 @@ if (projetData && !getTarifDataFromSession()) { // On a des données projet mais pas de tarif validé // = L'utilisateur a commencé par le projet - console.log('📥 Pré-remplissage depuis projet...'); + console.log('Pre-remplissage depuis projet'); setTimeout(() => { prefillTarifFromProjet(projetData); @@ -314,7 +303,7 @@ // Remplacer par notre version wrappée window.saveTarifRC = async function(...args) { - console.log('🎯 Interception saveTarifRC...'); + console.log('Interception de saveTarifRC'); // Appeler la fonction originale const result = await originalSaveTarifRC.apply(this, args); @@ -327,7 +316,7 @@ return result; }; - console.log('✅ saveTarifRC intercepté'); + console.log('saveTarifRC intercepte'); } }, 100); @@ -343,10 +332,10 @@ * Initialise l'orchestrateur au chargement de la page. */ function init() { - console.log('🎼 RC Sync Orchestrator: Démarrage...'); + console.log('RC Sync Orchestrator: demarrage'); const activePage = detectActivePage(); - console.log(`📄 Page active détectée: ${activePage || 'aucune'}`); + console.log(`Page active detectee: ${activePage || 'aucune'}`); if (activePage === 'tarif') { interceptTarifValidation(); @@ -383,6 +372,6 @@ getTarifDataFromSession }; - console.log('✅ RC Sync Orchestrator loaded'); + console.log('RC Sync Orchestrator loaded'); })(window); diff --git a/ecole/public/js/rc-sync-utils.js b/ecole/public/js/rc-sync-utils.js index 0939ba32..3ccb2708 100644 --- a/ecole/public/js/rc-sync-utils.js +++ b/ecole/public/js/rc-sync-utils.js @@ -1,14 +1,5 @@ /** - * ═══════════════════════════════════════════════════════════════════════════ - * RC SYNCHRONIZATION UTILITIES - * ═══════════════════════════════════════════════════════════════════════════ - * - * Ce module contient toutes les fonctions utilitaires pour la synchronisation - * bidirectionnelle entre les formulaires Tarif RC et Projet RC. - * - * @author AXA Transport Team - * @version 2.0.0 - * @since 2026-02-17 + * Fonctions utilitaires partagées entre les écrans RC Tarif et Projet. */ (function(window) { @@ -19,11 +10,8 @@ // ═══════════════════════════════════════════════════════════════════════ /** - * Liste exhaustive des champs qui impactent le calcul du tarif. - * Si l'un de ces champs est modifié dans le projet, un modal - * demandera à l'utilisateur de retourner au tarif. - * - * @constant {Array} + * Champs qui influencent le calcul du tarif. + * On s'en sert pour savoir quand proposer un retour vers l'onglet Tarif. */ const TARIF_IMPACTING_FIELDS = [ // Chiffre d'affaires et type de contrat @@ -76,17 +64,8 @@ // ═══════════════════════════════════════════════════════════════════════ /** - * Convertit une valeur en nombre en gérant les formats français et internationaux. - * Gère les espaces, virgules, points, et valeurs nulles/undefined. - * - * @param {string|number|null|undefined} x - Valeur à convertir - * @returns {number} Nombre converti ou 0 si impossible - * - * @example - * toNumber("1 234,56") // 1234.56 - * toNumber("1.234,56") // 1234.56 - * toNumber("1,234.56") // 1234.56 - * toNumber(null) // 0 + * Convertit une valeur texte/nombre en nombre JS. + * Accepte les formats FR/EN (espaces, virgules, points). */ function toNumber(x) { if (x == null) return 0; @@ -130,15 +109,7 @@ } /** - * Récupère la valeur d'un élément par son ID de manière flexible. - * Gère les différents types d'éléments (input, select, textarea, etc.) - * et les cas où l'ID contient des caractères spéciaux. - * - * @param {string} id - ID de l'élément - * @returns {HTMLElement|null} Élément trouvé ou null - * - * @example - * const element = getElementByIdFlexible("my-element"); + * Récupère un élément par id, avec fallback querySelector. */ function getElementByIdFlexible(id) { if (!id) return null; @@ -152,15 +123,7 @@ } /** - * Récupère la valeur d'un champ de formulaire de manière sécurisée. - * Gère les inputs, selects, textareas, checkboxes, et contenus textuels. - * - * @param {string} elementId - ID de l'élément - * @returns {string|number|boolean|null} Valeur du champ - * - * @example - * getValue("ca") // "100000" - * getValue("checkPJ") // true + * Lit la valeur d'un champ de formulaire selon son type. */ function getValue(elementId) { const element = getElementByIdFlexible(elementId); @@ -181,15 +144,7 @@ } /** - * Définit la valeur d'un champ de formulaire. - * Gère automatiquement le type de champ et met à jour l'interface. - * - * @param {string} elementId - ID de l'élément - * @param {any} value - Valeur à définir - * - * @example - * setValue("ca", 100000); - * setValue("checkPJ", true); + * Ecrit une valeur dans un champ de formulaire selon son type. */ function setValue(elementId, value) { const element = getElementByIdFlexible(elementId); @@ -218,83 +173,12 @@ } } - // ═══════════════════════════════════════════════════════════════════════ - // COMPARAISON DE DONNÉES - // ═══════════════════════════════════════════════════════════════════════ - - /** - * Compare deux tableaux pour vérifier leur égalité. - * Effectue une comparaison profonde élément par élément. - * - * @param {Array} arr1 - Premier tableau - * @param {Array} arr2 - Deuxième tableau - * @returns {boolean} true si les tableaux sont égaux - * - * @example - * arraysEqual([1,2,3], [1,2,3]) // true - * arraysEqual([1,2], [1,2,3]) // false - */ - function arraysEqual(arr1, arr2) { - if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false; - if (arr1.length !== arr2.length) return false; - - const sorted1 = [...arr1].sort(); - const sorted2 = [...arr2].sort(); - - return sorted1.every((val, idx) => val === sorted2[idx]); - } - - /** - * Compare deux valeurs en tenant compte de leur type. - * Gère les tableaux, objets, null, undefined, et valeurs primitives. - * - * @param {any} value1 - Première valeur - * @param {any} value2 - Deuxième valeur - * @returns {boolean} true si les valeurs sont égales - * - * @example - * valuesEqual([1,2], [2,1]) // true (ordre indépendant) - * valuesEqual(null, undefined) // true - * valuesEqual(100, "100") // true (conversion automatique) - */ - function valuesEqual(value1, value2) { - // Normaliser null et undefined - if (value1 == null && value2 == null) return true; - if (value1 == null || value2 == null) return false; - - // Comparer les tableaux - if (Array.isArray(value1) && Array.isArray(value2)) { - return arraysEqual(value1, value2); - } - - // Comparer les objets - if (typeof value1 === 'object' && typeof value2 === 'object') { - return JSON.stringify(value1) === JSON.stringify(value2); - } - - // Comparer les nombres (avec conversion) - if (!isNaN(value1) && !isNaN(value2)) { - return toNumber(value1) === toNumber(value2); - } - - // Comparaison standard - return value1 === value2; - } - // ═══════════════════════════════════════════════════════════════════════ // DÉTECTION DE CHANGEMENTS IMPACTANTS // ═══════════════════════════════════════════════════════════════════════ /** - * Vérifie si un champ donné impacte le calcul du tarif. - * Se base sur la liste TARIF_IMPACTING_FIELDS. - * - * @param {string} fieldName - Nom du champ - * @returns {boolean} true si le champ impacte le tarif - * - * @example - * isFieldImpactingTarif("ca") // true - * isFieldImpactingTarif("dateEffet") // false + * Dit si un nom de champ fait partie des champs qui influencent le tarif. */ function isFieldImpactingTarif(fieldName) { return TARIF_IMPACTING_FIELDS.some(field => @@ -302,74 +186,43 @@ ); } - /** - * Vérifie si un changement de valeur impacte le tarif. - * Compare la nouvelle valeur avec les données originales du tarif. - * - * @param {string} fieldName - Nom du champ modifié - * @param {any} newValue - Nouvelle valeur - * @param {Object} tarifOriginalData - Données originales du tarif - * @returns {boolean} true si le changement impacte le tarif - * - * @example - * const impacted = isChangeImpactingTarif("ca", 200000, tarifData); - * if (impacted) showReturnToTarifModal(); - */ - function isChangeImpactingTarif(fieldName, newValue, tarifOriginalData) { - // Vérifier si le champ est dans la liste des champs impactants - if (!isFieldImpactingTarif(fieldName)) { - return false; - } - - // Si pas de données originales, pas d'impact possible - if (!tarifOriginalData) { - return false; - } - - // Récupérer la valeur originale - const originalValue = tarifOriginalData[fieldName]; - - // Comparer les valeurs - return !valuesEqual(newValue, originalValue); - } - // ═══════════════════════════════════════════════════════════════════════ // MODAL DE RETOUR AU TARIF // ═══════════════════════════════════════════════════════════════════════ /** - * Affiche le modal demandant à l'utilisateur de retourner au tarif. - * Ce modal s'affiche quand une modification dans le projet impacte - * le calcul du tarif. - * - * @param {string} [fieldName] - Nom du champ modifié (optionnel, pour info) - * - * @example - * showReturnToTarifModal("ca"); + * Ouvre le modal "modif tarif" déjà présent dans la vue projet. */ function showReturnToTarifModal(fieldName) { - const modalId = 'modalRetourTarif'; - let modal = document.getElementById(modalId); - - // Créer le modal s'il n'existe pas + const modal = document.getElementById('modalModif'); if (!modal) { - modal = createReturnToTarifModal(); - document.body.appendChild(modal); + console.warn('Modal "modalModif" introuvable'); + return; } - // Mettre à jour le message si un champ est spécifié if (fieldName) { - const messageEl = modal.querySelector('#modalRetourTarifMessage'); - if (messageEl) { - messageEl.innerHTML = ` - Vous avez modifié "${fieldName}" qui impacte le calcul du tarif. -

- Vous devez retourner sur le formulaire Tarif pour recalculer et valider le nouveau tarif. - `; - } + console.warn(`Modification impactant le tarif: ${fieldName}`); + } + + const okBtn = document.getElementById('modif-OK'); + const noBtn = document.getElementById('modif-NO'); + + if (okBtn) { + okBtn.onclick = (event) => { + event?.preventDefault?.(); + navigateToTarif(); + }; + } + + if (noBtn) { + noBtn.onclick = (event) => { + event?.preventDefault?.(); + if (!window.M || !window.M.Modal) return; + const instance = window.M.Modal.getInstance(modal); + if (instance) instance.close(); + }; } - // Ouvrir le modal if (window.M && window.M.Modal) { const instance = window.M.Modal.getInstance(modal) || window.M.Modal.init(modal); instance.open(); @@ -377,50 +230,29 @@ } /** - * Crée l'élément DOM du modal de retour au tarif. - * - * @returns {HTMLElement} Élément modal créé - * @private - */ - function createReturnToTarifModal() { - const modal = document.createElement('div'); - modal.id = 'modalRetourTarif'; - modal.className = 'modal'; - - modal.innerHTML = ` - - - `; - - return modal; - } - - /** - * Navigate vers l'onglet Tarif depuis le Projet. - * - * @example - * navigateToTarif(); + * Redirige vers l'onglet Tarif depuis le Projet. */ function navigateToTarif() { // Fermer le modal - const modal = document.getElementById('modalRetourTarif'); + const modal = document.getElementById('modalModif'); if (modal && window.M) { const instance = window.M.Modal.getInstance(modal); if (instance) instance.close(); } + // Si on est sur le formulaire projet, on garde un snapshot local + // pour pré-remplir le tarif juste après la navigation. + try { + if (typeof window.collectProjetDataForTarifPrefill === 'function') { + const projetData = window.collectProjetDataForTarifPrefill(); + if (projetData) { + sessionStorage.setItem('rc_projet_data', JSON.stringify(projetData)); + } + } + } catch (error) { + console.error('Impossible de stocker les donnees projet avant redirection:', error); + } + // Naviguer vers le tarif const numParcours = new URLSearchParams(window.location.search).get('numParcours'); if (numParcours) { @@ -434,7 +266,6 @@ /** * API publique du module RC Sync. - * Toutes les fonctions exportées ici sont accessibles via window.RCSync. */ window.RCSync = { // Helpers @@ -443,13 +274,8 @@ setValue, getElementByIdFlexible, - // Comparaison - arraysEqual, - valuesEqual, - // Détection changements isFieldImpactingTarif, - isChangeImpactingTarif, // Modal showReturnToTarifModal, @@ -459,6 +285,6 @@ TARIF_IMPACTING_FIELDS }; - console.log('✅ RC Sync Utils loaded'); + console.log('RC Sync Utils loaded'); })(window); diff --git a/ecole/public/js/tarif-form-rc.js b/ecole/public/js/tarif-form-rc.js index 824c4d6b..4281d2ba 100644 --- a/ecole/public/js/tarif-form-rc.js +++ b/ecole/public/js/tarif-form-rc.js @@ -929,6 +929,327 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Le bouton Annuler ferme automatiquement le modal grâce à la classe "modal-close" } + function parsePrefillArray(value) { + if (Array.isArray(value)) return value; + if (typeof value === 'string') { + try { + const parsed = JSON.parse(value); + return Array.isArray(parsed) ? parsed : []; + } catch (error) { + return []; + } + } + return []; + } + + function normalizePrefillValue(value) { + if (Array.isArray(value)) { + return value + .map((item) => normalizePrefillValue(item)) + .sort((a, b) => String(a).localeCompare(String(b), 'fr')); + } + + if (value && typeof value === 'object') { + const normalized = {}; + Object.keys(value).sort().forEach((key) => { + normalized[key] = normalizePrefillValue(value[key]); + }); + return normalized; + } + + if (typeof value === 'string') { + const trimmed = value.trim(); + if (!trimmed) return ''; + const numeric = toNumber(trimmed); + if (!Number.isNaN(numeric) && trimmed.match(/^-?[\d\s,\.]+$/)) { + return numeric; + } + return trimmed.toLowerCase(); + } + + if (value == null) return null; + return value; + } + + function buildProjetPrefillDelta(snapshot, baseline) { + if (!snapshot || typeof snapshot !== 'object') return null; + if (!baseline || typeof baseline !== 'object') return snapshot; + + const delta = {}; + Object.keys(snapshot).forEach((key) => { + const currentValue = normalizePrefillValue(snapshot[key]); + const baselineValue = normalizePrefillValue(baseline[key]); + + if (JSON.stringify(currentValue) !== JSON.stringify(baselineValue)) { + delta[key] = snapshot[key]; + } + }); + + return Object.keys(delta).length > 0 ? delta : null; + } + + function applyProjetDataToTarif(projetSource) { + if (!projetSource || typeof projetSource !== 'object') return; + + const hasOwn = (key) => Object.prototype.hasOwnProperty.call(projetSource, key); + const hasAny = (keys) => keys.some(hasOwn); + const normalize = (value) => String(value || '') + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .toLowerCase() + .trim(); + + const toProjetNumber = (value) => { + if (value == null) return null; + const raw = String(value).trim(); + if (!raw || raw.toLowerCase() === 'nous consulter') return null; + const parsed = toNumber(raw); + return Number.isFinite(parsed) ? parsed : null; + }; + + const pickNumber = (...keys) => { + for (const key of keys) { + const parsed = toProjetNumber(projetSource[key]); + if (parsed != null) return parsed; + } + return null; + }; + + const setCheckboxState = (id, checked, dispatchClick) => { + const element = document.getElementById(id); + if (!element) return; + element.checked = Boolean(checked); + if (dispatchClick) { + element.dispatchEvent(new Event('click')); + } + }; + + const setFirstValue = (elements, value) => { + if (value == null) return; + for (const element of elements) { + if (element) { + element.value = value; + return; + } + } + }; + + const typeCotisation = projetSource.typeCot || projetSource.typeCotisation; + if (typeCotisation) { + const radio = document.getElementById(typeCotisation) || document.querySelector(`input[name="cotisation"][value="${typeCotisation}"]`); + if (radio) { + radio.checked = true; + radio.dispatchEvent(new Event('change')); + } + } + + if (hasOwn('ca')) { + const caField = getElementByIdFlexible('CA') || getElementByIdFlexible('chiffreAffaire'); + if (caField) { + const rawCA = String(projetSource.ca || '').trim(); + caField.value = (!rawCA || rawCA.toLowerCase() === 'nous consulter') ? '' : rawCA; + } + } + + const nbVehiculeField = getElementByIdFlexible('nbVehicules') || getElementByIdFlexible('nbrVehicule'); + const nbVehFromProjet = toProjetNumber(projetSource.nombreVehicules ?? projetSource.nbVehicules); + const nbVehFromTable = Array.isArray(projetSource.designationVehicule) ? projetSource.designationVehicule.length : null; + if (nbVehiculeField) { + if (nbVehFromProjet != null) { + nbVehiculeField.value = Math.max(0, Math.round(nbVehFromProjet)); + } else if (nbVehFromTable != null && nbVehFromTable > 0) { + nbVehiculeField.value = nbVehFromTable; + } + } + + const activityMappings = [ + { + keys: ['actVoiturier', 'actLoueur'], + checkboxId: 'checkVoiturier', + capitalKeys: ['valueActVoiturier', 'valueActLoueur'], + capitalFields: [ + getElementByIdFlexible('capitalVoiturier'), + document.querySelector('input[name="selectActVoiturier/Loueur"]') + ] + }, + { + keys: ['actMultimodal'], + checkboxId: 'checkCommissionnaire', + capitalKeys: ['valueActMultimodal'], + capitalFields: [ + getElementByIdFlexible('capitalCommissionnaire'), + document.querySelector('input[name="selectActCommissionnaire de Transport"]'), + document.querySelector('input[name="selectActCommissionnaireDeTransport"]') + ] + }, + { + keys: ['actDemEntr', 'actDemInterne', 'actDemPar', 'actDemParDom', 'actDemParAdv', 'actGardeMeuble'], + checkboxId: 'checkDemenageur', + capitalKeys: ['valueActDemEntr', 'valueActDemInterne', 'valueActDemPar', 'valueActDemParDom', 'valueActDemParAdv'], + capitalFields: [ + getElementByIdFlexible('capitalDemenageur'), + document.querySelector('input[name="selectActDéménageur"]'), + document.querySelector('input[name="selectActDemenageur"]') + ] + }, + { + keys: ['actPrestaLog', 'actEntDep'], + checkboxId: 'checkLogistique', + capitalKeys: ['valueActPrestaLog', 'valueActEntDep'], + capitalFields: [ + getElementByIdFlexible('capitalLogistique'), + document.querySelector('input[name="selectActLogistique"]') + ] + }, + { + keys: ['actAutocariste'], + checkboxId: 'checkAutocariste', + capitalKeys: ['valueActAutocariste'], + capitalFields: [ + getElementByIdFlexible('capitalAutocariste'), + document.querySelector('input[name="selectActAutocariste"]') + ] + }, + { + keys: ['actAutres'], + checkboxId: 'checkAutres', + capitalKeys: ['valueActAutres'], + capitalFields: [ + getElementByIdFlexible('capitalAutres'), + document.querySelector('input[name="selectActAutres activites"]'), + document.querySelector('input[name="selectActAutresActivites"]') + ] + } + ]; + + activityMappings.forEach((mapping) => { + if (!hasAny(mapping.keys)) return; + const selected = mapping.keys.some((key) => Boolean(projetSource[key])); + setCheckboxState(mapping.checkboxId, selected, true); + if (selected) { + const capitalValue = pickNumber(...mapping.capitalKeys); + setFirstValue(mapping.capitalFields, capitalValue); + } + }); + + if (hasAny(['zone1', 'zone2', 'zone3', 'zone4', 'zone5', 'zone6'])) { + const zone1 = Boolean(projetSource.zone1); + const zone2 = Boolean(projetSource.zone2); + const zone3 = Boolean(projetSource.zone3); + const zone4 = Boolean(projetSource.zone4); + const zone5 = Boolean(projetSource.zone5); + const zone6 = Boolean(projetSource.zone6); + + const zone1El = document.getElementById('zone1'); + const zone2El = document.getElementById('zone2'); + const zone3El = document.getElementById('zone3'); + const zone4El = document.getElementById('zone4'); + const zone5El = document.getElementById('zone5'); + const zone6El = document.getElementById('zone6'); + + if (zone1El) { + zone1El.checked = zone1 || zone2 || zone3; + zone1El.disabled = zone2 || zone3; + } + if (zone2El) { + zone2El.checked = zone2 || zone3; + zone2El.disabled = zone3; + } + if (zone3El) zone3El.checked = zone3; + if (zone4El) zone4El.checked = zone4; + if (zone5El) zone5El.checked = zone5; + if (zone6El) zone6El.checked = zone6; + } + + if (hasOwn('autresRC')) { + setCheckboxState('checkRCE', Boolean(projetSource.autresRC), true); + } + + if (hasOwn('pj')) { + setCheckboxState('checkPJ', Boolean(projetSource.pj), false); + } + + const activitesComplementairesMap = [ + { field: 'activitesVoiturier', container: 'actComplVoiturier/Loueur' }, + { field: 'activitesCommissionnaire', container: 'actComplCommissionnaire de Transport' }, + { field: 'activitesDemenageur', container: 'actComplDéménageur' }, + { field: 'activitesLogistique', container: 'actComplLogistique' } + ]; + + activitesComplementairesMap.forEach(({ field, container }) => { + if (!hasOwn(field)) return; + const wanted = parsePrefillArray(projetSource[field]); + const wantedSet = new Set(wanted.map((item) => String(item).trim())); + const checkboxes = document.querySelectorAll(`[name="${container}"] input[type="checkbox"]`); + checkboxes.forEach((checkbox) => { + const label = checkbox.nextElementSibling ? checkbox.nextElementSibling.textContent.trim() : checkbox.value; + checkbox.checked = wantedSet.has(label); + }); + }); + + const marchandiseKeys = [ + 'marOrdinaire', + 'marRoulant', + 'marEngins', + 'marRoulantDem', + 'marMobilerUsag', + 'marPerissable', + 'marAnimaux', + 'marCiterne', + 'marBeton', + 'marExceptionnels', + 'marVrac' + ]; + + if (hasAny(marchandiseKeys)) { + const flags = { + marOrdinaire: Boolean(projetSource.marOrdinaire), + marRoulant: Boolean(projetSource.marRoulant), + marEngins: Boolean(projetSource.marEngins), + marRoulantDem: Boolean(projetSource.marRoulantDem), + marMobilerUsag: Boolean(projetSource.marMobilerUsag), + marPerissable: Boolean(projetSource.marPerissable), + marAnimaux: Boolean(projetSource.marAnimaux), + marCiterne: Boolean(projetSource.marCiterne), + marBeton: Boolean(projetSource.marBeton), + marExceptionnels: Boolean(projetSource.marExceptionnels), + marVrac: Boolean(projetSource.marVrac) + }; + + const shouldSelectMarchandise = (label) => { + const text = normalize(label); + if (!text) return false; + if (flags.marRoulantDem && text.includes('vehicules roulants') && text.includes('demenagement')) return true; + if (flags.marRoulant && text.includes('vehicules roulants') && !text.includes('demenagement')) return true; + if (flags.marOrdinaire && text.includes('ordinaires')) return true; + if (flags.marEngins && text.includes('engins de chantier')) return true; + if (flags.marMobilerUsag && text.includes('mobiliers')) return true; + if (flags.marPerissable && text.includes('perissables')) return true; + if (flags.marAnimaux && text.includes('animaux vivants')) return true; + if (flags.marCiterne && text.includes('citerne')) return true; + if (flags.marBeton && text.includes('beton')) return true; + if (flags.marExceptionnels && text.includes('exceptionnels')) return true; + if (flags.marVrac && (text.includes('benne') || text.includes('vrac'))) return true; + return false; + }; + + [ + 'marVoiturier/Loueur', + 'marCommissionnaire de Transport', + 'marDéménageur', + 'marLogistique', + 'marAutocariste', + 'marAutres activites' + ].forEach((container) => { + const checkboxes = document.querySelectorAll(`[name="${container}"] input[type="checkbox"]`); + checkboxes.forEach((checkbox) => { + const label = checkbox.nextElementSibling ? checkbox.nextElementSibling.textContent : checkbox.value; + checkbox.checked = shouldSelectMarchandise(label); + }); + }); + } + } + // Peupler le formulaire avec les données function populateFormData() { //Poupulate select historique @@ -984,7 +1305,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio return; } - console.log('🔄 Pré-remplissage du formulaire tarif avec les données RC:', rc); + console.log('Pre-remplissage du formulaire tarif avec les donnees RC:', rc); // Type de cotisation if (rc.typeCotisation) { @@ -1095,7 +1416,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio { field: 'actComplLogistique', name: 'actComplLogistique' } ]; - console.log('🔄 Pré-remplissage des activités complémentaires...'); + console.log('Pre-remplissage des activites complementaires...'); activitiesTypes.forEach(({ field, name }) => { // D'abord DÉCOCHER toutes les checkboxes de cette activité const allCheckboxes = document.querySelectorAll(`[name="${name}"] input[type="checkbox"]`); @@ -1105,18 +1426,18 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio try { // PocketBase parse automatiquement les champs JSON, donc rc[field] est déjà un array const activites = Array.isArray(rc[field]) ? rc[field] : JSON.parse(rc[field]); - console.log(` ✓ ${field}:`, activites); + console.log(` ${field}:`, activites); activites.forEach(actText => { // Chercher la checkbox dont le span adjacent contient ce texte allCheckboxes.forEach(cb => { const label = cb.nextElementSibling?.textContent.trim(); if (label === actText) { cb.checked = true; - console.log(` ✓ Coché: ${actText}`); + console.log(` Coche: ${actText}`); } }); }); - } catch (e) { console.error(`❌ Erreur parsing ${field}:`, e); } + } catch (e) { console.error(`Erreur parsing ${field}:`, e); } } }); @@ -1130,7 +1451,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio { field: 'marchandisesAutres', name: 'marAutres activites' } ]; - console.log('🔄 Pré-remplissage des marchandises...'); + console.log('Pre-remplissage des marchandises...'); marchandisesTypes.forEach(({ field, name }) => { // D'abord DÉCOCHER toutes les checkboxes de cette marchandise const allCheckboxes = document.querySelectorAll(`[name="${name}"] input[type="checkbox"]`); @@ -1140,18 +1461,18 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio try { // PocketBase parse automatiquement les champs JSON, donc rc[field] est déjà un array const marchandises = Array.isArray(rc[field]) ? rc[field] : JSON.parse(rc[field]); - console.log(` ✓ ${field}:`, marchandises); + console.log(` ${field}:`, marchandises); marchandises.forEach(marText => { // Chercher la checkbox dont le span adjacent contient ce texte allCheckboxes.forEach(cb => { const label = cb.nextElementSibling?.textContent.trim(); if (label === marText) { cb.checked = true; - console.log(` ✓ Coché: ${marText}`); + console.log(` Coche: ${marText}`); } }); }); - } catch (e) { console.error(`❌ Erreur parsing ${field}:`, e); } + } catch (e) { console.error(`Erreur parsing ${field}:`, e); } } }); @@ -1160,8 +1481,8 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio ensureDefaultMarchandisesVoiturier(); // Pré-remplir les pourcentages (depuis tarifRC) - // Préférer l'objet tarif si présent, sinon retomber sur l'expansion RC - const tarifSource = tarif || rc?.["@expand"]?.tarifRC || rc?.tarifRC || rc || null; + // Priorité au tarif persistant; ne pas retomber sur rc pour ces champs. + const tarifSource = tarif || rc?.["@expand"]?.tarifRC || rc?.tarifRC || null; // Fallback ultime : données de session (hook RC orchestrator) si tout est vide let sessionTarifData = null; try { @@ -1268,7 +1589,33 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio } } - console.log('✅ Formulaire tarif pré-rempli avec succès'); + // Pré-remplissage Projet -> Tarif + // - si snapshot session: appliquer seulement les champs modifies sur Projet + // - sinon: utiliser Projet uniquement si on n'a pas de tarif persistant + let sessionProjetData = null; + try { + const storedProjetData = sessionStorage.getItem('rc_projet_data'); + sessionProjetData = storedProjetData ? JSON.parse(storedProjetData) : null; + } catch (error) { + sessionProjetData = null; + } + + let projetPrefillSource = null; + if (sessionProjetData) { + projetPrefillSource = buildProjetPrefillDelta(sessionProjetData, projet); + } else if (!tarifSource && !sessionTarifData && projet) { + projetPrefillSource = projet; + } + + if (projetPrefillSource) { + applyProjetDataToTarif(projetPrefillSource); + } + + if (sessionProjetData) { + sessionStorage.removeItem('rc_projet_data'); + } + + console.log('Formulaire tarif pre-rempli avec succes'); // Recalculer après pré-remplissage setTimeout(() => { @@ -2967,8 +3314,8 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio }; // ===== LOGS DE DÉBOGAGE ===== - console.log('📊 === DÉBUT SAUVEGARDE TARIF RC ==='); - console.log('🔍 rcMainData:', rcMainData); + console.log('=== DEBUT SAUVEGARDE TARIF RC ==='); + console.log('rcMainData:', rcMainData); // ===== ÉTAPE 2: Collecter les résultats de calculs pour tarifRC ===== const tarifRCData = { @@ -3068,7 +3415,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // ===== ÉTAPE 3: Sauvegarder/Mettre à jour tarifRC ===== if (tarif && tarif.id) { // Mettre à jour un enregistrement tarifRC existant - console.log(`📝 Mise à jour tarifRC existant (ID: ${tarif.id})`); + console.log(`Mise a jour tarifRC existant (ID: ${tarif.id})`); const response = await fetch(`/rc/tarif/update/${tarif.id}`, { method: 'POST', body: JSON.stringify(tarifRCData), @@ -3077,16 +3424,16 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio }, }); const data = await response.json(); - console.log('📥 Réponse serveur tarifRC update:', data); + console.log('Reponse serveur tarifRC update:', data); if (data.valid) { idTarifRC = data.tarifRc.id; } else { - console.error('❌ Échec de la mise à jour de tarifRC:', data.message); + console.error('Echec de la mise a jour de tarifRC:', data.message); return { valid: false, message: data.message }; } } else { // Créer un nouvel enregistrement tarifRC - console.log('📝 Création nouveau tarifRC'); + console.log('Creation nouveau tarifRC'); const response = await fetch(`/rc/tarif/create`, { method: 'POST', body: JSON.stringify(tarifRCData), @@ -3095,11 +3442,11 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio }, }); const data = await response.json(); - console.log('📥 Réponse serveur tarifRC create:', data); + console.log('Reponse serveur tarifRC create:', data); if (data.valid) { idTarifRC = data.tarifRc.id; } else { - console.error('❌ Échec de la création de tarifRC:', data.message); + console.error('Echec de la creation de tarifRC:', data.message); return { valid: false, message: data.message }; } } @@ -3107,8 +3454,8 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // ===== ÉTAPE 4: Mettre à jour RC principal avec les données communes ET la référence tarifRC ===== rcMainData.tarifRC = idTarifRC; // Ajouter la référence à tarifRC - console.log(`📝 Mise à jour RC principal (ID: ${rcId}) avec référence tarifRC: ${idTarifRC}`); - console.log('🔍 Données envoyées pour RC:', rcMainData); + console.log(`Mise a jour RC principal (ID: ${rcId}) avec reference tarifRC: ${idTarifRC}`); + console.log('Donnees envoyees pour RC:', rcMainData); const updateRCResponse = await fetch(`/rc/update/${rcId}`, { method: 'POST', @@ -3118,15 +3465,15 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio }, }); const updateRCData = await updateRCResponse.json(); - console.log('📥 Réponse serveur RC update:', updateRCData); + console.log('Reponse serveur RC update:', updateRCData); if (!updateRCData.valid) { - console.error('❌ Échec de la mise à jour de RC principal:', updateRCData.message); + console.error('Echec de la mise a jour de RC principal:', updateRCData.message); return { valid: false, message: 'Échec mise à jour RC principal' }; } - console.log('✅ === TARIF RC SAUVEGARDÉ AVEC SUCCÈS ==='); - console.log('✅ RC ID:', rcId, '| TarifRC ID:', idTarifRC); + console.log('=== TARIF RC SAUVEGARDE AVEC SUCCES ==='); + console.log('RC ID:', rcId, '| TarifRC ID:', idTarifRC); return { valid: true, idTarifRC, idRC: rcId }; } catch (error) { console.error('Erreur lors de la sauvegarde de tarifRC:', error); @@ -3374,7 +3721,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio window.modalTarifCom.close(); // Afficher un loader - M.toast({html: '⏳ Sauvegarde en cours...', classes: 'blue'}); + M.toast({html: 'Sauvegarde en cours...', classes: 'blue'}); // Sauvegarder le tarif const saveResult = await saveTarifRC(); @@ -3384,7 +3731,7 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio // Rediriger vers la page projet après un court délai setTimeout(() => { - console.log('🚀 Redirection vers projet...'); + console.log('Redirection vers projet...'); // Construire l'URL de redirection vers projet const numParcours = parcours?.numParcours; if (numParcours) { @@ -3394,11 +3741,11 @@ window.initSubmenuForm = initSubmenuForm;// Module IIFE pour éviter la pollutio } }, 1000); } else { - M.toast({html: '❌ Erreur lors de la sauvegarde du tarif', classes: 'red'}); + M.toast({html: 'Erreur lors de la sauvegarde du tarif', classes: 'red'}); } } // Exposer les fonctions globalement pour y accéder depuis l'extérieur window.initSubmenuForm = init; window.saveTarifRC = saveTarifRC; -})(); \ No newline at end of file +})(); diff --git a/ecole/src/db/pb_data/data.db b/ecole/src/db/pb_data/data.db index 8a065ea0..7a47c5a3 100644 Binary files a/ecole/src/db/pb_data/data.db and b/ecole/src/db/pb_data/data.db differ diff --git a/ecole/src/db/pb_data/data.db-shm b/ecole/src/db/pb_data/data.db-shm index 19889b16..be1746d3 100644 Binary files a/ecole/src/db/pb_data/data.db-shm and b/ecole/src/db/pb_data/data.db-shm differ diff --git a/ecole/src/db/pb_data/data.db-wal b/ecole/src/db/pb_data/data.db-wal index 01d6ba37..d9bd6747 100644 Binary files a/ecole/src/db/pb_data/data.db-wal and b/ecole/src/db/pb_data/data.db-wal differ diff --git a/ecole/src/db/pb_data/logs.db b/ecole/src/db/pb_data/logs.db index a9c46b80..f159daa2 100644 Binary files a/ecole/src/db/pb_data/logs.db and b/ecole/src/db/pb_data/logs.db differ diff --git a/ecole/src/db/pb_data/logs.db-shm b/ecole/src/db/pb_data/logs.db-shm index 85ce056d..847f7c4d 100644 Binary files a/ecole/src/db/pb_data/logs.db-shm and b/ecole/src/db/pb_data/logs.db-shm differ diff --git a/ecole/src/db/pb_data/logs.db-wal b/ecole/src/db/pb_data/logs.db-wal index df2eb83a..bec807aa 100644 Binary files a/ecole/src/db/pb_data/logs.db-wal and b/ecole/src/db/pb_data/logs.db-wal differ diff --git a/ecole/src/db/pb_data_backup/data.db b/ecole/src/db/pb_data_backup/data.db new file mode 100644 index 00000000..5b45ac62 Binary files /dev/null and b/ecole/src/db/pb_data_backup/data.db differ diff --git a/ecole/src/db/pb_data_backup/logs.db b/ecole/src/db/pb_data_backup/logs.db new file mode 100644 index 00000000..4a2a061e Binary files /dev/null and b/ecole/src/db/pb_data_backup/logs.db differ diff --git a/ecole/src/db/pocketbase b/ecole/src/db/pocketbase new file mode 100755 index 00000000..c1d7d104 Binary files /dev/null and b/ecole/src/db/pocketbase differ