RC: nouveaux formulaires et synchro

This commit is contained in:
Alexis Burnaz 2026-03-19 10:26:15 +01:00
parent 0125e2ae69
commit 4919940a26
24 changed files with 10322 additions and 486 deletions

View File

@ -1,5 +1,8 @@
body {
font-family: 'Roboto', sans-serif;
background-color: white;
color: black;
color-scheme: light;
}
h1, h2, h3, h4, h5, h6 {

View File

@ -33,8 +33,16 @@ document.addEventListener('DOMContentLoaded', function() {
let produit = parcours["@expand"].contrat.produit
const produitObj = contrat?.["@expand"]?.enCours || null;
const tarif = produitObj?.["@expand"]?.tarif || null;
const projet = produitObj?.["@expand"]?.projet || null;
// RC utilise tarifRC/projetRC, les autres produits utilisent tarif/projet
let tarif, projet;
if (produit === "rc") {
tarif = produitObj?.["@expand"]?.tarifRC || null;
projet = produitObj?.["@expand"]?.projetRC || null;
} else {
tarif = produitObj?.["@expand"]?.tarif || null;
projet = produitObj?.["@expand"]?.projet || null;
}
let scriptSrc;
// let newScriptModuloSrc;
@ -54,11 +62,21 @@ document.addEventListener('DOMContentLoaded', function() {
scriptSrc = `/js/${submenu}-form.js`;
}
// RC utilise tarifRC/projetRC, les autres produits utilisent tarif/projet
let tarifId, projetId;
if (produit === "rc") {
tarifId = contrat?.["@expand"]?.enCours?.["@expand"]?.tarifRC?.id || null;
projetId = contrat?.["@expand"]?.enCours?.["@expand"]?.projetRC?.id || null;
} else {
tarifId = contrat?.["@expand"]?.enCours?.["@expand"]?.tarif?.id || null;
projetId = contrat?.["@expand"]?.enCours?.["@expand"]?.projet?.id || null;
}
const etapes = {
"client": contrat?.client || null,
"intermediaire": contrat?.intermediaire || null,
"tarif": contrat?.["@expand"]?.enCours?.["@expand"]?.tarif?.id || null,
"projet": contrat?.["@expand"]?.enCours?.["@expand"]?.projet?.id || null,
"tarif": tarifId,
"projet": projetId,
"contrat": null
}
@ -79,7 +97,6 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('step-' + key).classList.add('line')
}
}
showLoader();
// Charger le formulaire associé
fetch(fetchUrl)
@ -89,6 +106,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Suppression de tout script précédemment chargé
const oldScript = document.querySelector('script.dynamic-script');
if (oldScript) {
oldScript.remove();
}
@ -120,26 +138,26 @@ document.addEventListener('DOMContentLoaded', function() {
inputChanged = true
})
})
hideLoader();
})
.catch(error => console.error('Error:', error));
//A MODIFIER UNE FOIS QUE RC SERA ADAPTé AU PARCOURS
if (produit == "RC") {
if (Object.keys(contrat?.["@expand"]?.enCours).length > 1) {
document.getElementById('generateProject').disabled = false;
}
// Gestion des boutons de génération pour tous les produits
const hasClient = parcours["@expand"]?.contrat?.client != '';
const hasIntermediaire = parcours["@expand"]?.contrat?.intermediaire != '';
const hasProduit = produitObj != undefined;
// Bouton génération déclinaison tarifaire
if (hasClient && hasIntermediaire && hasProduit && tarif != null) {
document.getElementById('generateDeclinaison').disabled = false;
} else {
// Enable / disable bouton generate project
if (parcours["@expand"].contrat.client != '' && parcours["@expand"].contrat.intermediaire != '' && produitObj != undefined && projet != null) {
document.getElementById('generateProject').disabled = false;
}
// Enable / disable bouton generate déclinaison
if (parcours["@expand"].contrat.client != '' && parcours["@expand"].contrat.intermediaire != '' && produitObj != undefined && tarif != null) {
document.getElementById('generateDeclinaison').disabled = false;
}
document.getElementById('generateDeclinaison').disabled = true;
}
// Bouton génération projet
if (hasClient && hasIntermediaire && hasProduit && projet != null) {
document.getElementById('generateProject').disabled = false;
} else {
document.getElementById('generateProject').disabled = true;
}
}
@ -157,9 +175,6 @@ document.addEventListener('DOMContentLoaded', function() {
const parcours = JSON.parse(sessionStorage.getItem('parcours'));
var produit = parcours["@expand"].contrat.produit
const btn = this // bouton "générer projet"
btn.disabled = true; // le desactiver le temps du téléchargement
var fileName
switch (produit.toLowerCase()) {
case 'fac':
@ -183,21 +198,26 @@ document.addEventListener('DOMContentLoaded', function() {
link.download = fileName;
link.click();
})
.finally(() => {
btn.disabled = false; // réactiver le bouton a la fin du téléchargement
})
.catch(error => console.error('Error downloading file:', error));
});
// Fonction de génération de projet
document.getElementById('generateProject').addEventListener('click', function() {
document.getElementById('generateProject').addEventListener('click', async function() {
const numParcours = getNumParcoursFromURL();
let filename;
const parcours = JSON.parse(sessionStorage.getItem('parcours'));
let produit = parcours["@expand"].contrat.produit
const btn = this // bouton "générer projet"
btn.disabled = true; // le desactiver le temps du téléchargement
// Sauvegarder les données du projet avant de générer le document (si RC)
if (produit.toLowerCase() === 'rc' && typeof window.saveProjetRC === 'function') {
console.log('Sauvegarde des données projet RC avant génération...');
const saveResult = await window.saveProjetRC();
if (!saveResult || !saveResult.valid) {
console.error('Échec de la sauvegarde du projet RC');
M.toast({html: 'Erreur lors de la sauvegarde du projet. Veuillez réessayer.'});
return;
}
}
// Envoi de la requête POST au serveur pour générer le projet
fetch(`/generate/${produit}/projet/${numParcours}`, {
@ -228,23 +248,17 @@ document.addEventListener('DOMContentLoaded', function() {
window.URL.revokeObjectURL(url); // Nettoie l'URL objet
a.remove(); // Supprime l'élément a du document
})
.finally(() => {
btn.disabled = false; // réactiver le bouton a la fin du téléchargement
})
.catch(error => console.error('Erreur lors de la génération du projet:', error));
});
document.getElementById('generateDeclinaison').addEventListener('click', function() {
document.getElementById('generateDeclinaison').addEventListener('click', async function() {
const numParcours = getNumParcoursFromURL();
let filename;
const parcours = JSON.parse(sessionStorage.getItem('parcours'));
let produit = parcours["@expand"].contrat.produit
const btn = this // bouton "générer déclinaison tarifaire"
btn.disabled = true; // le desactiver le temps du téléchargement
// Envoi de la requête POST au serveur pour générer le projet
// Envoi de la requête POST au serveur pour générer la déclinaison tarifaire
fetch(`/generate/${produit}/tarif/${numParcours}`, {
method: 'POST',
headers: {
@ -274,9 +288,6 @@ document.addEventListener('DOMContentLoaded', function() {
window.URL.revokeObjectURL(url); // Nettoie l'URL objet
a.remove(); // Supprime l'élément a du document
})
.finally(() => {
btn.disabled = false; // réactiver le bouton a la fin du téléchargement
})
.catch(error => console.error('Erreur lors de la génération du projet:', error));
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,647 @@
/**
*
* 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
*/
(function(window) {
'use strict';
const { toNumber, getValue, setValue, getElementByIdFlexible } = window.RCSync;
// ═══════════════════════════════════════════════════════════════════════
// MAPPING DES CHAMPS TARIF ↔ PROJET
// ═══════════════════════════════════════════════════════════════════════
/**
* Mapping complet des champs entre Tarif et Projet.
* Permet la synchronisation bidirectionnelle.
*
* Structure: { tarifFieldId: projetFieldId }
*/
const FIELD_MAPPING = {
// Informations générales
'CA': 'CA',
'chiffreAffaire': 'CA',
'nbVehicules': 'nombreVehicules',
'nbrVehicule': 'nombreVehicules',
// Type de cotisation
'cotisation': 'typeCot',
// Activités RCC - Voiturier
'checkVoiturier': 'actVoiturier',
'capitalVoiturier': 'valueActVoiturier',
// Activités RCC - Commissionnaire (Multimodal)
'checkCommissionnaire': 'actMultimodal',
'capitalCommissionnaire': 'valueActMultimodal',
// Activités RCC - Déménageur
'checkDemenageur': 'actDemEntr',
'capitalDemenageur': 'valueActDemEntr',
// Activités RCC - Logistique
'checkLogistique': 'actPrestaLog',
'capitalLogistique': 'valueActPrestaLog',
// RCE
'checkRCE': 'autresRC',
// Zones géographiques
'zone1': 'zone1',
'zone2': 'zone2',
'zone3': 'zone3',
'zone4': 'zone4',
'zone5': 'zone5',
'zone6': 'zone6',
// Protection Juridique
'checkPJ': 'pj',
// Garanties additionnelles - Engagements complémentaires
'checkDomImmat': 'extRCCConfie', // Simplifié
'checkContConf': 'extRCCConfie',
'checkTPPC': 'extRCCTPPC',
// Extensions RCC
'checkStationLavage': 'extRCCModifCalArrim',
// Extensions RCE
// (géré séparément car structure différente)
// Sinistralité
'sinistre': 'nbSinistres3ans'
};
// ═══════════════════════════════════════════════════════════════════════
// COLLECTE DES DONNÉES COMPLÈTES
// ═══════════════════════════════════════════════════════════════════════
/**
* 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);
*/
function collectAllTarifData() {
// Références flexibles aux éléments
const getEl = getElementByIdFlexible;
const data = {
// ═══ INFORMATIONS GÉNÉRALES ═══
typeCotisation: document.querySelector('input[name="cotisation"]:checked')?.value || null,
ca: toNumber(getValue('CA') || getValue('chiffreAffaire')),
nombreVehicules: Math.max(0, Math.round(toNumber(getValue('nbVehicules') || getValue('nbrVehicule')))),
// ═══ ACTIVITÉS RCC ═══
activites: {
voiturier: {
checked: getValue('checkVoiturier') || false,
capital: toNumber(getValue('capitalVoiturier')),
pourcentage: toNumber(getValue('pourcent_voiturier') || getValue('pourcentVoiturier/Loueur')),
isSet: Boolean(getValue('pourcent_voiturier')?.trim())
},
commissionnaire: {
checked: getValue('checkCommissionnaire') || false,
capital: toNumber(getValue('capitalCommissionnaire')),
pourcentage: toNumber(getValue('pourcent_commissionnaire')),
isSet: Boolean(getValue('pourcent_commissionnaire')?.trim())
},
demenageur: {
checked: getValue('checkDemenageur') || false,
capital: toNumber(getValue('capitalDemenageur')),
pourcentage: toNumber(getValue('pourcent_demenageur')),
isSet: Boolean(getValue('pourcent_demenageur')?.trim())
},
logistique: {
checked: getValue('checkLogistique') || false,
capital: toNumber(getValue('capitalLogistique')),
pourcentage: toNumber(getValue('pourcent_logistique')),
isSet: Boolean(getValue('pourcent_logistique')?.trim())
},
autocariste: {
checked: getValue('checkAutocariste') || false,
capital: toNumber(getValue('capitalAutocariste')),
pourcentage: toNumber(getValue('pourcent_autocariste')),
isSet: Boolean(getValue('pourcent_autocariste')?.trim())
},
autres: {
checked: getValue('checkAutres') || false,
capital: toNumber(getValue('capitalAutres')),
pourcentage: toNumber(getValue('pourcent_autres')),
isSet: Boolean(getValue('pourcent_autres')?.trim())
}
},
// ═══ RCE ═══
rce: {
checked: getValue('checkRCE') || false
},
// ═══ ACTIVITÉS COMPLÉMENTAIRES (JSON) ═══
activitesComplementaires: {
voiturier: collectActivitesComplJSON('voiturier'),
commissionnaire: collectActivitesComplJSON('commissionnaire'),
demenageur: collectActivitesComplJSON('demenageur'),
logistique: collectActivitesComplJSON('logistique')
},
// ═══ MARCHANDISES (JSON) ═══
marchandises: {
voiturier: collectMarchandisesJSON('voiturier'),
commissionnaire: collectMarchandisesJSON('commissionnaire'),
demenageur: collectMarchandisesJSON('demenageur'),
logistique: collectMarchandisesJSON('logistique'),
autocariste: collectMarchandisesJSON('autocariste'),
autres: collectMarchandisesJSON('autres')
},
// ═══ ZONES GÉOGRAPHIQUES ═══
zones: {
zone1: getValue('zone1') || false,
zone2: getValue('zone2') || false,
zone3: getValue('zone3') || false,
zone4: getValue('zone4') || false,
zone5: getValue('zone5') || false,
zone6: getValue('zone6') || false
},
// ═══ ENGAGEMENTS COMPLÉMENTAIRES ═══
engagementsComplementaires: {
domicileImmatriculation: {
checked: getValue('checkDomImmat') || false,
capital: toNumber(getValue('inputDomImmat'))
},
contenantConfie: {
checked: getValue('checkContConf') || false,
capital: toNumber(getValue('inputContConf'))
},
differenceInventaire: {
checked: getValue('checkDiffInv') || false,
capital: toNumber(getValue('inputDiffInv'))
}
},
// ═══ GARANTIES ADDITIONNELLES ═══
garantiesAdditionnelles: {
stationLavage: getValue('checkStationLavage') || false,
garageInterne: getValue('checkGarageInterne') || false,
cse: getValue('checkCSE') || false,
tppc: {
checked: getValue('checkTPPC') || false,
capital: toNumber(getValue('selTPPCcapital')),
vehicules: Math.max(0, Math.round(toNumber(getValue('selTPPCveh'))))
},
pj: getValue('checkPJ') || false
},
// ═══ SINISTRALITÉ ═══
sinistralite: {
nombre3ans: toNumber(getValue('sinistre')),
montant3ans: 0 // TODO: ajouter si champ existe
},
// ═══ RÉSULTATS DE CALCUL ═══
resultats: {
// Franchise 250
fr250: {
primeRCC: toNumber(getEl('rccFr250')?.textContent),
primeRCE: toNumber(getEl('rceFr250')?.textContent),
primePJ: toNumber(getEl('pjFr250')?.textContent),
primeTotal: toNumber(getEl('priceFr250')?.textContent),
tauxRCC: toNumber(getEl('tauxRccFr250')?.textContent),
tauxRCE: toNumber(getEl('tauxRceFr250')?.textContent),
tauxGlobal: toNumber(getEl('tauxGlobalFr250')?.textContent)
},
// Franchise 400
fr400: {
primeRCC: toNumber(getEl('rccFr400')?.textContent),
primeRCE: toNumber(getEl('rceFr400')?.textContent),
primePJ: toNumber(getEl('pjFr400')?.textContent),
primeTotal: toNumber(getEl('priceFr400')?.textContent),
tauxRCC: toNumber(getEl('tauxRccFr400')?.textContent),
tauxRCE: toNumber(getEl('tauxRceFr400')?.textContent),
tauxGlobal: toNumber(getEl('tauxGlobalFr400')?.textContent)
},
// Franchise 2000
fr2000: {
primeRCC: toNumber(getEl('rccFr2000')?.textContent),
primeRCE: toNumber(getEl('rceFr2000')?.textContent),
primePJ: toNumber(getEl('pjFr2000')?.textContent),
primeTotal: toNumber(getEl('priceFr2000')?.textContent),
tauxRCC: toNumber(getEl('tauxRccFr2000')?.textContent),
tauxRCE: toNumber(getEl('tauxRceFr2000')?.textContent),
tauxGlobal: toNumber(getEl('tauxGlobalFr2000')?.textContent)
},
franchiseChoisie: window.franchiseChoisie || null,
tarifCommercial: toNumber(getValue('tarifCom'))
},
// ═══ COMMENTAIRE ═══
commentaire: getValue('commentaire') || ''
};
console.log('📊 Données Tarif collectées:', 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
*/
function collectActivitesComplJSON(typeActivite) {
let name;
switch(typeActivite.toLowerCase()) {
case 'voiturier':
name = 'actComplVoiturier/Loueur';
break;
case 'commissionnaire':
name = 'actComplCommissionnaire de Transport';
break;
case 'demenageur':
name = 'actComplDéménageur';
break;
case 'logistique':
name = 'actComplLogistique';
break;
default:
return JSON.stringify([]);
}
const checkboxes = document.querySelectorAll(`[name="${name}"] input[type="checkbox"]:checked`);
const activites = [];
checkboxes.forEach(cb => {
const text = cb.nextElementSibling ? cb.nextElementSibling.textContent.trim() : cb.value;
activites.push(text);
});
return JSON.stringify(activites);
}
/**
* Fonction helper pour collecter les marchandises depuis le formulaire.
*
* @param {string} typeActivite - Type d'activité
* @returns {string} JSON array des marchandises cochées
* @private
*/
function collectMarchandisesJSON(typeActivite) {
let name;
switch(typeActivite.toLowerCase()) {
case 'voiturier':
name = 'marVoiturier/Loueur';
break;
case 'commissionnaire':
name = 'marCommissionnaire de Transport';
break;
case 'demenageur':
name = 'marDéménageur';
break;
case 'logistique':
name = 'marLogistique';
break;
case 'autocariste':
name = 'marAutocariste';
break;
case 'autres':
name = 'marAutres activites';
break;
default:
return JSON.stringify([]);
}
const checkboxes = document.querySelectorAll(`[name="${name}"] input[type="checkbox"]:checked`);
const marchandises = [];
checkboxes.forEach(cb => {
const text = cb.nextElementSibling ? cb.nextElementSibling.textContent.trim() : cb.value;
marchandises.push(text);
});
return JSON.stringify(marchandises);
}
// ═══════════════════════════════════════════════════════════════════════
// PRÉ-REMPLISSAGE TARIF → PROJET
// ═══════════════════════════════════════════════════════════════════════
/**
* Pré-remplit le formulaire Projet avec les données du Tarif.
* Cette fonction est appelée quand l'utilisateur passe du Tarif au Projet.
*
* @param {Object} tarifData - Données complètes du tarif (de collectAllTarifData)
*
* @example
* const tarifData = collectAllTarifData();
* prefillProjetFromTarif(tarifData);
*/
function prefillProjetFromTarif(tarifData) {
if (!tarifData) {
console.warn('Pas de données tarif à pré-remplir');
return;
}
console.log('📝 Pré-remplissage Projet depuis Tarif...');
try {
// ═══ INFORMATIONS GÉNÉRALES ═══
// CA
if (tarifData.ca) {
setValue('CA', tarifData.ca);
console.log(' ✓ CA:', tarifData.ca);
}
// Type de cotisation
if (tarifData.typeCotisation) {
const radio = document.querySelector(`input[name="typeCot"][value="${tarifData.typeCotisation}"]`);
if (radio) {
radio.checked = true;
console.log(' ✓ Type cotisation:', tarifData.typeCotisation);
}
}
// Nombre de véhicules
if (tarifData.nombreVehicules) {
setValue('nombreVehicules', tarifData.nombreVehicules);
console.log(' ✓ Véhicules:', tarifData.nombreVehicules);
}
// ═══ ACTIVITÉS ═══
const activitySelector = document.getElementById('activity-selector');
if (activitySelector && tarifData.activites) {
const activitesToAdd = [];
if (tarifData.activites.voiturier?.checked) {
activitesToAdd.push('Voiturier/Loueur');
}
if (tarifData.activites.commissionnaire?.checked) {
activitesToAdd.push('Commissionnaire de Transport');
}
if (tarifData.activites.demenageur?.checked) {
activitesToAdd.push('Déménageur d\'entreprises');
}
if (tarifData.activites.logistique?.checked) {
activitesToAdd.push('Prestataire logistique');
}
if (tarifData.activites.autocariste?.checked) {
activitesToAdd.push('Autocariste');
}
if (tarifData.activites.autres?.checked) {
activitesToAdd.push('Autres activités');
}
// Sélectionner les options dans le select
Array.from(activitySelector.options).forEach(option => {
if (activitesToAdd.includes(option.value)) {
option.selected = true;
}
});
// Trigger change pour créer les chips Materialize
const event = new Event('change', { bubbles: true });
activitySelector.dispatchEvent(event);
console.log(' ✓ Activités:', activitesToAdd.length);
}
// ═══ MARCHANDISES ═══
const marchandiseSelector = document.getElementById('marchandise-selector');
if (marchandiseSelector && tarifData.marchandises) {
const marchandisesToSelect = [];
// Parser les marchandises de chaque type
['voiturier', 'commissionnaire', 'demenageur', 'logistique', 'autocariste', 'autres'].forEach(type => {
const marchArray = tarifData.marchandises[type];
if (Array.isArray(marchArray)) {
marchArray.forEach(m => marchandisesToSelect.push(m));
}
});
// Sélectionner dans le select
Array.from(marchandiseSelector.options).forEach(option => {
if (marchandisesToSelect.includes(option.text) || marchandisesToSelect.includes(option.value)) {
option.selected = true;
}
});
const event = new Event('change', { bubbles: true });
marchandiseSelector.dispatchEvent(event);
console.log(' ✓ Marchandises:', marchandisesToSelect.length);
}
// ═══ ZONES GÉOGRAPHIQUES ═══
if (tarifData.zones) {
let zonesCount = 0;
Object.keys(tarifData.zones).forEach(zoneKey => {
const checkbox = document.getElementById(zoneKey);
if (checkbox && tarifData.zones[zoneKey]) {
checkbox.checked = true;
zonesCount++;
}
});
console.log(' ✓ Zones:', zonesCount);
}
// ═══ PROTECTION JURIDIQUE ═══
if (tarifData.garantiesAdditionnelles?.pj) {
const switchPJ = document.getElementById('switchPJ');
if (switchPJ) {
switchPJ.checked = true;
console.log(' ✓ PJ activée');
// Afficher la section PJ
const pjSection = document.getElementById('pj-section');
if (pjSection) pjSection.style.display = 'block';
}
}
// ═══ RCE ═══
if (tarifData.rce?.checked) {
const choixRCE = document.getElementById('choixRCE');
if (choixRCE) {
choixRCE.checked = true;
console.log(' ✓ RCE activée');
// Afficher la section RCE
const rceSection = document.getElementById('section-rce');
if (rceSection) rceSection.style.display = 'block';
}
}
// ═══ TPPC ═══
if (tarifData.garantiesAdditionnelles?.tppc?.checked) {
const checkTPPC = document.getElementById('checkTPPC');
if (checkTPPC) {
checkTPPC.checked = true;
if (tarifData.garantiesAdditionnelles.tppc.capital) {
setValue('capitalTPPC', tarifData.garantiesAdditionnelles.tppc.capital);
}
if (tarifData.garantiesAdditionnelles.tppc.vehicules) {
setValue('vehiculesTPPC', tarifData.garantiesAdditionnelles.tppc.vehicules);
}
console.log(' ✓ TPPC');
}
}
// ═══ ENGAGEMENTS COMPLÉMENTAIRES ═══
const engagements = tarifData.engagementsComplementaires;
if (engagements) {
if (engagements.domicileImmatriculation?.checked) {
setValue('checkDomImmat', true);
console.log(' ✓ Domicile immatriculation');
}
if (engagements.contenantConfie?.checked) {
setValue('checkContConf', true);
console.log(' ✓ Contenant confié');
}
}
// ═══ SINISTRALITÉ ═══
if (tarifData.sinistralite) {
if (tarifData.sinistralite.nombre3ans) {
setValue('nbSinistres3ans', tarifData.sinistralite.nombre3ans);
}
if (tarifData.sinistralite.montant3ans) {
setValue('montantSinistres3ans', tarifData.sinistralite.montant3ans);
}
console.log(' ✓ Sinistralité');
}
// ═══ RÉSULTATS TARIFAIRES ═══
if (tarifData.resultats) {
const res = tarifData.resultats;
// Taux
if (res.tauxRCCHT) setValue('tauxRCCHT', res.tauxRCCHT);
if (res.tauxRCCTTC) setValue('tauxRCCTTC', res.tauxRCCTTC);
if (res.tauxRCEHT) setValue('tauxRCEHT', res.tauxRCEHT);
if (res.tauxRCETTC) setValue('tauxRCETTC', res.tauxRCETTC);
if (res.tauxTotalHT) setValue('tauxTotalHT', res.tauxTotalHT);
if (res.tauxTotalTTC) setValue('tauxTotalTTC', res.tauxTotalTTC);
// Cotisations
if (res.cotRCCHT) setValue('cotRCCHT', res.cotRCCHT);
if (res.cotRCCTTC) setValue('cotRCCTTC', res.cotRCCTTC);
if (res.cotRCEHT) setValue('cotRCEHT', res.cotRCEHT);
if (res.cotRCETTC) setValue('cotRCETTC', res.cotRCETTC);
if (res.cotPJHT) setValue('cotPJHT', res.cotPJHT);
if (res.cotPJTTC) setValue('cotPJTTC', res.cotPJTTC);
if (res.cotTotalHT) setValue('cotTotalHT', res.cotTotalHT);
if (res.cotTotalTTC) setValue('cotTotalTTC', res.cotTotalTTC);
console.log(' ✓ Résultats tarifaires');
}
// Forcer la mise à jour des éléments Materialize
if (window.M && window.M.FormSelect) {
const selects = document.querySelectorAll('select');
window.M.FormSelect.init(selects);
}
if (window.M && window.M.updateTextFields) {
window.M.updateTextFields();
}
console.log('✅ Pré-remplissage Projet terminé');
} catch (error) {
console.error('❌ Erreur lors du pré-remplissage Projet:', error);
}
}
// ═══════════════════════════════════════════════════════════════════════
// PRÉ-REMPLISSAGE PROJET → TARIF
// ═══════════════════════════════════════════════════════════════════════
/**
* Pré-remplit le formulaire Tarif avec les données du Projet.
* Cette fonction est appelée quand l'utilisateur passe du Projet au Tarif.
*
* @param {Object} projetData - Données complètes du projet
*
* @example
* prefillTarifFromProjet(projetData);
*/
function prefillTarifFromProjet(projetData) {
if (!projetData) {
console.warn('Pas de données projet à pré-remplir');
return;
}
console.log('📝 Pré-remplissage Tarif depuis Projet...');
try {
// CA
if (projetData.ca) {
setValue('CA', projetData.ca);
}
// Type de cotisation
if (projetData.typeCot) {
const radio = document.querySelector(`input[name="cotisation"][value="${projetData.typeCot}"]`);
if (radio) radio.checked = true;
}
// Zones géographiques
['zone1', 'zone2', 'zone3', 'zone4', 'zone5', 'zone6'].forEach(zone => {
if (projetData[zone]) {
setValue(zone, true);
}
});
// PJ
if (projetData.pj) {
setValue('checkPJ', true);
}
// RCE
if (projetData.autresRC) {
setValue('checkRCE', true);
}
console.log('✅ Pré-remplissage Tarif terminé');
} catch (error) {
console.error('❌ Erreur lors du pré-remplissage Tarif:', error);
}
}
// ═══════════════════════════════════════════════════════════════════════
// EXPORT PUBLIC
// ═══════════════════════════════════════════════════════════════════════
window.RCDataManager = {
collectAllTarifData,
prefillProjetFromTarif,
prefillTarifFromProjet,
FIELD_MAPPING
};
console.log('✅ RC Data Manager loaded');
})(window);

View File

@ -0,0 +1,388 @@
/**
*
* 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
*/
(function(window) {
'use strict';
// Attendre que les dépendances soient chargées
if (!window.RCSync || !window.RCDataManager) {
console.error('❌ Dépendances RC Sync manquantes');
return;
}
const { isChangeImpactingTarif, showReturnToTarifModal } = window.RCSync;
const { collectAllTarifData, prefillProjetFromTarif, prefillTarifFromProjet } = window.RCDataManager;
// ═══════════════════════════════════════════════════════════════════════
// CONFIGURATION
// ═══════════════════════════════════════════════════════════════════════
const SESSION_STORAGE_KEYS = {
TARIF_DATA: 'rc_tarif_validated_data',
PROJET_DATA: 'rc_projet_data',
TARIF_ORIGINAL: 'rc_tarif_original_for_comparison'
};
// ═══════════════════════════════════════════════════════════════════════
// DÉTECTION DE LA PAGE ACTIVE
// ═══════════════════════════════════════════════════════════════════════
/**
* Détecte la page active (tarif ou projet) depuis l'URL.
*
* @returns {'tarif'|'projet'|null} Page active ou null
*/
function detectActivePage() {
const params = new URLSearchParams(window.location.search);
const submenu = params.get('submenu');
if (submenu === 'tarif' || submenu === 'tarifrc') {
return 'tarif';
} else if (submenu === 'projet' || submenu === 'projetrc') {
return 'projet';
}
return null;
}
// ═══════════════════════════════════════════════════════════════════════
// GESTION SESSIONSTORAGE
// ═══════════════════════════════════════════════════════════════════════
/**
* Sauvegarde les données du tarif validé dans sessionStorage.
*
* @param {Object} tarifData - Données complètes du tarif
*/
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');
} catch (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
*/
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) {
return null;
}
}
// ═══════════════════════════════════════════════════════════════════════
// HOOK: APRÈS VALIDATION TARIF
// ═══════════════════════════════════════════════════════════════════════
/**
* 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.
*/
function onTarifValidated() {
console.log('🎯 Hook: Tarif validé, collecte des données...');
try {
// Collecter toutes les données du tarif
const tarifData = collectAllTarifData();
// Sauvegarder en session pour le pré-remplissage projet
saveTarifDataToSession(tarifData);
console.log('✅ Données tarif prêtes pour le projet');
} catch (error) {
console.error('❌ Erreur hook tarif validé:', error);
}
}
// ═══════════════════════════════════════════════════════════════════════
// INITIALISATION PAGE PROJET
// ═══════════════════════════════════════════════════════════════════════
/**
* 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
*/
function initProjetPage() {
console.log('🚀 Initialisation RC Orchestrator pour page Projet...');
// Les données rc/tarif/projet sont DÉJÀ chargées depuis la base
// par le code existant dans projet-form-RC.js
// On configure juste la détection des changements
setTimeout(() => {
setupProjetChangeDetection();
}, 1000); // Attendre que prefillFromTarif() ait fini
}
/**
* 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
*/
function setupProjetChangeDetection() {
// Les données originales sont dans les variables globales window.tarif et window.rc
// définies par projet-form-RC.js
const tarifOriginal = window.tarif;
const rcOriginal = window.rc;
if (!tarifOriginal && !rcOriginal) {
console.log(' Pas de tarif/rc, pas de détection');
return;
}
console.log('👁️ Configuration détection changements...');
console.log('📋 Données originales:', { tarif: tarifOriginal, rc: rcOriginal });
// Liste COMPLÈTE des éléments à surveiller (tous les champs impactants)
const elementsToWatch = [
// CA et infos générales
'CA', 'chiffreAffaire', 'nombreVehicules', 'nbrVehicule',
// Zones géographiques
'zone1', 'zone2', 'zone3', 'zone4', 'zone5', 'zone6',
// Protection Juridique
'switchPJ', 'checkPJ',
// RCE
'choixRCE', 'checkRCE',
// TPPC
'checkTPPC', 'capitalTPPC', 'vehiculesTPPC',
// Engagements complémentaires
'checkDomImmat', 'checkContConf', 'checkDiffInv',
// Garanties additionnelles
'checkStationLavage', 'checkGarageInterne', 'checkCSE',
// Sinistralité
'nbSinistres3ans', 'montantSinistres3ans',
// Autres
'programmeInternationale', 'participationResultat'
];
// Ajouter des listeners sur tous les éléments surveillés
elementsToWatch.forEach(elementId => {
const element = document.getElementById(elementId);
if (!element) return;
const eventType = element.type === 'checkbox' ? 'change' : 'blur';
element.addEventListener(eventType, function(e) {
const fieldName = this.id;
const newValue = this.type === 'checkbox' ? this.checked : this.value;
console.log(`🔍 Changement détecté: ${fieldName} = ${newValue}`);
// Vérifier si c'est un champ impactant
if (isFieldImpactingTarif(fieldName)) {
console.warn(`⚠️ "${fieldName}" impacte le tarif !`);
showReturnToTarifModal(fieldName);
} else {
console.log(` "${fieldName}" n'impacte pas le tarif`);
}
});
});
// Surveiller les radio buttons (type de cotisation)
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 !`);
showReturnToTarifModal('Type de cotisation');
});
});
// Surveiller le select activités
const activitySelector = document.getElementById('activity-selector');
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 !`);
showReturnToTarifModal('Activités');
});
}
// Surveiller le select marchandises
const marchandiseSelector = document.getElementById('marchandise-selector');
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 !`);
showReturnToTarifModal('Marchandises');
});
}
// Surveiller les boutons d'action sur les zones (Monde entier / Reset)
['btnMondeEntier', 'btnReset'].forEach(btnId => {
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 !');
showReturnToTarifModal('Zones géographiques');
});
});
console.log('✅ Détection changements configurée sur tous les champs impactants');
}
// ═══════════════════════════════════════════════════════════════════════
// INITIALISATION PAGE TARIF
// ═══════════════════════════════════════════════════════════════════════
/**
* Initialise le formulaire tarif au chargement.
* Pré-remplit depuis le projet si l'utilisateur vient du projet.
*/
function initTarifPage() {
console.log('🚀 Initialisation page Tarif...');
// Vérifier si on vient du projet
const projetData = JSON.parse(sessionStorage.getItem(SESSION_STORAGE_KEYS.PROJET_DATA) || 'null');
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...');
setTimeout(() => {
prefillTarifFromProjet(projetData);
}, 500);
}
}
// ═══════════════════════════════════════════════════════════════════════
// INTERCEPTION DES FONCTIONS EXISTANTES
// ═══════════════════════════════════════════════════════════════════════
/**
* Intercepte la fonction de validation du tarif commercial existante.
* Ajoute notre hook avant la redirection.
*/
function interceptTarifValidation() {
// Attendre que la fonction window.saveTarifRC soit disponible
const checkInterval = setInterval(() => {
if (window.saveTarifRC) {
clearInterval(checkInterval);
// Sauvegarder la fonction originale
const originalSaveTarifRC = window.saveTarifRC;
// Remplacer par notre version wrappée
window.saveTarifRC = async function(...args) {
console.log('🎯 Interception saveTarifRC...');
// Appeler la fonction originale
const result = await originalSaveTarifRC.apply(this, args);
// Si succès, appeler notre hook
if (result && result.valid) {
onTarifValidated();
}
return result;
};
console.log('✅ saveTarifRC intercepté');
}
}, 100);
// Timeout après 5 secondes
setTimeout(() => clearInterval(checkInterval), 5000);
}
// ═══════════════════════════════════════════════════════════════════════
// DÉMARRAGE AUTOMATIQUE
// ═══════════════════════════════════════════════════════════════════════
/**
* Initialise l'orchestrateur au chargement de la page.
*/
function init() {
console.log('🎼 RC Sync Orchestrator: Démarrage...');
const activePage = detectActivePage();
console.log(`📄 Page active détectée: ${activePage || 'aucune'}`);
if (activePage === 'tarif') {
interceptTarifValidation();
// Attendre que le formulaire soit initialisé
setTimeout(() => {
initTarifPage();
}, 1000);
} else if (activePage === 'projet') {
// Attendre que le formulaire soit initialisé
setTimeout(() => {
initProjetPage();
}, 1000);
}
}
// Démarrage au chargement du DOM
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// ═══════════════════════════════════════════════════════════════════════
// EXPORT PUBLIC
// ═══════════════════════════════════════════════════════════════════════
window.RCOrchestrator = {
onTarifValidated,
initProjetPage,
initTarifPage,
saveTarifDataToSession,
getTarifDataFromSession
};
console.log('✅ RC Sync Orchestrator loaded');
})(window);

View File

@ -0,0 +1,464 @@
/**
*
* 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
*/
(function(window) {
'use strict';
// ═══════════════════════════════════════════════════════════════════════
// CONSTANTES
// ═══════════════════════════════════════════════════════════════════════
/**
* 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<string>}
*/
const TARIF_IMPACTING_FIELDS = [
// Chiffre d'affaires et type de contrat
'ca', 'chiffreAffaires', 'CA',
'typeCotisation', 'cotisation',
'nombreVehicules', 'nbVehicules',
// Activités RCC
'checkVoiturier', 'capitalVoiturier', 'actVoiturier',
'checkCommissionnaire', 'capitalCommissionnaire', 'actMultimodal',
'checkDemenageur', 'capitalDemenageur',
'checkLogistique', 'capitalLogistique',
'checkAutocariste', 'capitalAutocariste',
'checkAutres', 'capitalAutres',
// RCE
'checkRCE', 'autresRC',
// Activités complémentaires
'actComplVoiturier', 'actComplCommissionnaire', 'actComplDemenageur', 'actComplLogistique',
'activitesVoiturier', 'activitesCommissionnaire', 'activitesDemenageur', 'activitesLogistique',
// Marchandises
'marchandisesVoiturier', 'marchandisesCommissionnaire', 'marchandisesDemenageur',
'marchandisesLogistique', 'marchandisesAutocariste', 'marchandisesAutres',
'marOrdinaire', 'marRoulant', 'marEngins', 'marRoulantDem', 'marMobilerUsag',
'marPerissable', 'marAnimaux', 'marCiterne', 'marBeton', 'marExceptionnels', 'marVrac',
// Zones géographiques
'zone1', 'zone2', 'zone3', 'zone4', 'zone5', 'zone6',
// Extensions de garantie RCC
'extRCCModifCalArrim', 'extRCCFerroutage', 'extRCCFraisRecons',
'extRCCConfie', 'typeExtConfies', 'extRCCTPPC', 'extRCCRegie', 'extRCCSansMontageDemontage',
'checkDomImmat', 'capitalDomImmat', 'checkContConf', 'capitalContConf',
'checkDiffInv', 'capitalDiffInv', 'checkTPPC', 'capitalTPPC', 'vehiculesTPPC',
// Extensions de garantie RCE
'extRCEBraDebra', 'extRCEMontageDemontage',
// Garanties additionnelles
'checkStationLavage', 'checkGarageInterne', 'checkCSE', 'checkPJ', 'pj',
// Sinistralité
'sinistre', 'nbSinistres3ans', 'montantSinistres3ans'
];
// ═══════════════════════════════════════════════════════════════════════
// HELPERS - MANIPULATION DE VALEURS
// ═══════════════════════════════════════════════════════════════════════
/**
* 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
*/
function toNumber(x) {
if (x == null) return 0;
let value = String(x).trim();
if (!value) return 0;
value = value
.replace(/\s/g, '')
.replace(/[^\d.,-]/g, '');
if (!value) return 0;
const isNegative = value.startsWith('-');
value = value.replace(/-/g, '');
if (isNegative && value) {
value = '-' + value;
}
const hasComma = value.includes(',');
const hasDot = value.includes('.');
if (hasComma) {
value = value.replace(/\./g, '').replace(/,/g, '.');
} else if (hasDot) {
const dotMatches = value.match(/\./g);
const dotCount = dotMatches ? dotMatches.length : 0;
if (dotCount > 1) {
const parts = value.split('.');
const lastSegment = parts[parts.length - 1];
if (lastSegment.length === 3) {
value = parts.join('');
} else {
value = parts.slice(0, -1).join('') + '.' + lastSegment;
}
}
}
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : 0;
}
/**
* 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 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");
*/
function getElementByIdFlexible(id) {
if (!id) return null;
const direct = document.getElementById(id);
if (direct) return direct;
try {
return document.querySelector(`[id="${id.replace(/"/g, '\\"')}"]`);
} catch (err) {
return null;
}
}
/**
* 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
*/
function getValue(elementId) {
const element = getElementByIdFlexible(elementId);
if (!element) return null;
if (element.type === 'checkbox') {
return element.checked;
} else if (element.type === 'radio') {
const checked = document.querySelector(`input[name="${element.name}"]:checked`);
return checked ? checked.value : null;
} else if (element.tagName === 'SELECT') {
return element.value;
} else if (element.value !== undefined) {
return element.value;
} else {
return element.textContent || element.innerText || null;
}
}
/**
* 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);
*/
function setValue(elementId, value) {
const element = getElementByIdFlexible(elementId);
if (!element) {
console.warn(`Élément non trouvé: ${elementId}`);
return;
}
if (element.type === 'checkbox') {
element.checked = Boolean(value);
} else if (element.type === 'radio') {
const radio = document.querySelector(`input[name="${element.name}"][value="${value}"]`);
if (radio) radio.checked = true;
} else if (element.tagName === 'SELECT') {
element.value = value;
// Réinitialiser Materialize select si présent
if (window.M && window.M.FormSelect) {
const instance = window.M.FormSelect.getInstance(element);
if (instance) instance.destroy();
window.M.FormSelect.init(element);
}
} else if (element.value !== undefined) {
element.value = value;
} else {
element.textContent = value;
}
}
// ═══════════════════════════════════════════════════════════════════════
// 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
*/
function isFieldImpactingTarif(fieldName) {
return TARIF_IMPACTING_FIELDS.some(field =>
fieldName.includes(field) || field.includes(fieldName)
);
}
/**
* 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");
*/
function showReturnToTarifModal(fieldName) {
const modalId = 'modalRetourTarif';
let modal = document.getElementById(modalId);
// Créer le modal s'il n'existe pas
if (!modal) {
modal = createReturnToTarifModal();
document.body.appendChild(modal);
}
// Mettre à jour le message si un champ est spécifié
if (fieldName) {
const messageEl = modal.querySelector('#modalRetourTarifMessage');
if (messageEl) {
messageEl.innerHTML = `
Vous avez modifié <strong>"${fieldName}"</strong> qui impacte le calcul du tarif.
<br><br>
Vous devez retourner sur le formulaire Tarif pour recalculer et valider le nouveau tarif.
`;
}
}
// Ouvrir le modal
if (window.M && window.M.Modal) {
const instance = window.M.Modal.getInstance(modal) || window.M.Modal.init(modal);
instance.open();
}
}
/**
* 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 = `
<div class="modal-content">
<h5> Modification impactant le tarif</h5>
<p id="modalRetourTarifMessage">
Vous avez modifié une donnée qui impacte le calcul du tarif.
<br><br>
<strong>Vous devez retourner sur le formulaire Tarif pour recalculer et valider le nouveau tarif.</strong>
</p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-red btn-flat">Annuler</a>
<a href="#!" class="waves-effect waves-green btn" onclick="window.RCSync.navigateToTarif()">
Aller au Tarif
</a>
</div>
`;
return modal;
}
/**
* Navigate vers l'onglet Tarif depuis le Projet.
*
* @example
* navigateToTarif();
*/
function navigateToTarif() {
// Fermer le modal
const modal = document.getElementById('modalRetourTarif');
if (modal && window.M) {
const instance = window.M.Modal.getInstance(modal);
if (instance) instance.close();
}
// Naviguer vers le tarif
const numParcours = new URLSearchParams(window.location.search).get('numParcours');
if (numParcours) {
window.location.href = `/navParcours?numParcours=${numParcours}&submenu=tarif`;
}
}
// ═══════════════════════════════════════════════════════════════════════
// EXPORT PUBLIC
// ═══════════════════════════════════════════════════════════════════════
/**
* API publique du module RC Sync.
* Toutes les fonctions exportées ici sont accessibles via window.RCSync.
*/
window.RCSync = {
// Helpers
toNumber,
getValue,
setValue,
getElementByIdFlexible,
// Comparaison
arraysEqual,
valuesEqual,
// Détection changements
isFieldImpactingTarif,
isChangeImpactingTarif,
// Modal
showReturnToTarifModal,
navigateToTarif,
// Constantes
TARIF_IMPACTING_FIELDS
};
console.log('✅ RC Sync Utils loaded');
})(window);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -8,157 +8,196 @@ const path = require("path");
const moment = require("moment");
const parcoursService = require("../services/parcoursService");
const contratService = require("../services/contratService");
const userService = require("../services/userService");
const globalService = require("../services/globalService");
//const projetformrc = require("../../public/js/projet-form-rc");
//const moduloRC = require("../constantes/json-modulateur-rc"); useless pour le moment ?
require("moment/locale/fr");
moment.locale("fr");
// Fonctions helper pour récupérer les valeurs selon la franchise choisie
function getSelectedTarifReference(tarifRC) {
const franchise = tarifRC.franchiseChoisie;
if (franchise === '250') return tarifRC.primeTotal_250;
if (franchise === '400') return tarifRC.primeTotal_400;
if (franchise === 'mini300') return tarifRC.primeTotal_2000;
return null;
}
function getSelectedPrime(tarifRC, type) {
const franchise = tarifRC.franchiseChoisie;
if (!franchise) return 0;
const suffix = franchise === 'mini300' ? '2000' : franchise;
const fieldName = `prime${type}_${suffix}`;
return tarifRC[fieldName] || 0;
}
function getSelectedTaux(tarifRC, type) {
const franchise = tarifRC.franchiseChoisie;
if (!franchise) return 0;
const suffix = franchise === 'mini300' ? '2000' : franchise;
const fieldName = `taux${type}_${suffix}`;
return tarifRC[fieldName] || 0;
}
router.post("/rc/projet/:numParcours", async (req, res) => {
const content = fs.readFileSync(
path.resolve("src/templates/template-projet-rc.docx"),
"binary"
);
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true });
const numParcours = req.params.numParcours.toUpperCase();
const parcours = await parcoursService.getParcoursByNumParcours(numParcours);
const contrat = await contratService.getContratById(parcours.contrat);
const client = contrat?.["@expand"]?.client || {};
const intermediaire = contrat?.["@expand"]?.intermediaire || {};
const rc = contrat?.["@expand"]?.enCours || {};
const listAssAdd = [];
try {
rc.assureAdditionnel.forEach((objet) => {
listAssAdd.push(
objet.nom +
const content = fs.readFileSync(
path.resolve("src/templates/template-projet-rc.docx"),
"binary"
);
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true });
const numParcours = req.params.numParcours.toUpperCase();
const parcours = await parcoursService.getParcoursByNumParcours(numParcours);
const contrat = await contratService.getContratById(parcours.contrat);
const client = contrat?.["@expand"]?.client || {};
const intermediaire = contrat?.["@expand"]?.intermediaire || {};
// Récupérer la collection rc avec ses relations (APRÈS placerDansEnCours, c'est dans enCours)
const rcFull = contrat?.["@expand"]?.enCours;
if (!rcFull) {
logger.log('error', 'No RC found for contrat:', contrat.id);
return res.status(404).send("Aucune donnée RC trouvée pour ce contrat");
}
// Les données sont déjà expandées par contratService
const rc = rcFull?.["@expand"]?.projetRC || {};
const listAssAdd = [];
// Traiter les assurés additionnels s'ils existent
if (rc.assureAdditionnel && Array.isArray(rc.assureAdditionnel)) {
rc.assureAdditionnel.forEach((objet) => {
listAssAdd.push(
objet.nom +
" - Adresse : " +
objet.adresse +
" - Siret : " +
objet.siret
);
});
} catch (error) { }
// Conditions zone
hasMondeEntier = false;
hasZone1 = false;
hasZone2 = false;
hasZone3 = false;
hasZone4 = false;
hasZone5 = false;
hasZone6 = false;
hasActiviteButNotMultimodal = false; // Attention trick pour les zones géographiques dans le cas de multimodal
hasZone456 = false;
// Cas monde entier
if (
rc.zone1 &&
rc.zone2 &&
rc.zone3 &&
rc.zone4 &&
rc.zone5 &&
rc.zone6 &&
(rc.actVoiturier ||
rc.actLoueur ||
rc.actDouane ||
rc.actDemPar ||
rc.actDemParDom ||
rc.actDemParAdv ||
rc.actDemEntr ||
rc.actDemInterne ||
rc.actGardeMeuble ||
rc.actEntDep ||
rc.actPrestaLog ||
rc.actLevageur)
) {
hasMondeEntier = true;
}
// Cas date du jour d'édition
let dateNow;
dateNow = moment().format("DD MMMM YYYY");
// Cas une ou plusieurs zone(s) spécifique(s) + cas zone456
if (hasMondeEntier == false && (rc.actVoiturier || rc.actLoueur || rc.actDouane || rc.actDemPar || rc.actDemParDom || rc.actDemParAct || rc.actDemEntr || rc.actDemInterne || rc.actGardeMeuble || rc.actEntDep || rc.actPrestaLog || rc.actLevageur || rc.actDemParAdv)) {
if (rc.zone1) {
hasZone1 = true;
);
});
}
if (rc.zone2) {
hasZone2 = true;
// Conditions zone
let hasMondeEntier = false;
let hasZone1 = false;
let hasZone2 = false;
let hasZone3 = false;
let hasZone4 = false;
let hasZone5 = false;
let hasZone6 = false;
let hasActiviteButNotMultimodal = false; // Attention trick pour les zones géographiques dans le cas de multimodal
let hasZone456 = false;
// Cas monde entier
if (
rc.zone1 &&
rc.zone2 &&
rc.zone3 &&
rc.zone4 &&
rc.zone5 &&
rc.zone6 &&
(rc.actVoiturier ||
rc.actLoueur ||
rc.actDouane ||
rc.actDemPar ||
rc.actDemParDom ||
rc.actDemParAdv ||
rc.actDemEntr ||
rc.actDemInterne ||
rc.actGardeMeuble ||
rc.actEntDep ||
rc.actPrestaLog ||
rc.actLevageur)
) {
hasMondeEntier = true;
}
if (rc.zone3) {
hasZone3 = true;
// Cas date du jour d'édition
let dateNow = moment().format("DD MMMM YYYY");
// Cas une ou plusieurs zone(s) spécifique(s) + cas zone456
if (hasMondeEntier == false && (rc.actVoiturier || rc.actLoueur || rc.actDouane || rc.actDemPar || rc.actDemParDom || rc.actDemParAct || rc.actDemEntr || rc.actDemInterne || rc.actGardeMeuble || rc.actEntDep || rc.actPrestaLog || rc.actLevageur || rc.actDemParAdv)) {
if (rc.zone1) {
hasZone1 = true;
}
if (rc.zone2) {
hasZone2 = true;
}
if (rc.zone3) {
hasZone3 = true;
}
if (rc.zone4) {
hasZone4 = true;
hasZone456 = true;
}
if (rc.zone5) {
hasZone5 = true;
hasZone456 = true;
}
if (rc.zone6) {
hasZone6 = true;
hasZone456 = true;
}
}
if (rc.zone4) {
hasZone4 = true;
hasZone456 = true;
// Cas Activité multimodal + Au minimum une zone
if (rc.actMultimodal) {
if (hasZone1 || hasZone2 || hasZone3 || hasZone456 || hasMondeEntier) {
hasActiviteButNotMultimodal = true;
}
}
if (rc.zone5) {
hasZone5 = true;
hasZone456 = true;
let hasMondeEntierOrMultimodal, hasNotMondeEntierOrMultimodal;
if (rc.actMultimodal || hasMondeEntier) {
hasMondeEntierOrMultimodal = true;
hasNotMondeEntierOrMultimodal = false;
} else {
hasMondeEntierOrMultimodal = false;
hasNotMondeEntierOrMultimodal = true;
}
if (rc.zone6) {
hasZone6 = true;
hasZone456 = true;
// Conditions Extensions RCC
let extRCC = false;
if ( rc.extRCCModifCalArrim == true || rc.extRCCFerroutage == true || rc.extRCCFraisRecons == true || rc.extRCCConfie == true || rc.extRCCTPPC == true || rc.extRCCRegie == true || rc.extRCCSansMontageDemontage == true ) {
extRCC = true;
}
}
// Cas Activité multimodal + Au minimum une zone
if (rc.actMultimodal) {
if (hasZone1 || hasZone2 || hasZone3 || hasZone456 || hasMondeEntier) {
hasActiviteButNotMultimodal = true;
// Conditions Extensions RCE
let extRCE = false;
if (rc.extRCEMontageDemontage == true || rc.extRCEBraDebra == true) {
extRCE = true;
}
}
if (rc.actMultimodal || hasMondeEntier) {
hasMondeEntierOrMultimodal = true;
hasNotMondeEntierOrMultimodal = false;
} else {
hasMondeEntierOrMultimodal = false;
hasNotMondeEntierOrMultimodal = true;
}
// Conditions Activitées
let hasActiviteDemenageurGardeMeuble = false;
if ( rc.actDemPar == true || rc.actDemEntr == true || rc.actDemParDom == true || rc.actDemParAdv == true || rc.actDemInterne) {
hasActiviteDemenageurGardeMeuble = true;
}
// Conditions Extensions RCC
let extRCC = false;
let oneOfActiviteDemenageurParticulier = false;
if ( rc.actDemPar == true || rc.actDemParDom == true || rc.actDemParAdv == true ) {
oneOfActiviteDemenageurParticulier = true;
}
if ( rc.extRCCModifCalArrim == true || rc.extRCCFerroutage == true || rc.extRCCFraisRecons == true || rc.extRCCConfie == true || rc.extRCCTPPC == true || rc.extRCCRegie == true || rc.extRCCSansMontageDemontage == true ) {
extRCC = true;
}
let coorDem = "";
if ( (rc.actDemPar == true || rc.actDemParDom == true || rc.actDemParAdv == true) && (rc.actDemEntr == true || rc.actDemInterne == true) && rc.actGardeMeuble == true) {
coorDem = ",";
} else if ( (rc.actDemPar == true || rc.actDemParDom == true || rc.actDemParAdv == true) && (rc.actDemEntr == true || rc.actDemInterne == true) && rc.actGardeMeuble == false ) {
coorDem = " et";
}
// Conditions Extensions RCC
let extRCE = false;
// Variables numériques
const franchiseTarif = "250";
if ( rc.extRCEMontageDemontage == true || rc.extRCEBraDebra == true) {
extRCE = true;
}
// Conditions Activitées
let hasActiviteDemenageurGardeMeuble = false;
if ( rc.actDemPar == true || rc.actDemEntr == true || rc.actDemParDom == true || rc.actDemParAdv == true || rc.actDemInterne) {
hasActiviteDemenageurGardeMeuble = true;
}
let oneOfActiviteDemenageurParticulier = false;
if ( rc.actDemPar == true || rc.actDemParDom == true || rc.actDemParAdv == true ) {
oneOfActiviteDemenageurParticulier = true;
}
let coorDem = "";
if ( (rc.actDemPar == true || rc.actDemParDom == true || rc.actDemParAdv == true) && (rc.actDemEntr == true || rc.actDemInterne == true) && rc.actGardeMeuble == true) {
coorDem = ",";
} else if ( (rc.actDemPar == true || rc.actDemParDom == true || rc.actDemParAdv == true) && (rc.actDemEntr == true || rc.actDemInterne == true) && rc.actGardeMeuble == false ) {
coorDem = " et";
}
// Variables numériques
const franchiseTarif = "250";
try {
doc.render({
// Client
nomClient: client.nom,
@ -303,51 +342,310 @@ router.post("/rc/projet/:numParcours", async (req, res) => {
fraisRepFraction: globalService.customFormatNumber(rc.cotFraisTTC, true),
ca: globalService.customFormatNumber(rc.ca, true),
});
const buf = doc.getZip().generate({ type: "nodebuffer" });
const currentDate = new Date();
// Formatage de la date au format "JJ-MM-AAAA-HH-MM-SS"
const day = String(currentDate.getDate()).padStart(2, "0");
const month = String(currentDate.getMonth() + 1).padStart(2, "0");
const year = currentDate.getFullYear();
const hours = String(currentDate.getHours()).padStart(2, "0");
const minutes = String(currentDate.getMinutes()).padStart(2, "0");
const seconds = String(currentDate.getSeconds()).padStart(2, "0");
const formattedDate = `${day}-${month}-${year}-${hours}-${minutes}-${seconds}`;
// Génération du nom de fichier
const sanitizedClientNom = client.nom
.replace(/[^\w\s.-]/gi, "")
.replace(/\s+/g, "-");
const filename = `Projet-${contrat.produit}-${parcours.numParcours}-${sanitizedClientNom}-${formattedDate}`;
// Définit le type de contenu et un nom de fichier par défaut pour le téléchargement
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
);
res.setHeader(
"Content-Disposition",
"attachment; filename=" + filename + ".docx"
);
// Envoie le buffer au client, déclenchant le téléchargement
res.send(buf);
} catch (error) {
const e = {
message: error.message,
name: error.name,
stack: error.stack,
properties: error.properties,
};
logger.log('error', JSON.stringify({ error: e }));
// Envoyez une réponse d'erreur si le rendu échoue
return res.status(500).send("Erreur lors de la génération du document");
logger.log('error', 'Error in RC projet generation:', error);
return res.status(500).send("Erreur lors de la génération du projet RC");
}
const buf = doc.getZip().generate({ type: "nodebuffer" });
const currentDate = new Date();
// Formatage de la date au format "JJ-MM-AAAA-HH-MM-SS"
const day = String(currentDate.getDate()).padStart(2, "0");
const month = String(currentDate.getMonth() + 1).padStart(2, "0");
const year = currentDate.getFullYear();
const hours = String(currentDate.getHours()).padStart(2, "0");
const minutes = String(currentDate.getMinutes()).padStart(2, "0");
const seconds = String(currentDate.getSeconds()).padStart(2, "0");
const formattedDate = `${day}-${month}-${year}-${hours}-${minutes}-${seconds}`;
// Génération du nom de fichier
const sanitizedClientNom = client.nom
.replace(/[^\w\s.-]/gi, "")
.replace(/\s+/g, "-");
const filename = `Projet-${contrat.produit}-${parcours.numParcours}-${sanitizedClientNom}-${formattedDate}`;
// Définit le type de contenu et un nom de fichier par défaut pour le téléchargement
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
);
res.setHeader(
"Content-Disposition",
"attachment; filename=" + filename + ".docx"
);
// Envoie le buffer au client, déclenchant le téléchargement
res.send(buf);
});
router.post("/rc/tarif/:numParcours", async (req, res) => {
try {
const content = fs.readFileSync(
path.resolve("src/templates/template-declinaison-tarifaire-rc.docx"),
"binary"
);
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true });
const numParcours = req.params.numParcours.toUpperCase();
const parcours = await parcoursService.getParcoursByNumParcours(numParcours);
const contrat = await contratService.getContratById(parcours.contrat);
const client = contrat?.["@expand"]?.client || {};
const user = await userService.getUserById(parcours.dernierUtilisateur);
// Récupérer la collection rc avec ses relations (APRÈS placerDansEnCours, c'est dans enCours)
const rcFull = contrat?.["@expand"]?.enCours;
if (!rcFull) {
logger.log('error', 'No RC found for contrat:', contrat.id);
return res.status(404).send("Aucune donnée RC trouvée pour ce contrat");
}
// Les données sont déjà expandées par contratService
const rc = rcFull?.["@expand"]?.tarifRC || {};
const rcMain = rcFull || {};
// Formatage des taux avec maximum 2 décimales
function formatTaux(taux) {
if (!taux) return 0;
return parseFloat(taux).toFixed(2);
}
// Préparer les données des activités avec leurs marchandises et activités complémentaires
const activites = [];
// Voiturier/Loueur
if (rcMain.checkVoiturier || rcMain.checkLoueur) {
activites.push({
type: 'Voiturier/Loueur',
capital: rcMain.capitalVoiturier || 0,
pourcentage: rc.pourcentageVoiturier || 0,
marchandises: rcMain.marchandisesVoiturier || [],
activitesCompl: rcMain.activitesVoiturier || []
});
}
// Commissionnaire de Transport
if (rcMain.checkCommissionnaire) {
activites.push({
type: 'Commissionnaire de Transport',
capital: rcMain.capitalCommissionnaire || 0,
pourcentage: rc.pourcentageCommissionnaire || 0,
marchandises: rcMain.marchandisesCommissionnaire || [],
activitesCompl: rcMain.activitesCommissionnaire || []
});
}
// Déménageur
if (rcMain.checkDemenageur) {
activites.push({
type: 'Déménageur',
capital: rcMain.capitalDemenageur || 0,
pourcentage: rc.pourcentageDemenageur || 0,
marchandises: rcMain.marchandisesDemenageur || [],
activitesCompl: rcMain.activitesDemenageur || []
});
}
// Logistique
if (rcMain.checkLogistique) {
activites.push({
type: 'Logistique',
capital: rcMain.capitalLogistique || 0,
pourcentage: rc.pourcentageLogistique || 0,
marchandises: rcMain.marchandisesLogistique || [],
activitesCompl: rcMain.activitesLogistique || []
});
}
// Autocariste
if (rcMain.checkAutocariste) {
activites.push({
type: 'Autocariste',
capital: rcMain.capitalAutocariste || 0,
pourcentage: rc.pourcentageAutocariste || 0,
marchandises: rcMain.marchandisesAutocariste || [],
activitesCompl: rcMain.activitesAutocariste || []
});
}
// Autres activités
if (rcMain.checkAutres) {
activites.push({
type: 'Autres activités',
capital: rcMain.capitalAutres || 0,
pourcentage: rc.pourcentageAutres || 0,
marchandises: rcMain.marchandisesAutres || [],
activitesCompl: rcMain.activitesAutres || []
});
}
// Préparer les zones
const zones = [];
if (rcMain.zone1) zones.push("Zone 1");
if (rcMain.zone2) zones.push("Zone 2");
if (rcMain.zone3) zones.push("Zone 3");
if (rcMain.zone4) zones.push("Zone 4");
if (rcMain.zone5) zones.push("Zone 5");
if (rcMain.zone6) zones.push("Zone 6");
// Préparer les garanties additionnelles
const garantiesAdditionnelles = [];
if (rc.checkStationLavage) garantiesAdditionnelles.push("Station de lavage");
if (rc.checkGarageInterne) garantiesAdditionnelles.push("Garage interne");
if (rc.checkCSE) garantiesAdditionnelles.push("CSE");
if (rc.checkTPPC) garantiesAdditionnelles.push(`TPPC (Capital: ${rc.capitalTPPC || 0}€, Véhicules: ${rc.vehiculesTPPC || 0})`);
if (rc.checkPJ) garantiesAdditionnelles.push("Protection Juridique");
// Préparer les engagements complémentaires
const engagements = [];
if (rc.checkDomImmat) engagements.push(`Dommages immatériels (Capital: ${rc.capitalDomImmat || 0}€)`);
if (rc.checkContConf) engagements.push(`Contenu confié (Capital: ${rc.capitalContConf || 0}€)`);
if (rc.checkDiffInv) engagements.push(`Différence d'inventaire (Capital: ${rc.capitalDiffInv || 0}€)`);
let dateNow = moment().format("DD MMMM YYYY");
doc.render({
// Infos générales
matricule: user.matricule,
date: dateNow,
typeCotisation: rcMain.typeCotisation || "Non défini",
hasforfaitaire: !(rcMain.typeCotisation === "forfaitaire" || (!rcMain.chiffreAffaires || rcMain.chiffreAffaires === 0)),
hasRCE: rcMain.checkRCE || false,
hasContrat: !!contrat.numContrat,
numContrat: contrat.numContrat,
hasSaisine: !!contrat.numSaisine,
numSaisine: contrat.numSaisine,
numeroCS: contrat.numSaisine || contrat.numContrat,
nomAssure: client.nom,
ca: rcMain.chiffreAffaires || 0,
hasNbVehicule: (rcMain.nombreVehicules || 0) > 0,
nbVehicule: rcMain.nombreVehicules || 0,
// Activités principales
hasVoiturierLoueur: rcMain.checkVoiturier || rcMain.checkLoueur,
capitalAssureVoiturierLoueur: rcMain.capitalVoiturier || 0,
pourcentageVoiturierLoueur: rc.pourcentageVoiturier || 0,
marchandiseVoiturierLoueur: rcMain.marchandisesVoiturier || [],
activiteVoiturierLoueur: rcMain.actComplVoiturier || [],
hasComTransport: rcMain.checkCommissionnaire || false,
capitalAssureComTransport: rcMain.capitalCommissionnaire || 0,
pourcentageComTransport: rc.pourcentageCommissionnaire || 0,
marchandiseComTransport: rcMain.marchandisesCommissionnaire || [],
activiteComTransport: rcMain.actComplCommissionnaire || [],
hasDemenageur: rcMain.checkDemenageur || false,
capitalAssureDemenageur: rcMain.capitalDemenageur || 0,
pourcentageDemenageur: rc.pourcentageDemenageur || 0,
marchandiseDemenageur: rcMain.marchandisesDemenageur || [],
activiteDemenageur: rcMain.actComplDemenageur || [],
hasLogistique: rcMain.checkLogistique || false,
capitalAssureLogistique: rcMain.capitalLogistique || 0,
pourcentageLogistique: rc.pourcentageLogistique || 0,
marchandiseLogistique: rcMain.marchandisesLogistique || [],
activiteLogistique: rcMain.actComplLogistique || [],
hasAutocariste: rcMain.checkAutocariste || false,
capitalAssureAutocariste: rcMain.capitalAutocariste || 0,
pourcentageAutocariste: rc.pourcentageAutocariste || 0,
marchandiseAutocariste: rcMain.marchandisesAutocariste || [],
activiteAutocariste: rcMain.activitesAutocariste || [],
hasAutre: rcMain.checkAutres || false,
capitalAssureAutre: rcMain.capitalAutres || 0,
pourcentageAutre: rc.pourcentageAutres || 0,
marchandiseAutre: rcMain.marchandisesAutres || [],
//activiteAutre: rcMain.activitesAutres || [], existe pas dans ejs
// Zones géographiques
zones: zones,
// Garanties additionnelles
hasGarantieAdd: garantiesAdditionnelles.length > 0,
garantieAdd: garantiesAdditionnelles,
// Engagements complémentaires
hasEngagement: engagements.length > 0,
engagement: engagements,
// Tarifs sélectionnés
franchise: rc.franchiseChoisie || rc.franchiseSelectionnee,
tarifReference: getSelectedTarifReference(rc),
tarifCommercial: rc.tarifcommercial,
primeRCC: getSelectedPrime(rc, 'RCC'),
tauxRCC: formatTaux(getSelectedTaux(rc, 'RCC')),
hasRCE: rcMain.checkRCE,
primeRCE: getSelectedPrime(rc, 'RCE'),
tauxRCE: formatTaux(getSelectedTaux(rc, 'RCE')),
hasPrimePJ: rc.checkPJ && getSelectedPrime(rc, 'PJ') > 0,
primePJ: getSelectedPrime(rc, 'PJ'),
tauxGlobal: formatTaux(getSelectedTaux(rc, 'Global')),
// Propositions franchise 250
tarifRefP1: rc.primeTotal_250 || 0,
pRCCP1: rc.primeRCC_250 || 0,
tRCCP1: formatTaux(rc.tauxRCC_250),
pRCEP1: rc.primeRCE_250 || 0,
tRCEP1: formatTaux(rc.tauxRCE_250),
primePJP1: rc.primePJ_250 || 0,
tauxGlobalP1: formatTaux(rc.tauxGlobal_250),
// Propositions franchise 400
tarifRefP2: rc.primeTotal_400 || 0,
pRCCP2: rc.primeRCC_400 || 0,
tRCCP2: formatTaux(rc.tauxRCC_400),
pRCEP2: rc.primeRCE_400 || 0,
tRCEP2: formatTaux(rc.tauxRCE_400),
primePJP2: rc.primePJ_400 || 0,
tauxGlobalP2: formatTaux(rc.tauxGlobal_400),
// Propositions franchise 2000
tarifRefP3: rc.primeTotal_2000 || 0,
pRCCP3: rc.primeRCC_2000 || 0,
tRCCP3: formatTaux(rc.tauxRCC_2000),
pRCEP3: rc.primeRCE_2000 || 0,
tRCEP3: formatTaux(rc.tauxRCE_2000),
primePJP3: rc.primePJ_2000 || 0,
tauxGlobalP3: formatTaux(rc.tauxGlobal_2000),
});
const buf = doc.getZip().generate({ type: "nodebuffer" });
const currentDate = new Date();
// Formatage de la date au format "JJ-MM-AAAA-HH-MM-SS"
const day = String(currentDate.getDate()).padStart(2, "0");
const month = String(currentDate.getMonth() + 1).padStart(2, "0");
const year = currentDate.getFullYear();
const hours = String(currentDate.getHours()).padStart(2, "0");
const minutes = String(currentDate.getMinutes()).padStart(2, "0");
const seconds = String(currentDate.getSeconds()).padStart(2, "0");
const formattedDate = `${day}-${month}-${year}-${hours}-${minutes}-${seconds}`;
const sanitizedClientNom = client.nom
.replace(/[^\w\s.-]/gi, "")
.replace(/\s+/g, "-");
const filename = `Tarif-RC-${parcours.numParcours}-${sanitizedClientNom}-${formattedDate}`;
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
);
res.setHeader("Content-Disposition", "attachment; filename=" + filename + ".docx");
res.send(buf);
} catch (error) {
logger.log("error", 'Error in RC tarif generation:', error);
return res.status(500).send("Erreur lors de la génération du tarif RC");
}
});
module.exports = router;

View File

@ -22,7 +22,7 @@ router.get('/contrat', async (req, res) => {
});
router.get('/tarifrc', async (req, res) => {
renderPage('dev.ejs', res, {}, true);
renderPage('tarifformrc.ejs', res, {}, true);
});
router.get('/tariffac', async (req, res) => {

View File

@ -1,12 +1,152 @@
const express = require('express');
const router = express.Router();
const rcService = require('../services/rcService');
const constantesJSON = require("../constantes/json-modulateur-rc");
const logger = require('../utils/logger');
// ===== Routes RC principale =====
router.post('/create', async (req, res) => {
const data = req.body;
const rc = await rcService.createRc(data);
try {
const data = req.body;
const rc = await rcService.createRc(data);
res.json({ valid: Boolean(rc), rc });
} catch (error) {
logger.log("error", "Error creating RC:", error);
res.status(500).json({ valid: false, error: "Internal Server Error" });
}
});
res.json({ valid: Boolean(rc), rc });
router.get('/:id', async (req, res) => {
try {
const rc = await rcService.getRcById(req.params.id);
res.json({ valid: Boolean(rc), rc });
} catch (error) {
logger.log("error", "Error getting RC:", error);
res.status(500).json({ valid: false, error: "Internal Server Error" });
}
});
router.post('/update/:id', async (req, res) => {
try {
const rc = await rcService.updateRc(req.params.id, req.body);
res.json({ valid: Boolean(rc), rc });
} catch (error) {
logger.log("error", "Error updating RC:", error);
res.status(500).json({ valid: false, error: "Internal Server Error" });
}
});
// ===== Routes TarifRC =====
router.post('/tarif/create', async (req, res) => {
try {
const data = req.body;
const tarifRc = await rcService.createTarifRc(data);
res.json({ valid: Boolean(tarifRc), tarifRc });
} catch (error) {
logger.log("error", "Error creating TarifRC:", error);
res.status(500).json({ valid: false, error: "Internal Server Error" });
}
});
router.get('/tarif/:id', async (req, res) => {
try {
const tarifRc = await rcService.getTarifRcById(req.params.id);
res.json({ valid: Boolean(tarifRc), tarifRc });
} catch (error) {
logger.log("error", "Error getting TarifRC:", error);
res.status(500).json({ valid: false, error: "Internal Server Error" });
}
});
router.post('/tarif/update/:id', async (req, res) => {
try {
const tarifRc = await rcService.updateTarifRc(req.params.id, req.body);
res.json({ valid: Boolean(tarifRc), tarifRc });
} catch (error) {
logger.log("error", "Error updating TarifRC:", error);
res.status(500).json({ valid: false, error: "Internal Server Error" });
}
});
// ===== Routes ProjetRC =====
router.post('/projet/create', async (req, res) => {
try {
const data = req.body;
const projetRc = await rcService.createProjetRc(data);
res.json({ valid: Boolean(projetRc), projetRc });
} catch (error) {
logger.log("error", "Error creating ProjetRC:", error);
res.status(500).json({ valid: false, error: "Internal Server Error" });
}
});
router.get('/projet/:id', async (req, res) => {
try {
const projetRc = await rcService.getProjetRcById(req.params.id);
res.json({ valid: Boolean(projetRc), projetRc });
} catch (error) {
logger.log("error", "Error getting ProjetRC:", error);
res.status(500).json({ valid: false, error: "Internal Server Error" });
}
});
router.post('/projet/update/:id', async (req, res) => {
try {
const projetRc = await rcService.updateProjetRc(req.params.id, req.body);
res.json({ valid: Boolean(projetRc), projetRc });
} catch (error) {
logger.log("error", "Error updating ProjetRC:", error);
res.status(500).json({ valid: false, error: "Internal Server Error" });
}
});
// ===== Routes Modulateurs =====
router.get("/modulo/:objDemande", async (req, res) => {
const objDemande = req.params.objDemande;
var objRetourne
switch (objDemande) {
case "CARC":
objRetourne = constantesJSON.modRCCA;
break;
case "activiteRCC":
objRetourne = constantesJSON.modRCActRCC;
break;
case "activiteRCE":
objRetourne = constantesJSON.modRCActRCE;
break;
case "activiteComplRC":
objRetourne = constantesJSON.modRCActCompl;
break;
case "marchandiseRC":
objRetourne = constantesJSON.modRCMar;
break;
case "zoneRC":
objRetourne = constantesJSON.modRCZone;
break;
case "engagComplRC":
objRetourne = constantesJSON.modRCEngagCompl;
break;
case "garAdditionelRC":
objRetourne = constantesJSON.modRCGarAdd ;
break;
case "sinistreRC":
objRetourne = constantesJSON.modRCSinistre;
break;
case "franchiseRC":
objRetourne = constantesJSON.modRCFranchise;
break;
case "primeMiniRC":
objRetourne = constantesJSON.modRCPrimeMini;
break;
}
try {
res.json({valid: Boolean(objRetourne), objRetourne});
} catch (error) {
logger.log("error", `Error finding constant ${objDemande}:`, error);
res.status(500).json({valid: false, error: "Internal Server Error"});
}
});
module.exports = router;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -45,12 +45,31 @@ async function createContrat() {
async function expandProjetTarif(resultat) {
const produit = resultat?.['produit'].toLowerCase()
const idProjet = resultat?.['@expand']?.[produit]?.['projet'] || undefined
const idTarif = resultat?.['@expand']?.[produit]?.['tarif'] || undefined
// RC utilise tarifRC/projetRC, les autres produits utilisent tarif/projet
let idProjet, idTarif, collectionProjet, collectionTarif;
if (produit === 'rc') {
idProjet = resultat?.['@expand']?.[produit]?.['projetRC'] || undefined
idTarif = resultat?.['@expand']?.[produit]?.['tarifRC'] || undefined
collectionProjet = 'projetRC'; // Nom de collection RC
collectionTarif = 'tarifRC'; // Nom de collection RC
} else {
idProjet = resultat?.['@expand']?.[produit]?.['projet'] || undefined
idTarif = resultat?.['@expand']?.[produit]?.['tarif'] || undefined
collectionProjet = produit + "projet"; // tppcprojet, facprojet
collectionTarif = produit + "tarif"; // tppctarif, factarif (n'existe pas mais bon)
}
// Pour RC, inclure à la fois les clés RC spécifiques (tarifRC/projetRC) et les clés génériques (tarif/projet)
// afin de rester compatible avec l'ancien front qui lisait `tarif`/`projet`.
const expand = {
projet: (idProjet) ? await db.records.getOne(produit + "projet", idProjet) : null,
tarif: (idTarif) ? await db.records.getOne(produit + "tarif", idTarif) : null
projet: idProjet ? await db.records.getOne(collectionProjet, idProjet) : null,
tarif: idTarif ? await db.records.getOne(collectionTarif, idTarif) : null,
};
if (produit === 'rc') {
expand.projetRC = expand.projet;
expand.tarifRC = expand.tarif;
}
resultat['@expand'][produit] = {...resultat['@expand'][produit], '@expand': expand}

View File

@ -1,10 +1,55 @@
const { db } = require('../db/db-connect');
const logger = require('../utils/logger');
// ===== Collection RC principale =====
async function createRc(data) {
return await db.records.create('rc', data);
}
async function getRcById(id) {
return await db.records.getOne('rc', id, {
expand: 'tarifRC,projetRC'
});
}
async function updateRc(id, data) {
return await db.records.update('rc', id, data);
}
// ===== Collection TarifRC =====
async function createTarifRc(data) {
return await db.records.create('tarifRC', data);
}
async function getTarifRcById(id) {
return await db.records.getOne('tarifRC', id);
}
async function updateTarifRc(id, data) {
return await db.records.update('tarifRC', id, data);
}
// ===== Collection ProjetRC =====
async function createProjetRc(data) {
return await db.records.create('projetRC', data);
}
async function getProjetRcById(id) {
return await db.records.getOne('projetRC', id);
}
async function updateProjetRc(id, data) {
return await db.records.update('projetRC', id, data);
}
module.exports = {
createRc
createRc,
getRcById,
updateRc,
createTarifRc,
getTarifRcById,
updateTarifRc,
createProjetRc,
getProjetRcById,
updateProjetRc
};

View File

@ -63,6 +63,12 @@
<script src="/js/loader.js"></script>
<!-- Script pour la navigation AJAX -->
<script src="/js/navigation.js"></script>
<!-- Utilitaires de synchronisation RC -->
<script src="/js/rc-sync-utils.js"></script>
<!-- Gestionnaire de données RC -->
<script src="/js/rc-data-manager.js"></script>
<!-- Orchestrateur de synchronisation RC -->
<script src="/js/rc-orchestrator.js"></script>
</main>
</body>

View File

@ -931,7 +931,9 @@
<input type="text" name="cotRCEHT" id="cotRCEHT" placeholder="00.00" />
<span id="cotRCEHT-error" class="helper-text red-text"></span>
</td>
<td><input type="text" name="cotRCETaux" id="cotRCETaux" value="9" disabled /></td>
<td>
<input type="text" name="cotRCETaux" id="cotRCETaux" value="9" disabled />
</td>
<td>
<input type="text" name="cotRCETTC" id="cotRCETTC" placeholder="00.00" />
<span id="cotRCETTC-error" class="helper-text red-text"></span>
@ -943,7 +945,9 @@
<input type="text" name="cotPJHT" id="cotPJHT" placeholder="100.00" />
<span id="cotPJHT-error" class="helper-text red-text"></span>
</td>
<td><input type="text" name="cotPJTaux" id="cotPJTaux" value="13.4" disabled /></td>
<td>
<input type="text" name="cotPJTaux" id="cotPJTaux" value="13.4" disabled />
</td>
<td>
<input type="text" name="cotPJTTC" id="cotPJTTC" placeholder="113.40" />
<span id="cotPJTTC-error" class="helper-text red-text"></span>
@ -1181,3 +1185,26 @@
<a class="modal-close waves-effect waves-green btn-flat">Fermer</a>
</div>
</div>
<!-- MODAL modification & liaison tarif -->
<div id="modalModif" class="modal modalAlert">
<div class="modal-content">
<h4>Attention</h4>
<p>
Si vous modifiez cette information, la prime calculée à l'étape "Tarif" sera incorrecte, ce qui entraînera
la suppression du tarif de référence associé à ce contrat.
</p>
<p>Cela s'applique aux informations suivantes :</p>
<ul class="modalRed">
<li>Activité assurée</li>
<li>Garanties</li>
<li>Extensions de Garanties impactant le tarif</li>
<li>Type de Cotisation (avec un type de contrat en cotisation forfaitaire) </li>
</ul>
</div>
<div class="modal-footer">
<a id="modif-OK" class="modal-close waves-effect btn-flat red darken-1">Modifier dans Tarif</a>
<a id="modif-NO" class="modal-close waves-effect btn-flat indigo darken-4">Annuler</a>
</div>
</div>

1252
ecole/views/tarifformrc.ejs Normal file

File diff suppressed because it is too large Load Diff