Compare commits
No commits in common. "trc" and "main" have entirely different histories.
|
|
@ -1,6 +0,0 @@
|
|||
#DB_URL=http://ppsi_nanterre.axa-fr.intraxa:3005/
|
||||
DB_URL=http://127.0.0.1:8091/
|
||||
DB_ADMIN=admin@axa.fr
|
||||
DB_PASSWORD=DTadmin123TT
|
||||
NODE_ENV=developpement
|
||||
PORT=8082
|
||||
|
|
@ -1,368 +0,0 @@
|
|||
/* Historique Parcours */
|
||||
body>main>div>div.section.center-align {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
body>main>div>div.section.center-align>div.container {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#historiqueParcours_wrapper {
|
||||
width: 100% !important;
|
||||
overflow-x: auto !important;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
table#historiqueParcours {
|
||||
width: 150%;
|
||||
border-collapse: collapse !important;
|
||||
border: 2px solid darkblue !important;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#historiqueParcours th,
|
||||
#historiqueParcours td {
|
||||
color: black;
|
||||
text-align: left;
|
||||
font-size: 11px;
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
table.dataTable thead th {
|
||||
padding-right: 18px !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
/* Div pour encapsuler le texte dans les cellules d'en-tête */
|
||||
table.dataTable thead th>div {
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
#historiqueParcours_filter {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_filter {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
float: left;
|
||||
padding: 2px;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Cacher complètement le label "Rechercher" de DataTables */
|
||||
.dataTables_wrapper .dataTables_filter label {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
font-size: 0 !important;
|
||||
line-height: 0 !important;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_filter label > span:first-child {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* fond blanc, bordure grise fine, capsule arrondie */
|
||||
.dataTables_wrapper .dataTables_filter input[type="search"] {
|
||||
background-color: #fff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 42px;
|
||||
outline: none;
|
||||
height: 42px;
|
||||
width: 220px;
|
||||
font-size: 15px;
|
||||
margin: 0;
|
||||
padding: 0 20px 0 52px;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s ease;
|
||||
color: #333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_filter input[type="search"]:focus {
|
||||
background-color: #fff;
|
||||
border-color: #1d9bf0;
|
||||
box-shadow: 0 0 0 2px #1d9bf0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_filter input[type="search"]::placeholder {
|
||||
color: #71767a;
|
||||
}
|
||||
|
||||
/* Icône de loupe SVG */
|
||||
.dataTables_wrapper .dataTables_filter label::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2371767a' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.35-4.35'/%3E%3C/svg%3E");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Texte "Rechercher" à côté de l'icône */
|
||||
.dataTables_wrapper .dataTables_filter label::after {
|
||||
content: "Rechercher";
|
||||
position: absolute;
|
||||
left: 52px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 15px;
|
||||
color: #71767a;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Cacher le texte "Rechercher" quand on tape, focus, ou si l'input a une valeur */
|
||||
.dataTables_wrapper .dataTables_filter:focus-within label::after,
|
||||
.dataTables_wrapper .dataTables_filter label.has-value::after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_filter input[type="search"]:focus::placeholder {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
#historiqueParcours_length>label {
|
||||
font-size: 14px;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.dataTables_length select[name="historiqueParcours_length"] {
|
||||
display: block !important;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
padding: 8px;
|
||||
margin: 0 0.5em;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 5px;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
/* Style Input recherche par ligne */
|
||||
#historiqueParcours>thead>tr:nth-child(2)>th>input {
|
||||
font-size: 13px !important;
|
||||
padding: 6px !important;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* icone de tri sur les colonnes */
|
||||
table.dataTable thead .sorting,
|
||||
table.dataTable thead .sorting_asc,
|
||||
table.dataTable thead .sorting_desc {
|
||||
background-position: right center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* Ajouter un espacement entre le texte et l'icône */
|
||||
table.dataTable thead .sorting:before,
|
||||
table.dataTable thead .sorting_asc:before,
|
||||
table.dataTable thead .sorting_desc:before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* boutons de navigation */
|
||||
.dataTables_wrapper .dataTables_paginate .paginate_button {
|
||||
background-color: white !important;
|
||||
border: darkblue solid 1.5px !important;
|
||||
color: black !important;
|
||||
padding: 6px 12px !important;
|
||||
margin: 0 2px !important;
|
||||
cursor: pointer !important;
|
||||
border-radius: 4px !important;
|
||||
transition: background-color 0.3s, color 0.3s, border-color 0.3s !important;
|
||||
}
|
||||
|
||||
#historiqueParcours_paginate>span>a.paginate_button.current,
|
||||
.dataTables_wrapper .dataTables_paginate:hover .paginate_button:hover {
|
||||
background: none !important;
|
||||
background-color: darkblue !important;
|
||||
border-color: white !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* NC value */
|
||||
td.nc-value {
|
||||
color: lightgray !important;
|
||||
}
|
||||
|
||||
/* Les bouton pour le filtres et les extraction */
|
||||
#divBtnFilter {
|
||||
display: inline-grid;
|
||||
width: 100%;
|
||||
grid-template-columns: auto;
|
||||
column-gap: 3%;
|
||||
row-gap: 10%;
|
||||
}
|
||||
|
||||
#checkRegionAdmin {
|
||||
grid-column: 1 / span 3;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
#divToggleSearch {
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
justify-self: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#toggleSearch {
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
min-width: fit-content;
|
||||
padding: 0 24px;
|
||||
color: white !important;
|
||||
background-color: darkblue !important;
|
||||
border: none !important;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(16, 0, 75, 0.2), 0 4px 8px rgba(16, 0, 75, 0.1);
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
#toggleSearch:hover {
|
||||
background-color: #26a69a !important;
|
||||
}
|
||||
|
||||
#toggleSearch:active {
|
||||
background-color: gray !important;
|
||||
}
|
||||
|
||||
#divExtractAll {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
#divExtractFilter {
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
#divBtnFilter button {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(16, 0, 75, 0.2),
|
||||
0 4px 8px rgba(16, 0, 75, 0.1);
|
||||
}
|
||||
|
||||
/* Bouton Reprendre */
|
||||
#btnReprendre,
|
||||
#btnGenerate {
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 0px 15px;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#btnReprendre i {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* checkbox Filter Region Admin a supprimer probablement*/
|
||||
#checkRegionAdmin {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
[class^="checkbox-wrapper-"] {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#checkRegionAdmin input[type="checkbox"] {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#checkRegionAdmin label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#historiqueParcours tr.shown > td { background: #fffdf5; }
|
||||
.parcours-details { font-size: 0.95rem; }
|
||||
|
||||
|
||||
/* Style pour les boutons d'export désactivés */
|
||||
#divBtnFilter button:disabled {
|
||||
opacity: 0.5 !important;
|
||||
cursor: not-allowed !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
#historiqueParcours td .col-with-text {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
|
||||
#historiqueParcours_wrapper {
|
||||
overflow-x: visible !important;
|
||||
}
|
||||
|
||||
table.dataTable {
|
||||
width: 100% !important;
|
||||
table-layout: auto !important;
|
||||
}
|
||||
|
||||
.col-with-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.col-with-text .np {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
|
||||
.btn-row-details {
|
||||
cursor: pointer;
|
||||
width: 35px;
|
||||
height: 28px;
|
||||
background-color: #F44336 !important;
|
||||
border-radius: 6px;
|
||||
border: 3px solid darkblue;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
transition: 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
/* petit effet hover propre pour le bouton détails*/
|
||||
.btn-row-details:hover {
|
||||
background-color: #d7372f;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
|
@ -1,279 +0,0 @@
|
|||
// Module IIFE pour éviter la pollution de l'espace global
|
||||
(function () {
|
||||
// Variables globales du module
|
||||
let parcours, contrat, client, matricule;
|
||||
|
||||
// Initialisation du formulaire et des données
|
||||
function init() {
|
||||
const token = localStorage.getItem('jwtToken');
|
||||
|
||||
if (token) {
|
||||
const decoded = jwt_decode(token);
|
||||
matricule = decoded.userMatricule;
|
||||
}
|
||||
|
||||
// Accéder aux informations stockées du parcours
|
||||
parcours = JSON.parse(sessionStorage.getItem('parcours'));
|
||||
contrat = JSON.parse(sessionStorage.getItem('contrat'));
|
||||
client = contrat?.["@expand"]?.client || null;
|
||||
|
||||
console.log("Matricule user actuel:", matricule);
|
||||
console.log("Initialisation pour formulaire client :", contrat);
|
||||
|
||||
// Materialize init Modal
|
||||
var modals = document.querySelectorAll('.modal');
|
||||
M.Modal.init(modals);
|
||||
|
||||
// Appel des différentes fonctions d'initialisation
|
||||
setupEventListeners();
|
||||
populateFormData();
|
||||
updateSubmitButtonState('clientForm');
|
||||
}
|
||||
|
||||
// Configuration des écouteurs d'événements
|
||||
function setupEventListeners() {
|
||||
document.getElementById('clientFormBtn').addEventListener('click', handleSubmitForm);
|
||||
document.getElementById('creditsafe').addEventListener('click', (event) => handleOpenLink(event, 'creditsafeURL'));
|
||||
document.getElementById('easyQP').addEventListener('click', (event) => handleOpenLink(event, 'easyQPURL'));
|
||||
document.getElementById('refNoteFi').addEventListener('click', (event) => handleOpenLink(event, 'refNoteFiURL'));
|
||||
document.getElementById('cl063-client').addEventListener('click', (event) => handleExtractClient(event));
|
||||
document.getElementById('modalExtraireClient').addEventListener('click', (event) => handleModalExtract(event));
|
||||
|
||||
// Controle de saisie et format sur les champs du formulaire
|
||||
document.getElementById('noteFinanciereClient').addEventListener('input', function () {
|
||||
validateField('noteFinanciereClient', true);
|
||||
updateSubmitButtonState('clientForm');
|
||||
});
|
||||
|
||||
document.getElementById('numClient').addEventListener('input', function () {
|
||||
validateField('numClient', true);
|
||||
updateSubmitButtonState('clientForm');
|
||||
});
|
||||
|
||||
document.getElementById('nomClient').addEventListener('input', function () {
|
||||
validateField('nomClient', true);
|
||||
updateSubmitButtonState('clientForm');
|
||||
});
|
||||
|
||||
document.getElementById('emailClient').addEventListener('input', function () {
|
||||
validateField('emailClient', true);
|
||||
updateSubmitButtonState('clientForm');
|
||||
});
|
||||
|
||||
document.getElementById('adresseClient').addEventListener('input', function () {
|
||||
validateField('adresseClient', true);
|
||||
updateSubmitButtonState('clientForm');
|
||||
});
|
||||
|
||||
document.getElementById('codePostalClient').addEventListener('input', function () {
|
||||
validateField('codePostalClient', true);
|
||||
updateSubmitButtonState('clientForm');
|
||||
});
|
||||
|
||||
document.getElementById('villeClient').addEventListener('input', function () {
|
||||
validateField('villeClient', true);
|
||||
updateSubmitButtonState('clientForm');
|
||||
});
|
||||
|
||||
document.getElementById('modalNumClient').addEventListener('input', function () {
|
||||
validateField('modalNumClient', true);
|
||||
updateSubmitButtonState('modalExtraireClient');
|
||||
});
|
||||
}
|
||||
|
||||
// Peupler le formulaire avec les données
|
||||
function populateFormData() {
|
||||
const clientStorage = JSON.parse(sessionStorage.getItem('tmp'));
|
||||
|
||||
if (client) {
|
||||
document.getElementById('nomClient').value = client.nom || '';
|
||||
document.getElementById('numClient').value = client.numClient || '';
|
||||
document.getElementById('adresseClient').value = client.adresse || '';
|
||||
document.getElementById('emailClient').value = client.mail || '';
|
||||
document.getElementById('codePostalClient').value = client.codePostal || '';
|
||||
document.getElementById('villeClient').value = client.ville || '';
|
||||
document.getElementById('noteFinanciereClient').value = client.noteFinanciere || '';
|
||||
}
|
||||
|
||||
if (clientStorage) {
|
||||
document.getElementById('nomClient').value = clientStorage.nomClient || '';
|
||||
document.getElementById('numClient').value = clientStorage.numClient || '';
|
||||
document.getElementById('adresseClient').value = clientStorage.adresseClient || '';
|
||||
document.getElementById('codePostalClient').value = clientStorage.postalClient || '';
|
||||
document.getElementById('villeClient').value = clientStorage.villeClient || '';
|
||||
}
|
||||
}
|
||||
|
||||
// Gérer l'ouverture de liens externes
|
||||
function handleOpenLink(event, urlType) {
|
||||
event.preventDefault();
|
||||
let url = '';
|
||||
|
||||
switch (urlType) {
|
||||
case 'creditsafeURL':
|
||||
url = 'https://www.creditsafe.fr/csfr?UserName=735265dorothee&Password=UH04EuLocXZMxIRqY19w6A%3d%3d&BackOfficeCountry=FR&origincountry=FR&linkages=Y';
|
||||
break;
|
||||
case 'easyQPURL':
|
||||
if (document.getElementById('numClient').value != "") {
|
||||
url = `https://qualite-portefeuille-iard-ent.axa-fr.intraxa/client/${document.getElementById('numClient').value}`;
|
||||
} else {
|
||||
url = `https://qualite-portefeuille-iard-ent.axa-fr.intraxa/`;
|
||||
}
|
||||
break;
|
||||
case 'refNoteFiURL':
|
||||
url = 'https://app.powerbi.com/groups/me/apps/7b48f9a2-bd97-4178-bc1a-a5f7ef6e985f/reports/d29a9f83-cafe-4a0f-bc38-0929921e8cd3/ReportSection?ctid=396b38cc-aa65-492b-bb0e-3d94ed25a97b';
|
||||
break;
|
||||
}
|
||||
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
// Gérer l'extraction axaPAC
|
||||
async function handleExtractClient(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Affiche le modal
|
||||
const elem = document.getElementById('modalExtractAxAPAC');
|
||||
const instance = M.Modal.getInstance(elem);
|
||||
instance.open();
|
||||
}
|
||||
|
||||
async function handleModalExtract() {
|
||||
document.getElementById('modalExtraireClient').disabled = true;
|
||||
|
||||
const response = await fetch(`/client/extract`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
"matricule": matricule,
|
||||
"numClient": document.getElementById("modalNumClient").value
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const res = await response.json();
|
||||
|
||||
if (res.valid) {
|
||||
document.getElementById("error-extract").style.display = "none";
|
||||
document.getElementById('modalExtraireClient').disabled = false;
|
||||
console.log(res);
|
||||
|
||||
// Save sessionStorage for Intermediaire
|
||||
sessionStorage.setItem('tmp', JSON.stringify(res.data));
|
||||
|
||||
// Populate data
|
||||
document.getElementById("numClient").value = (res.data.numClient) ? res.data.numClient : null;
|
||||
document.getElementById("nomClient").value = (res.data.nomClient) ? res.data.nomClient : null;
|
||||
document.getElementById("adresseClient").value = (res.data.adresseClient) ? res.data.adresseClient : null;
|
||||
document.getElementById("codePostalClient").value = (res.data.postalClient) ? res.data.postalClient : null;
|
||||
document.getElementById("villeClient").value = (res.data.villeClient) ? res.data.villeClient : null;
|
||||
|
||||
validateField('numClient', true);
|
||||
validateField('nomClient', true);
|
||||
validateField('adresseClient', true);
|
||||
validateField('codePostalClient', true);
|
||||
validateField('villeClient', true);
|
||||
updateSubmitButtonState('clientForm');
|
||||
|
||||
// close le modal
|
||||
const elem = document.getElementById('modalExtractAxAPAC');
|
||||
const instance = M.Modal.getInstance(elem);
|
||||
instance.close();
|
||||
} else {
|
||||
document.getElementById("error-extract").style.display = "block";
|
||||
document.getElementById('modalExtraireClient').disabled = false;
|
||||
console.log("Problème rencontré lors de l'extraction cl063 AxA PAC :", res.error);
|
||||
}
|
||||
}
|
||||
|
||||
// Gérer la soumission du formulaire
|
||||
async function handleSubmitForm(event) {
|
||||
event.preventDefault();
|
||||
|
||||
let idClient = client?.id;
|
||||
const numClient = document.getElementById('numClient').value;
|
||||
|
||||
let responseClient;
|
||||
|
||||
if (numClient) {
|
||||
responseClient = await fetch(`/client/read/${numClient}`, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
const dataClient = await responseClient.json();
|
||||
|
||||
if (dataClient.valid) {
|
||||
idClient = dataClient.idClient;
|
||||
} else {
|
||||
// Créez le client ici si non trouvé
|
||||
const responseCreateClient = await fetch(`/client/create`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
const dataCreateClient = await responseCreateClient.json();
|
||||
|
||||
if (dataCreateClient.valid) {
|
||||
idClient = dataCreateClient.client.id;
|
||||
}
|
||||
}
|
||||
|
||||
// Mettre à jour le contrat avec l'ID du client
|
||||
if (idClient) {
|
||||
const response = await fetch(`/client/update/${idClient}`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
"nom": document.getElementById('nomClient').value.toUpperCase(),
|
||||
"numClient": document.getElementById('numClient').value,
|
||||
"adresse": document.getElementById('adresseClient').value.toUpperCase(),
|
||||
"mail": document.getElementById('emailClient').value,
|
||||
"codePostal": document.getElementById('codePostalClient').value,
|
||||
"ville": document.getElementById('villeClient').value.toUpperCase(),
|
||||
"noteFinanciere": document.getElementById('noteFinanciereClient').value
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.valid) {
|
||||
const idContrat = contrat?.id;
|
||||
|
||||
fetch(`/contrat/update/client/${idContrat}/${idClient}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.valid) {
|
||||
// Update Session storage
|
||||
const clientStorage = JSON.parse(sessionStorage.getItem('tmp'));
|
||||
|
||||
if (clientStorage) {
|
||||
clientStorage.nomClient = document.getElementById('nomClient').value.toUpperCase();
|
||||
clientStorage.numClient = document.getElementById('numClient').value;
|
||||
clientStorage.adresseClient = document.getElementById('adresseClient').value.toUpperCase();
|
||||
clientStorage.postalClient = document.getElementById('codePostalClient').value;
|
||||
clientStorage.villeClient = document.getElementById('villeClient').value.toUpperCase();
|
||||
|
||||
sessionStorage.setItem('tmp', JSON.stringify(clientStorage))
|
||||
}
|
||||
|
||||
// Redirect vers intermédiaire
|
||||
window.location.href = `/navParcours?numParcours=${getNumParcoursFromURL()}&submenu=intermediaire`;
|
||||
} else {
|
||||
console.log('Echec lors de la mise à jour de la relation id contrat - id client :', data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Exposer init globalement pour y accéder depuis l'extérieur
|
||||
window.initSubmenuForm = init;
|
||||
})();
|
||||
|
|
@ -1,443 +0,0 @@
|
|||
// Événement de vérification d'authentification
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Vérification de la page sur laquelle nous sommes
|
||||
if (window.location.pathname !== '/auth') {
|
||||
// Récupération du token JWT du localStorage
|
||||
const token = localStorage.getItem('jwtToken');
|
||||
|
||||
// Si pas de token, redirige vers la page d'authentification
|
||||
if (!token) {
|
||||
window.location.replace('/auth');
|
||||
} else {
|
||||
// Si un token existe, vérification de sa validité
|
||||
fetch('/auth/verifyToken', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token
|
||||
}
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
|
||||
// Si le token n'est pas valide, redirige vers la page d'authentification
|
||||
if (!data.valid) {
|
||||
localStorage.removeItem('jwtToken');
|
||||
window.location.replace('/auth');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Événement du bouton déconnexion
|
||||
document.addEventListener('DOMContentLoaded', function (event) {
|
||||
// Empêche le comportement par défaut du formulaire
|
||||
event.preventDefault();
|
||||
|
||||
const logoutBtn = document.getElementById('logoutBtn');
|
||||
|
||||
if (window.location.pathname === '/auth') {
|
||||
logoutBtn.style.display = 'none';
|
||||
} else {
|
||||
logoutBtn.addEventListener('click', function () {
|
||||
// Suppression du token JWT du localStorage et redirection
|
||||
localStorage.removeItem('jwtToken');
|
||||
window.location.replace('/auth');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Événement d'initialisation des Materialize content
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Init dropdown
|
||||
var sidenav = document.querySelectorAll('.sidenav');
|
||||
M.Sidenav.init(sidenav);
|
||||
|
||||
// Init Modal
|
||||
var modals = document.querySelectorAll('.modal');
|
||||
M.Modal.init(modals);
|
||||
});
|
||||
|
||||
// Événement condition de groupe pour les menus
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const token = localStorage.getItem('jwtToken');
|
||||
|
||||
if (token) {
|
||||
const decoded = jwt_decode(token);
|
||||
const userAuthGroupe = decoded.userAuthGroupe;
|
||||
|
||||
const reportingItem = document.getElementById("reportingSidenavSelect");
|
||||
const adminItem = document.getElementById("adminSidenavSelect");
|
||||
|
||||
// Cache par défaut
|
||||
reportingItem.style.display = 'none';
|
||||
adminItem.style.display = 'none';
|
||||
|
||||
if (userAuthGroupe === 'MANAGER' || userAuthGroupe === 'ADMIN') {
|
||||
reportingItem.style.display = 'block';
|
||||
}
|
||||
|
||||
if (userAuthGroupe === 'ADMIN') {
|
||||
adminItem.style.display = 'block';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fonction pour extraire le numéro de parcours de l'URL
|
||||
function getNumParcoursFromURL() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
return urlParams.get('numParcours');
|
||||
}
|
||||
|
||||
|
||||
// Fonction pour extraire le sous-menu de l'URL
|
||||
function getSubmenuFromURL() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
return urlParams.get('submenu');
|
||||
}
|
||||
|
||||
// Fonction pour vérifier si une valeur est nulle ou non définie
|
||||
function isNullOrUndefined(value) {
|
||||
return value === null || value === undefined;
|
||||
}
|
||||
|
||||
// Fonction pour éviter la duplication du code fetch
|
||||
async function fetchWithJson(url, method, body = null, useLoader = true) {
|
||||
let loaderShown = false;
|
||||
|
||||
if (useLoader && typeof showLoader === 'function') {
|
||||
showLoader();
|
||||
loaderShown = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
if (body) options.body = JSON.stringify(body);
|
||||
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) throw new Error('Réseau ou erreur serveur');
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (loaderShown && typeof hideLoader === 'function') {
|
||||
hideLoader();
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
// Toujours cacher le loader en cas d'erreur
|
||||
if (loaderShown && typeof hideLoader === 'function') {
|
||||
hideLoader();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction de load du parcours en session storage
|
||||
async function loadParcours(numParcours) {
|
||||
try {
|
||||
const data = await fetchWithJson(`/parcours/read/${numParcours}`, 'GET');
|
||||
|
||||
if (data.valid) {
|
||||
sessionStorage.setItem('parcours', JSON.stringify(data.parcours));
|
||||
} else {
|
||||
sessionStorage.setItem('parcours', null);
|
||||
sessionStorage.setItem('admins', JSON.stringify(data.admins));
|
||||
console.log("Parcours introuvable");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération des informations parcours :", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction générique de toggle
|
||||
function toggler(btn, div) {
|
||||
if (document.getElementById(btn).checked) {
|
||||
document.getElementById(div).style.display = 'block';
|
||||
} else {
|
||||
document.getElementById(div).style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction de load du parcours en session storage
|
||||
async function loadContrat(idContrat) {
|
||||
try {
|
||||
const data = await fetchWithJson(`/contrat/read/id/${idContrat}`, 'GET');
|
||||
|
||||
if (data.valid) {
|
||||
sessionStorage.setItem('contrat', JSON.stringify(data.contrat));
|
||||
} else {
|
||||
sessionStorage.setItem('contrat', null);
|
||||
console.log("Contrat introuvable");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la récupération des informations contrat :", error);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Fonctions utilitaires génériques ==========
|
||||
|
||||
/**
|
||||
* Formatage des dates (ISO vers format français)
|
||||
* @param {string} iso - Date au format ISO
|
||||
* @param {boolean} withTime - Inclure l'heure (défaut: true)
|
||||
* @returns {string} Date formatée (dd/mm/yyyy ou dd/mm/yyyy hh:mm)
|
||||
*/
|
||||
function fmtDate(iso, withTime = true) {
|
||||
// Si la valeur est null, undefined, ou vide
|
||||
if (!iso || (typeof iso === 'string' && iso.trim() === "")) return "NC";
|
||||
|
||||
// Convertir en string si ce n'est pas déjà le cas
|
||||
const dateStr = String(iso).trim();
|
||||
|
||||
// Vérifier les valeurs invalides connues
|
||||
if (dateStr === "00/00/0000" || dateStr === "00/00" || dateStr === "null" || dateStr === "undefined") {
|
||||
return "NC";
|
||||
}
|
||||
|
||||
let d;
|
||||
|
||||
// Si c'est déjà au format jj/mm/aaaa (format français)
|
||||
if (dateStr.includes("/") && dateStr.split("/").length === 3) {
|
||||
const parts = dateStr.split("/");
|
||||
const day = parseInt(parts[0], 10);
|
||||
const month = parseInt(parts[1], 10);
|
||||
const year = parseInt(parts[2], 10);
|
||||
|
||||
// Vérifier si les valeurs sont valides
|
||||
if (isNaN(day) || isNaN(month) || isNaN(year)) {
|
||||
return "NC";
|
||||
}
|
||||
|
||||
// Si le jour ou le mois est 00, considérer comme invalide
|
||||
if (day === 0 || month === 0) {
|
||||
return "NC";
|
||||
}
|
||||
|
||||
// Si le mois est invalide
|
||||
if (month < 1 || month > 12) {
|
||||
return "NC";
|
||||
}
|
||||
|
||||
// Si l'année est 0000, afficher juste jj/mm (sans l'année)
|
||||
if (year === 0) {
|
||||
return `${String(day).padStart(2, "0")}/${String(month).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
// Si l'année est valide, créer une vraie date et valider
|
||||
const monthIndex = month - 1; // Les mois commencent à 0
|
||||
d = new Date(year, monthIndex, day);
|
||||
// Vérifier si la date est valide (ex: 31/02/2000 serait invalide)
|
||||
if (d.getDate() !== day || d.getMonth() !== monthIndex || d.getFullYear() !== year) {
|
||||
return "NC";
|
||||
}
|
||||
|
||||
// Formater la date
|
||||
const dd = String(d.getDate()).padStart(2, "0");
|
||||
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const yyyy = d.getFullYear();
|
||||
if (!withTime) return `${dd}/${mm}/${yyyy}`;
|
||||
const hh = String(d.getHours()).padStart(2, "0");
|
||||
const mi = String(d.getMinutes()).padStart(2, "0");
|
||||
return `${dd}/${mm}/${yyyy} ${hh}:${mi}`;
|
||||
}
|
||||
// Si c'est au format jj/mm (pour date d'échéance)
|
||||
else if (dateStr.includes("/") && dateStr.split("/").length === 2) {
|
||||
const parts = dateStr.split("/");
|
||||
const day = parseInt(parts[0], 10);
|
||||
const month = parseInt(parts[1], 10);
|
||||
|
||||
// Pour l'échéance, on retourne juste jj/mm
|
||||
if (isNaN(day) || isNaN(month) || day === 0 || month === 0 || month > 12) {
|
||||
return "NC";
|
||||
}
|
||||
return `${String(day).padStart(2, "0")}/${String(month).padStart(2, "0")}`;
|
||||
}
|
||||
// Sinon, essayer de parser comme date ISO
|
||||
else {
|
||||
d = new Date(dateStr);
|
||||
if (isNaN(d.getTime())) return "NC";
|
||||
|
||||
const dd = String(d.getDate()).padStart(2, "0");
|
||||
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const yyyy = d.getFullYear();
|
||||
if (!withTime) return `${dd}/${mm}/${yyyy}`;
|
||||
const hh = String(d.getHours()).padStart(2, "0");
|
||||
const mi = String(d.getMinutes()).padStart(2, "0");
|
||||
return `${dd}/${mm}/${yyyy} ${hh}:${mi}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un élément key-value pour l'affichage de détails
|
||||
* @param {string} label - Libellé
|
||||
* @param {*} value - Valeur (booléen converti en Oui/Non)
|
||||
* @returns {string} HTML formaté
|
||||
*/
|
||||
function kv(label, value) {
|
||||
const v = (value === true) ? "Oui" : (value === false) ? "Non" : (value ?? "NC");
|
||||
return `<div style="display:flex; gap:8px; margin:2px 0;">
|
||||
<div style="min-width:220px;"><strong>${label}</strong> :</div>
|
||||
<div>${v}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une grille à 2 colonnes pour l'affichage de détails
|
||||
* @param {string} innerLeft - Contenu colonne gauche
|
||||
* @param {string} innerRight - Contenu colonne droite
|
||||
* @returns {string} HTML formaté
|
||||
*/
|
||||
function gridWrap2cols(innerLeft, innerRight) {
|
||||
return `<div style="display:grid;grid-template-columns:repeat(2,minmax(280px,1fr));gap:14px;">${innerLeft}${innerRight}</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction debounce pour limiter la fréquence d'exécution d'une fonction
|
||||
* Utile pour les recherches en temps réel et éviter les appels API excessifs
|
||||
* @param {Function} fn - Fonction à débouncer
|
||||
* @param {number} delay - Délai en millisecondes (défaut: 300ms)
|
||||
* @returns {Function} Fonction débouncée avec méthode cancel()
|
||||
*/
|
||||
function debounce(fn, delay = 300) {
|
||||
let t;
|
||||
function wrapped(...args) {
|
||||
clearTimeout(t);
|
||||
t = setTimeout(() => fn(...args), delay);
|
||||
}
|
||||
wrapped.cancel = () => clearTimeout(t);
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Décode un token JWT et retourne le payload
|
||||
* @param {string} token - Token JWT à décoder
|
||||
* @returns {Object|null} Payload décodé ou null en cas d'erreur
|
||||
*/
|
||||
function parseJwt(token) {
|
||||
try {
|
||||
const base64Url = token.split(".")[1];
|
||||
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const jsonPayload = decodeURIComponent(
|
||||
atob(base64)
|
||||
.split("")
|
||||
.map(function (c) {
|
||||
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
})
|
||||
.join("")
|
||||
);
|
||||
return JSON.parse(jsonPayload);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du décodage du token:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message d'erreur dans l'élément avec l'ID "error"
|
||||
* @param {string} message - Message d'erreur à afficher
|
||||
*/
|
||||
function displayError(message) {
|
||||
const errorElement = document.getElementById("error");
|
||||
if (errorElement) {
|
||||
errorElement.textContent = message;
|
||||
errorElement.style.display = "block";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un message formaté avec différents types (info, warn, error, dev)
|
||||
* @param {string} type - Type de message : 'info', 'warn', 'error', 'dev'
|
||||
* @param {string} title - Titre du message
|
||||
* @param {string} description - Description du message (peut contenir du HTML)
|
||||
* @returns {string} HTML du message formaté
|
||||
*/
|
||||
function createMessageBox(type, title, description) {
|
||||
// Protection contre les paramètres invalides
|
||||
if (!type || typeof type !== 'string') type = 'info';
|
||||
if (!title || typeof title !== 'string') title = 'Message';
|
||||
if (!description || typeof description !== 'string') description = '';
|
||||
|
||||
const configs = {
|
||||
info: {
|
||||
icon: 'fa-info-circle',
|
||||
bgColor: '#e3f2fd',
|
||||
borderColor: '#2196f3',
|
||||
textColor: '#1565c0'
|
||||
},
|
||||
warn: {
|
||||
icon: 'fa-exclamation-triangle',
|
||||
bgColor: '#fff3e0',
|
||||
borderColor: '#ff9800',
|
||||
textColor: '#e65100'
|
||||
},
|
||||
error: {
|
||||
icon: 'fa-times-circle',
|
||||
bgColor: '#ffebee',
|
||||
borderColor: '#f44336',
|
||||
textColor: '#c62828'
|
||||
},
|
||||
dev: {
|
||||
icon: 'fa-tools',
|
||||
bgColor: '#fff3cd',
|
||||
borderColor: '#ffc107',
|
||||
textColor: '#856404'
|
||||
}
|
||||
};
|
||||
|
||||
const config = configs[type] || configs.info;
|
||||
|
||||
// Échappement basique pour éviter les problèmes (description peut contenir du HTML valide)
|
||||
const safeTitle = String(title).replace(/</g, '<').replace(/>/g, '>');
|
||||
// Description peut contenir du HTML (comme <br>), donc on ne l'échappe pas complètement
|
||||
// mais on s'assure qu'elle est une string
|
||||
const safeDescription = String(description);
|
||||
|
||||
return `
|
||||
<div style="padding: 20px; text-align: center; background-color: ${config.bgColor}; border: 1px solid ${config.borderColor}; border-radius: 4px; margin: 10px 0; width: 100%; box-sizing: border-box;">
|
||||
<p style="color: ${config.textColor}; font-weight: 600; margin: 0 0 10px 0;">
|
||||
<i class="fas ${config.icon}" style="margin-right: 8px;"></i>
|
||||
${safeTitle}
|
||||
</p>
|
||||
<p style="color: ${config.textColor}; margin: 0; font-size: 0.9rem;">
|
||||
${safeDescription}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate une valeur en euros :
|
||||
* - espace insécable fine entre milliers,
|
||||
* - virgule pour les décimales,
|
||||
* - 0 à 2 décimales max,
|
||||
* - symbole € à la fin.
|
||||
* Si la valeur est invalide => "NC".
|
||||
*/
|
||||
function formatEuro(value, options) {
|
||||
const opts = Object.assign({ minimumFractionDigits: 0, maximumFractionDigits: 2 }, options || {});
|
||||
if (value === null || value === undefined || value === '' || value === 'NC') return 'NC';
|
||||
|
||||
// Accepte string avec virgule ou espaces
|
||||
const normalized = (typeof value === 'string')
|
||||
? value.replace(/\s|\u00A0|\u202F| /g, '').replace(',', '.')
|
||||
: value;
|
||||
|
||||
const num = Number(normalized);
|
||||
if (!isFinite(num)) return 'NC';
|
||||
|
||||
const formatted = new Intl.NumberFormat('fr-FR', {
|
||||
minimumFractionDigits: opts.minimumFractionDigits,
|
||||
maximumFractionDigits: opts.maximumFractionDigits
|
||||
}).format(num);
|
||||
|
||||
return formatted + ' €';
|
||||
}
|
||||
|
|
@ -1,858 +0,0 @@
|
|||
// public/js/historiqueParcours.js
|
||||
document.addEventListener("DOMContentLoaded", async function () {
|
||||
// Récupération du token
|
||||
const token = localStorage.getItem("jwtToken");
|
||||
|
||||
if (!token) {
|
||||
throw new Error("Aucun token trouvé dans le localStorage.");
|
||||
}
|
||||
|
||||
const userData = parseJwt(token);
|
||||
|
||||
if (!userData) {
|
||||
displayError("Erreur lors de l'extraction des données utilisateur à partir du token.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialiser DataTables en mode server-side (obligé pour pagination)
|
||||
const table = initServerSideDataTable();
|
||||
|
||||
// Variable pour suivre l'état des exports
|
||||
let isExporting = false;
|
||||
|
||||
// Fonction pour désactiver/activer les boutons d'export
|
||||
function setExportButtonsState(disabled) {
|
||||
isExporting = disabled;
|
||||
const buttons = ["#exportCSV", "#exportCSVFilter", "#exportXlxs", "#exportXlxsFilter"];
|
||||
buttons.forEach(selector => {
|
||||
const $btn = $(selector);
|
||||
$btn.prop("disabled", disabled);
|
||||
if (disabled) {
|
||||
$btn.css("opacity", "0.5");
|
||||
$btn.css("cursor", "not-allowed");
|
||||
} else {
|
||||
$btn.css("opacity", "1");
|
||||
$btn.css("cursor", "pointer");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Exports CSV/XLSX
|
||||
$("#exportCSV").on("click", function () {
|
||||
if (isExporting) return;
|
||||
|
||||
const dt = $("#historiqueParcours").DataTable();
|
||||
if (!dt) {
|
||||
displayError("Impossible d'accéder à la table de données.");
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = dt.settings()[0];
|
||||
if (!settings || !settings.aoColumns) {
|
||||
displayError("Structure de données invalide.");
|
||||
return;
|
||||
}
|
||||
|
||||
setExportButtonsState(true);
|
||||
|
||||
const payload = {
|
||||
mode: "full", // export total
|
||||
search: { value: "" },
|
||||
columns: settings.aoColumns.map((c, i) => ({
|
||||
data: i,
|
||||
search: { value: "" }
|
||||
})),
|
||||
// on garde l'ordre actuel pour la récuperation
|
||||
order: (dt.order() || []).map(([col, dir]) => ({ column: col || 0, dir: dir || "asc" }))
|
||||
};
|
||||
|
||||
fetch("/historiqueParcours/export/csv", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then(resp => {
|
||||
if (!resp.ok) throw new Error("Export CSV (complet) impossible");
|
||||
|
||||
return resp.blob();
|
||||
})
|
||||
.then(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "historique_parcours_complet.csv";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
setExportButtonsState(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
displayError("Export CSV (complet) impossible");
|
||||
setExportButtonsState(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#exportCSVFilter").on("click", function () {
|
||||
if (isExporting) return;
|
||||
|
||||
const dt = $("#historiqueParcours").DataTable();
|
||||
if (!dt) {
|
||||
displayError("Impossible d'accéder à la table de données.");
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = dt.settings()[0];
|
||||
if (!settings || !settings.aoColumns) {
|
||||
displayError("Structure de données invalide.");
|
||||
return;
|
||||
}
|
||||
|
||||
setExportButtonsState(true);
|
||||
|
||||
const payload = {
|
||||
mode: "filtered", // export avec les filtres/colonnes/tri actuels
|
||||
search: { value: dt.search() || "" }, // recherche globale
|
||||
columns: settings.aoColumns.map((c, i) => ({
|
||||
data: i,
|
||||
search: { value: dt.column(i).search() || "" } // filtres par colonne
|
||||
})),
|
||||
order: (dt.order() || []).map(([col, dir]) => ({ column: col || 0, dir: dir || "asc" }))
|
||||
};
|
||||
|
||||
fetch("/historiqueParcours/export/csv", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then(resp => {
|
||||
if (!resp.ok) throw new Error("Export CSV (filtré) impossible");
|
||||
return resp.blob();
|
||||
})
|
||||
.then(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "historique_parcours_filtre.csv";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
setExportButtonsState(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
displayError("Export CSV (filtré) impossible");
|
||||
setExportButtonsState(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#exportXlxs").on("click", function () {
|
||||
if (isExporting) return;
|
||||
|
||||
const dt = $("#historiqueParcours").DataTable();
|
||||
if (!dt) {
|
||||
displayError("Impossible d'accéder à la table de données.");
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = dt.settings()[0];
|
||||
if (!settings || !settings.aoColumns) {
|
||||
displayError("Structure de données invalide.");
|
||||
return;
|
||||
}
|
||||
|
||||
setExportButtonsState(true);
|
||||
|
||||
const payload = {
|
||||
mode: "full",
|
||||
search: { value: "" },
|
||||
columns: settings.aoColumns.map((c, i) => ({ data: i, search: { value: "" } })),
|
||||
order: (dt.order() || []).map(([col, dir]) => ({ column: col || 0, dir: dir || "asc" }))
|
||||
};
|
||||
|
||||
fetch("/historiqueParcours/export/xls", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then(resp => { if (!resp.ok) throw new Error(); return resp.blob(); })
|
||||
.then(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url; a.download = "historique_parcours_complet.xls";
|
||||
document.body.appendChild(a); a.click();
|
||||
URL.revokeObjectURL(url); a.remove();
|
||||
setExportButtonsState(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
displayError("Export XLS (complet) impossible");
|
||||
setExportButtonsState(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#exportXlxsFilter").on("click", function () {
|
||||
if (isExporting) return;
|
||||
|
||||
const dt = $("#historiqueParcours").DataTable();
|
||||
if (!dt) {
|
||||
displayError("Impossible d'accéder à la table de données.");
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = dt.settings()[0];
|
||||
if (!settings || !settings.aoColumns) {
|
||||
displayError("Structure de données invalide.");
|
||||
return;
|
||||
}
|
||||
|
||||
setExportButtonsState(true);
|
||||
|
||||
const payload = {
|
||||
mode: "filtered", // export avec les filtres/colonnes/tri actuels
|
||||
search: { value: dt.search() || "" }, // recherche globale
|
||||
columns: settings.aoColumns.map((c, i) => ({
|
||||
data: i,
|
||||
search: { value: dt.column(i).search() || "" } // filtres par colonne
|
||||
})),
|
||||
order: (dt.order() || []).map(([col, dir]) => ({ column: col || 0, dir: dir || "asc" }))
|
||||
};
|
||||
|
||||
fetch("/historiqueParcours/export/xls", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
.then(resp => {
|
||||
if (!resp.ok) throw new Error("Export XLS (filtré) impossible");
|
||||
return resp.blob();
|
||||
})
|
||||
.then(blob => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = "historique_parcours_filtre.xls";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
setExportButtonsState(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
displayError("Export XLS (filtré) impossible");
|
||||
setExportButtonsState(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Délégation pour la génération de projet
|
||||
$("#historiqueParcours").on("click", "button#btnGenerate", function () {
|
||||
const numParcours = $(this).data("num-parcours");
|
||||
const produit = $(this).data("produit");
|
||||
generateProject(numParcours, produit);
|
||||
});
|
||||
});
|
||||
|
||||
/* =========================
|
||||
* Helpers spécifiques server-side
|
||||
* ========================= */
|
||||
|
||||
// Initialisation DataTables en server-side (recherche globale + par colonnes + tri + pagination)
|
||||
function initServerSideDataTable() {
|
||||
let inflightController = null;
|
||||
|
||||
const table = $("#historiqueParcours").DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searching: true,
|
||||
paging: true,
|
||||
orderCellsTop: true,
|
||||
fixedHeader: true,
|
||||
responsive: false,
|
||||
autoWidth: false,
|
||||
scrollX: false,
|
||||
pageLength: 10,
|
||||
retrieve: true,
|
||||
order: [[0, "desc"]],
|
||||
searchDelay: 350,
|
||||
language: {
|
||||
search: "Rechercher",
|
||||
lengthMenu: "Afficher _MENU_ entrées par page",
|
||||
info: "Affichage de _START_ à _END_ sur _TOTAL_ entrées",
|
||||
infoEmpty: "Affichage de 0 à 0 sur 0 entrée",
|
||||
infoFiltered: "(filtré de _MAX_ entrées au total)",
|
||||
paginate: { first: "Début", previous: "Précédent", next: "Suivant", last: "Fin" },
|
||||
},
|
||||
|
||||
ajax: function (data, callback) {
|
||||
const body = {
|
||||
draw: data.draw || 1,
|
||||
start: data.start || 0,
|
||||
length: data.length || 10,
|
||||
order: data.order || [],
|
||||
columns: data.columns || [],
|
||||
search: data.search || { value: "" },
|
||||
};
|
||||
|
||||
if (inflightController) inflightController.abort(); // action en cours
|
||||
inflightController = new AbortController();
|
||||
const currentController = inflightController;
|
||||
|
||||
fetch("/historiqueParcours/datatable", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
signal: currentController.signal,
|
||||
})
|
||||
.then(res => {
|
||||
// Vérifier si la requête a été annulée
|
||||
if (currentController.signal.aborted || inflightController !== currentController) {
|
||||
return null;
|
||||
}
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return res.json();
|
||||
})
|
||||
.then(payload => {
|
||||
// Vérifier si la requête a été annulée
|
||||
if (currentController.signal.aborted || inflightController !== currentController) {
|
||||
return;
|
||||
}
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
throw new Error("Réponse invalide du serveur");
|
||||
}
|
||||
callback({
|
||||
draw: payload.draw || 0,
|
||||
recordsTotal: payload.recordsTotal || 0,
|
||||
recordsFiltered: payload.recordsFiltered || payload.recordsTotal || 0,
|
||||
data: Array.isArray(payload.data) ? payload.data : []
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
// Ignorer silencieusement toutes les erreurs d'abort
|
||||
if (err && (err.name === "AbortError" || err.name === "DOMException")) {
|
||||
return;
|
||||
}
|
||||
// Vérifier aussi si le signal a été aborted
|
||||
if (currentController.signal.aborted || inflightController !== currentController) {
|
||||
return;
|
||||
}
|
||||
// Seulement afficher une erreur si ce n'est PAS un abort
|
||||
displayError("Failed to fetch data. Please try again later.");
|
||||
callback({ draw: 0, recordsTotal: 0, recordsFiltered: 0, data: [] });
|
||||
});
|
||||
},
|
||||
|
||||
initComplete: function () {
|
||||
const api = this.api();
|
||||
|
||||
// Recherche globale : debounce y compris ENTER (plus de bypass immédiat)
|
||||
const $globalInput = $('div.dataTables_filter input[type="search"]');
|
||||
const $filterLabel = $globalInput.closest('label');
|
||||
$globalInput.off('.DT'); // nettoie handlers datatables
|
||||
const debouncedGlobal = debounce((v) => {
|
||||
api.search(v);
|
||||
api.ajax.reload();
|
||||
}, 350);
|
||||
|
||||
// Fonction pour gérer l'affichage du texte "Rechercher"
|
||||
function toggleSearchPlaceholder() {
|
||||
if ($globalInput.val().trim() !== '') {
|
||||
$filterLabel.addClass('has-value');
|
||||
} else {
|
||||
$filterLabel.removeClass('has-value');
|
||||
}
|
||||
}
|
||||
|
||||
$globalInput.on('input keyup keydown', function () {
|
||||
debouncedGlobal(this.value);
|
||||
toggleSearchPlaceholder();
|
||||
});
|
||||
|
||||
// Vérifier l'état initial
|
||||
toggleSearchPlaceholder();
|
||||
|
||||
// Recherche par colonne avec DEBOUNCE (ENTER inclus)
|
||||
const debouncedColSearch = debounce((i, val) => {
|
||||
api.column(i).search(val);
|
||||
api.ajax.reload();
|
||||
}, 350);
|
||||
|
||||
$("#historiqueParcours thead tr:eq(1) th").each(function (i) {
|
||||
$("input", this).on("input keyup keydown change", function () {
|
||||
debouncedColSearch(i, this.value);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("#toggleSearch").on("click", function () {
|
||||
const row = $("#historiqueParcours thead tr:eq(1)");
|
||||
row.toggle();
|
||||
|
||||
if (row.is(":visible")) {
|
||||
$(this).text("ENLEVER LA RECHERCHE PAR COLONNE");
|
||||
} else {
|
||||
$(this).text("ACTIVER LA RECHERCHE PAR COLONNE");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Cacher la 2e ligne au départ
|
||||
$("#historiqueParcours thead tr:eq(1)").hide();
|
||||
},
|
||||
|
||||
// --- Ajout bouton détails en 1re colonne
|
||||
columnDefs: [
|
||||
{
|
||||
targets: 0,
|
||||
render: function (data, type, row) {
|
||||
const np = String(data ?? "");
|
||||
const numPacours = np.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||||
|
||||
|
||||
return `
|
||||
<span class="col-with-text">
|
||||
<button class="btn-row-details" title="Détails" aria-label="Afficher les détails">?</button>
|
||||
<span class="np">${numPacours}</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
{ type: "date-eu", targets: 1 }, // Date de Création (colonne 1)
|
||||
{
|
||||
// Appliquer la classe nc-value aux cellules contenant "NC"
|
||||
targets: "_all",
|
||||
createdCell: function (td, cellData, rowData, row, col) {
|
||||
// Exclure la première colonne (bouton détails) et les deux dernières (boutons)
|
||||
if (col !== 0 && col < rowData.length - 2) {
|
||||
const cellText = String(cellData || "").trim();
|
||||
if (cellText === "NC") {
|
||||
td.classList.add("nc-value");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ responsivePriority: 1, targets: -1 },
|
||||
{ responsivePriority: 2, targets: -2 }
|
||||
],
|
||||
});
|
||||
|
||||
// --- Toggle des détails
|
||||
$('#historiqueParcours tbody').on('click', '.btn-row-details', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const api = $('#historiqueParcours').DataTable();
|
||||
const $tr = $(this).closest('tr');
|
||||
|
||||
// si c'est une ligne enfant (responsive), remonter à la parent
|
||||
const row = api.row($tr.hasClass('child') ? $tr.prev() : $tr);
|
||||
|
||||
if (row.child.isShown()) {
|
||||
row.child.hide();
|
||||
// change icône
|
||||
this.textContent = "?";
|
||||
this.style.background = "#e74c3c";
|
||||
return;
|
||||
}
|
||||
|
||||
// récupérer le numParcours depuis la 1re cellule
|
||||
const raw = row.data()?.[0] ?? "";
|
||||
const tmp = document.createElement('div');
|
||||
tmp.innerHTML = String(raw);
|
||||
const numParcours = tmp.textContent.replace("?", "").trim();
|
||||
if (!numParcours) return;
|
||||
|
||||
row.child('<div style="padding:12px 16px; font-style:italic;">Chargement des détails…</div>').show();
|
||||
this.textContent = "?";
|
||||
this.style.background = "#c0392b";
|
||||
|
||||
fetch(`/historiqueParcours/details/${encodeURIComponent(numParcours)}`)
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
||||
return r.json();
|
||||
})
|
||||
.then(payload => {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
throw new Error("Réponse invalide");
|
||||
}
|
||||
if (!payload.valid) {
|
||||
row.child(createMessageBox('error', 'Impossible de charger les détails',
|
||||
'Une erreur est survenue lors du chargement des détails du parcours.'));
|
||||
this.textContent = "?";
|
||||
this.style.background = "#e74c3c";
|
||||
return;
|
||||
}
|
||||
row.child(formatDetailsPanel(payload));
|
||||
})
|
||||
.catch(() => {
|
||||
row.child(createMessageBox('error', 'Erreur de chargement',
|
||||
'Une erreur réseau est survenue lors du chargement des détails.'));
|
||||
this.textContent = "?";
|
||||
this.style.background = "#e74c3c";
|
||||
});
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/* =========================
|
||||
* Fonctions annexes importantes
|
||||
* ========================= */
|
||||
|
||||
async function fetchUserDetails(matriculeUser) {
|
||||
try {
|
||||
const response = await fetch(`/user/read/matricule/${matriculeUser}`);
|
||||
const data = await response.json();
|
||||
|
||||
return data.valid ? data : null;
|
||||
} catch (error) {
|
||||
displayError(`Erreur lors de la récupération du contrat avec le matricule ${matriculeUser} :`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function generateProject(numParcours, produit) {
|
||||
try {
|
||||
if (!numParcours || !produit) {
|
||||
displayError("Paramètres manquants pour la génération du projet.");
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`/generate/${produit}/projet/${encodeURIComponent(numParcours)}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error("Erreur réseau ou serveur");
|
||||
|
||||
const disposition = response.headers.get("content-disposition");
|
||||
let filename = "projet.docx";
|
||||
if (disposition) {
|
||||
const parts = disposition.split(";");
|
||||
if (parts.length > 1) {
|
||||
const filenamePart = parts[1].trim();
|
||||
if (filenamePart.startsWith("filename=")) {
|
||||
filename = filenamePart.split("=")[1]?.replace(/"/g, "") || filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
if (!blob || blob.size === 0) {
|
||||
throw new Error("Fichier vide reçu");
|
||||
}
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
} catch (error) {
|
||||
displayError("Erreur lors de la génération du projet : " + (error?.message || "Erreur inconnue"));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- Helpers rendu ----------
|
||||
// Les fonctions kv, fmtDate, gridWrap2cols, debounce, parseJwt et displayError sont dans global.js
|
||||
|
||||
//formatDetailsPanel : rend l'HTML pour la récuperation des données par parcours
|
||||
function formatDetailsPanel(payload) {
|
||||
const prodKey = (payload.produit || "").toUpperCase();
|
||||
const prod = payload.produitRecord || null;
|
||||
const contrat = payload.contrat || null;
|
||||
|
||||
let body = "";
|
||||
if (prodKey === "TPPC") body = sectionTPPC(prod, contrat);
|
||||
else if (prodKey === "RC") body = sectionRC(prod, contrat);
|
||||
else if (prodKey === "FAC") body = sectionFAC(prod, contrat);
|
||||
else body = createMessageBox('warn', 'Produit non renseigné', 'Le type de produit n\'a pas été spécifié pour ce parcours.');
|
||||
|
||||
return `
|
||||
<div class="parcours-details" style="padding:14px 16px; background:#fafafa; border-top:1px solid #e0e0e0;">
|
||||
<div style="font-weight:600; margin:0 0 8px;">Détails ${prodKey || ""}</div>
|
||||
${body}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
// Retourne le tarif en fonction du type de tarif
|
||||
function buildTarifBlock({ tarifRef, ht, ttc }) {
|
||||
if (tarifRef && String(tarifRef).trim() !== '') {
|
||||
return kv("Tarif de référence", tarifRef);
|
||||
}
|
||||
const htStr = (ht !== undefined && ht !== null) ? formatEuro(ht) : "NC";
|
||||
const ttcStr = (ttc !== undefined && ttc !== null) ? formatEuro(ttc) : "NC";
|
||||
return kv("Tarif commercial HT / TTC", `${htStr} / ${ttcStr}`);
|
||||
}
|
||||
|
||||
function safeCA(value) {
|
||||
return (value === null || value === undefined || value === '' || value === 'NC')
|
||||
? "NC"
|
||||
: formatEuro(value);
|
||||
}
|
||||
|
||||
|
||||
// ---------- Sections produit ----------
|
||||
|
||||
//sectionTTPC : récupere les détails d'un parcours TPPC pour le mettre en forme (2 colonnes)
|
||||
// ---------- TPPC ----------
|
||||
function sectionTPPC(produit, contrat) {
|
||||
if (!produit) {
|
||||
if (contrat) {
|
||||
return createMessageBox(
|
||||
'info',
|
||||
'Informations non disponibles',
|
||||
'Les informations sur ce Parcours TPPC ne sont pas encore disponibles.<br>La fiche TPPC n\'a pas encore été créée pour ce parcours.'
|
||||
);
|
||||
}
|
||||
return createMessageBox(
|
||||
'error',
|
||||
'Fiche TPPC introuvable',
|
||||
'Impossible de récupérer les informations de la fiche TPPC pour ce parcours.'
|
||||
);
|
||||
}
|
||||
|
||||
const tarif = produit?.["@expand"]?.tarif || null; // tppctarif
|
||||
const projet = produit?.["@expand"]?.projet || null; // tppcprojet
|
||||
|
||||
// 1) CA
|
||||
const caStr = safeCA(produit.ca);
|
||||
|
||||
// 2) Type de cotisation
|
||||
let typeCotStr = "NC";
|
||||
if (projet?.typeCot) {
|
||||
typeCotStr = (projet.typeCot === "revisable") ? "Révisable"
|
||||
: (projet.typeCot === "forfaitaire") ? "Forfaitaire"
|
||||
: projet.typeCot;
|
||||
}
|
||||
|
||||
// 3) Activité
|
||||
const activiteAssuree = produit.actAssuree || "NC";
|
||||
const nbVehicules = (produit.nbVehic !== undefined && produit.nbVehic !== null) ? String(produit.nbVehic) : "NC";
|
||||
|
||||
// 4) Marchandises / Garanties
|
||||
const garanties = Array.isArray(produit.garanties)
|
||||
? produit.garanties.join(", ")
|
||||
: (produit.garanties || "NC");
|
||||
|
||||
const extensions = [];
|
||||
if (produit.marCiternes) extensions.push("Citernes");
|
||||
if (produit.marDenreesSousTemp) extensions.push("Denrées sous température");
|
||||
if (produit.marAnimaux) extensions.push("Animaux vivants");
|
||||
if (produit.marFranchise) extensions.push("Franchise");
|
||||
const extensionsStr = extensions.length > 0 ? extensions.join(", ") : "Aucune";
|
||||
|
||||
// 5) Zones (supprimer pour TPPC)
|
||||
const zonesStr = "NC";
|
||||
|
||||
// 6) Dates (projet)
|
||||
const dateEffet = projet?.dateEffet ? fmtDate(projet.dateEffet, false) : "NC";
|
||||
const dateEcheance = projet?.dateEcheance ? fmtDate(projet.dateEcheance, false) : "NC";
|
||||
const dateFin = projet?.dateFin ? fmtDate(projet.dateFin, false) : "NC";
|
||||
const datesStr = `Effet: ${dateEffet} / Échéance: ${dateEcheance} / Fin: ${dateFin}`;
|
||||
|
||||
// 7) Tarif (ref ou com HT/TTC)
|
||||
const blocTarif = buildTarifBlock({
|
||||
tarifRef: tarif?.tarifRef,
|
||||
ht: (produit.primeHT ?? produit.cotTotalHT ?? null),
|
||||
ttc: (produit.primeTTC ?? produit.cotTotalTTC ?? null)
|
||||
});
|
||||
|
||||
// Disposition en 2 colonnes
|
||||
const gauche = [
|
||||
kv("Chiffre d'affaires", caStr),
|
||||
kv("Type de cotisation", typeCotStr),
|
||||
kv("Activité assurée", activiteAssuree),
|
||||
kv("Nombre de véhicules", nbVehicules),
|
||||
kv("Garanties", garanties),
|
||||
kv("Extensions de garanties", extensionsStr),
|
||||
].join("");
|
||||
|
||||
const droite = [
|
||||
kv("Zones", zonesStr),
|
||||
kv("Dates", datesStr),
|
||||
blocTarif,
|
||||
].join("");
|
||||
|
||||
return gridWrap2cols(gauche, droite);
|
||||
}
|
||||
|
||||
|
||||
//sectionRC : récupere les détails d'un parcours RC pour le mettre en forme (2 colonnes)
|
||||
// ---------- RC ----------
|
||||
function sectionRC(produit, contrat) {
|
||||
if (!produit && contrat) {
|
||||
return createMessageBox(
|
||||
'info',
|
||||
'Informations non disponibles',
|
||||
'Les informations sur ce Parcours RC ne sont pas encore disponibles.<br>La fiche RC n\'a pas encore été créée pour ce parcours.'
|
||||
);
|
||||
}
|
||||
if (!produit) {
|
||||
return createMessageBox(
|
||||
'dev',
|
||||
'Fonctionnalité en cours de développement',
|
||||
'L\'affichage des détails pour les parcours RC n\'est pas encore disponible.<br>Cette fonctionnalité sera bientôt implémentée.'
|
||||
);
|
||||
}
|
||||
|
||||
// 1) CA
|
||||
const caStr = safeCA(produit.ca);
|
||||
|
||||
// 2) Type de cotisation
|
||||
let typeCotStr = "NC";
|
||||
if (produit.typeCot) {
|
||||
typeCotStr = (produit.typeCot === "revisable") ? "Révisable"
|
||||
: (produit.typeCot === "forfaitaire") ? "Forfaitaire"
|
||||
: produit.typeCot;
|
||||
}
|
||||
|
||||
// 3) Activités
|
||||
const activites = [];
|
||||
if (produit.actVoiturier) activites.push("Voiturier");
|
||||
if (produit.actLoueur) activites.push("Loueur");
|
||||
if (produit.actMultimodal) activites.push("Multimodal");
|
||||
if (produit.actDouane) activites.push("Douane");
|
||||
if (produit.actLevageur) activites.push("Levageur");
|
||||
if (produit.actTransitaire) activites.push("Transitaire");
|
||||
const activiteAssuree = activites.length > 0 ? activites.join(", ") : "NC";
|
||||
|
||||
// 4) Marchandises
|
||||
const garanties = [];
|
||||
if (produit.marRoulant) garanties.push("Roulant");
|
||||
if (produit.marEngins) garanties.push("Engins");
|
||||
if (produit.marPerissable) garanties.push("Périssable");
|
||||
if (produit.marOrdinaire) garanties.push("Ordinaire");
|
||||
if (produit.marAnimaux) garanties.push("Animaux");
|
||||
if (produit.marCiterne)garanties.push("Citerne");
|
||||
if (produit.marBeton) garanties.push("Béton");
|
||||
if (produit.marExceptionnels) garanties.push("Exceptionnels");
|
||||
if (produit.marMobilerUsag) garanties.push("Mobilier usagé");
|
||||
if (produit.marVrac) garanties.push("Vrac");
|
||||
if (produit.marRoulantDem) garanties.push("Roulant déménagement");
|
||||
const garantiesStr = garanties.length > 0 ? garanties.join(", ") : "NC";
|
||||
|
||||
// 5) Zones
|
||||
const zones = [];
|
||||
if (produit.zone1) zones.push("1");
|
||||
if (produit.zone2) zones.push("2");
|
||||
if (produit.zone3) zones.push("3");
|
||||
if (produit.zone4) zones.push("4");
|
||||
if (produit.zone5) zones.push("5");
|
||||
if (produit.zone6) zones.push("6");
|
||||
const zonesStr = zones.length > 0 ? zones.join(", ") : "NC";
|
||||
|
||||
// 6) Dates
|
||||
const dateEffet = produit.dateEffet ? fmtDate(produit.dateEffet, false) : "NC";
|
||||
const dateEcheance = produit.dateEcheance ? fmtDate(produit.dateEcheance, false) : "NC";
|
||||
const dateFin = produit.dateFin ? fmtDate(produit.dateFin, false) : "NC";
|
||||
const datesStr = `Effet: ${dateEffet} / Échéance: ${dateEcheance} / Fin: ${dateFin}`;
|
||||
|
||||
// 7) Tarif
|
||||
const tarif = produit?.["@expand"]?.tarif || null;
|
||||
const blocTarif = buildTarifBlock({
|
||||
tarifRef: tarif?.tarifRef,
|
||||
ht: produit.cotTotalHT ?? null,
|
||||
ttc: produit.cotTotalTTC ?? null
|
||||
});
|
||||
|
||||
const gauche = [
|
||||
kv("Chiffre d'affaires", caStr),
|
||||
kv("Type de cotisation", typeCotStr),
|
||||
kv("Activités assurées", activiteAssuree),
|
||||
kv("Marchandises", garantiesStr),
|
||||
].join("");
|
||||
|
||||
const droite = [
|
||||
kv("Zones", zonesStr),
|
||||
kv("Dates", datesStr),
|
||||
blocTarif,
|
||||
].join("");
|
||||
|
||||
return gridWrap2cols(gauche, droite);
|
||||
}
|
||||
|
||||
|
||||
//sectionFAC : récupere les détails d'un parcours FAC pour le mettre en forme (2 colonnes)
|
||||
// ---------- FAC ----------
|
||||
function sectionFAC(produit, contrat) {
|
||||
if (!produit && contrat) {
|
||||
return createMessageBox(
|
||||
'info',
|
||||
'Informations non disponibles',
|
||||
'Les informations sur ce Parcours FAC ne sont pas encore disponibles.<br>La fiche FAC n\'a pas encore été créée pour ce parcours.'
|
||||
);
|
||||
}
|
||||
if (!produit) {
|
||||
return createMessageBox(
|
||||
'dev',
|
||||
'Fonctionnalité en cours de développement',
|
||||
'L\'affichage des détails pour les parcours FAC n\'est pas encore disponible.<br>Cette fonctionnalité sera bientôt implémentée.'
|
||||
);
|
||||
}
|
||||
|
||||
// 1) CA
|
||||
const caStr = safeCA(produit.ca);
|
||||
|
||||
// 2) Type de cotisation
|
||||
const typeCotStr = "NC";
|
||||
|
||||
// 3) Activité
|
||||
const activiteAssuree = produit.actAssure || "NC";
|
||||
|
||||
// 4) Garanties (modes de transport déclarés)
|
||||
const garanties = [];
|
||||
if (produit.terrestre && produit.terrestre !== "NC" && produit.terrestre !== "") garanties.push("Terrestre");
|
||||
if (produit.maritime && produit.maritime !== "NC" && produit.maritime !== "") garanties.push("Maritime");
|
||||
if (produit.aerien && produit.aerien !== "NC" && produit.aerien !== "") garanties.push("Aérien");
|
||||
if (produit.postal && produit.postal !== "NC" && produit.postal !== "") garanties.push("Postal");
|
||||
if (produit.fluvial && produit.fluvial !== "NC" && produit.fluvial !== "") garanties.push("Fluvial");
|
||||
const garantiesStr = garanties.length > 0 ? garanties.join(", ") : "NC";
|
||||
|
||||
// 5) Zones
|
||||
const zones = [];
|
||||
if (produit.zone1) zones.push("1");
|
||||
if (produit.zone2) zones.push("2");
|
||||
if (produit.zone3) zones.push("3");
|
||||
if (produit.zone4) zones.push("4");
|
||||
if (produit.zone5) zones.push("5");
|
||||
if (produit.zone6) zones.push("6");
|
||||
const zonesStr = zones.length > 0 ? zones.join(", ") : "NC";
|
||||
|
||||
// 6) Dates
|
||||
const dateEffet = produit.dateEffet ? fmtDate(produit.dateEffet, false) : "NC";
|
||||
const dateEcheance = produit.dateEcheance ? fmtDate(produit.dateEcheance, false) : "NC";
|
||||
const dateFin = produit.dateFin ? fmtDate(produit.dateFin, false) : "NC";
|
||||
const datesStr = `Effet: ${dateEffet} / Échéance: ${dateEcheance} / Fin: ${dateFin}`;
|
||||
|
||||
// 7) Tarif
|
||||
const tarif = produit?.["@expand"]?.tarif || null;
|
||||
const blocTarif = buildTarifBlock({
|
||||
tarifRef: tarif?.tarifRef,
|
||||
ht: produit.cotAnnuelleHT ?? null,
|
||||
ttc: produit.cotAnnuelleTTC ?? null
|
||||
});
|
||||
|
||||
const gauche = [
|
||||
kv("Chiffre d'affaires", caStr),
|
||||
kv("Type de cotisation", typeCotStr),
|
||||
kv("Activité assurée", activiteAssuree),
|
||||
kv("Garanties", garantiesStr),
|
||||
].join("");
|
||||
|
||||
const droite = [
|
||||
kv("Zones", zonesStr),
|
||||
kv("Dates", datesStr),
|
||||
blocTarif,
|
||||
].join("");
|
||||
|
||||
return gridWrap2cols(gauche, droite);
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
let activateTimeout = null;
|
||||
let timeoutWarning = null;
|
||||
let currentPageLoad = null;
|
||||
let safetyTimeout = null; // Timeout de sécurité pour éviter que le loader reste bloqué
|
||||
|
||||
/**
|
||||
* Active le loader et le rend visible
|
||||
* Affiche un message d'annulation après 8 secondes
|
||||
*/
|
||||
window.showLoader = function() {
|
||||
const loader = document.getElementById("loader-overlay");
|
||||
if (!loader) return; // Protection : si l'élément n'existe pas, on ne fait rien
|
||||
|
||||
const timeoutMessage = document.getElementById("timeout-message");
|
||||
const errorMessage = document.getElementById("error-message");
|
||||
|
||||
// Nettoyer les timeouts précédents
|
||||
clearTimeout(activateTimeout);
|
||||
clearTimeout(timeoutWarning);
|
||||
|
||||
// Réinitialiser l'affichage
|
||||
if (errorMessage) errorMessage.style.display = "none";
|
||||
if (timeoutMessage) timeoutMessage.style.display = "none";
|
||||
loader.classList.remove("hidden");
|
||||
|
||||
// Afficher le loader après 500ms (évite le flash sur les chargements rapides)
|
||||
activateTimeout = setTimeout(() => {
|
||||
if (loader && !loader.classList.contains("hidden")) {
|
||||
loader.classList.add("active");
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// Afficher le message de timeout après 8 secondes
|
||||
timeoutWarning = setTimeout(() => {
|
||||
if (loader && loader.classList.contains("active") && !loader.classList.contains("hidden")) {
|
||||
if (timeoutMessage) {
|
||||
timeoutMessage.style.display = "block";
|
||||
}
|
||||
}
|
||||
}, 8000);
|
||||
|
||||
// Protection : cacher automatiquement le loader après 30 secondes maximum
|
||||
// Au cas où hideLoader ne serait jamais appelé (page qui ne charge pas, erreur, etc.)
|
||||
if (safetyTimeout) clearTimeout(safetyTimeout);
|
||||
safetyTimeout = setTimeout(() => {
|
||||
if (loader && !loader.classList.contains("hidden")) {
|
||||
hideLoader();
|
||||
}
|
||||
}, 30000); // 30 secondes max
|
||||
};
|
||||
|
||||
/**
|
||||
* Désactive et cache le loader
|
||||
*/
|
||||
window.hideLoader = function() {
|
||||
const loader = document.getElementById("loader-overlay");
|
||||
if (!loader) return; // Protection : si l'élément n'existe pas, on ne fait rien
|
||||
|
||||
const timeoutMessage = document.getElementById("timeout-message");
|
||||
|
||||
clearTimeout(activateTimeout);
|
||||
clearTimeout(timeoutWarning);
|
||||
if (safetyTimeout) {
|
||||
clearTimeout(safetyTimeout);
|
||||
safetyTimeout = null;
|
||||
}
|
||||
|
||||
loader.classList.remove("active");
|
||||
if (timeoutMessage) timeoutMessage.style.display = "none";
|
||||
|
||||
setTimeout(() => {
|
||||
if (loader) {
|
||||
loader.classList.add("hidden");
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
/**
|
||||
* Affiche un message d'erreur dans le loader
|
||||
* @param {string} msg - Message d'erreur à afficher
|
||||
*/
|
||||
window.showError = function(msg) {
|
||||
const errorMessage = document.getElementById("error-message");
|
||||
const timeoutMessage = document.getElementById("timeout-message");
|
||||
|
||||
if (!errorMessage) return; // Protection : si l'élément n'existe pas, on ne fait rien
|
||||
|
||||
clearTimeout(activateTimeout);
|
||||
clearTimeout(timeoutWarning);
|
||||
|
||||
if (timeoutMessage) timeoutMessage.style.display = "none";
|
||||
|
||||
errorMessage.textContent = msg;
|
||||
errorMessage.style.display = "block";
|
||||
};
|
||||
|
||||
/**
|
||||
* Annule le chargement en cours et cache le loader
|
||||
*/
|
||||
const cancelLoadingLink = document.getElementById("cancel-loading-link");
|
||||
if (cancelLoadingLink) {
|
||||
cancelLoadingLink.addEventListener("click", () => {
|
||||
// Annuler le chargement de la page en cours si possible
|
||||
if (currentPageLoad && currentPageLoad.abort) {
|
||||
currentPageLoad.abort();
|
||||
}
|
||||
|
||||
hideLoader();
|
||||
|
||||
// Afficher une notification discrète
|
||||
if (typeof M !== 'undefined' && M.toast) {
|
||||
M.toast({
|
||||
html: 'Chargement annulé',
|
||||
classes: 'rounded grey darken-3',
|
||||
displayLength: 2500
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Permet de stocker l'abort controller du chargement en cours
|
||||
* @param {AbortController} controller - Controller de la requête fetch
|
||||
*/
|
||||
window.setCurrentPageLoad = function(controller) {
|
||||
currentPageLoad = controller;
|
||||
};
|
||||
});
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/**
|
||||
* Affiche le loader lors des navigations entre pages (localhost)
|
||||
* Comme on est en localhost, on peut se permettre d'afficher le loader
|
||||
* au clic sur un lien, il disparaîtra automatiquement au chargement de la nouvelle page
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
// Intercepter les clics sur les liens internes pour afficher le loader
|
||||
document.body.addEventListener("click", (e) => {
|
||||
const link = e.target.closest("a");
|
||||
|
||||
// Si c'est un lien interne (commence par /) et pas un lien spécial
|
||||
if (link &&
|
||||
link.getAttribute("href") &&
|
||||
link.getAttribute("href").startsWith("/") &&
|
||||
!link.classList.contains("modal-close") &&
|
||||
!link.getAttribute("href").startsWith("/#")) {
|
||||
|
||||
// Afficher le loader avant la navigation
|
||||
if (typeof showLoader === 'function') {
|
||||
showLoader();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Afficher le loader lors du rechargement (F5, Cmd+R)
|
||||
window.addEventListener("beforeunload", () => {
|
||||
if (typeof showLoader === 'function') {
|
||||
showLoader();
|
||||
}
|
||||
});
|
||||
|
||||
// Cacher le loader quand la page est complètement chargée
|
||||
window.addEventListener("load", () => {
|
||||
if (typeof hideLoader === 'function') {
|
||||
// Petit délai pour éviter le flash
|
||||
setTimeout(hideLoader, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1,647 +0,0 @@
|
|||
/**
|
||||
* ═══════════════════════════════════════════════════════════════════════════
|
||||
* 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);
|
||||
|
|
@ -1,388 +0,0 @@
|
|||
/**
|
||||
* ═══════════════════════════════════════════════════════════════════════════
|
||||
* 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);
|
||||
|
|
@ -1,464 +0,0 @@
|
|||
/**
|
||||
* ═══════════════════════════════════════════════════════════════════════════
|
||||
* 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 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");
|
||||
*/
|
||||
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);
|
||||
|
|
@ -1,625 +0,0 @@
|
|||
/**
|
||||
* Utilitaires de synchro RC (collecte + pré-remplissage Tarif/Projet).
|
||||
*/
|
||||
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
const { toNumber, getValue, setValue, getElementByIdFlexible } = window.RCSync;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// MAPPING DES CHAMPS TARIF ↔ PROJET
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Mapping des champs Tarif <-> Projet.
|
||||
* Format: { idTarif: idProjet }.
|
||||
*/
|
||||
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
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Lit l'ensemble des champs utiles depuis le formulaire tarif.
|
||||
*/
|
||||
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')),
|
||||
// Pas de champ montant dans le formulaire actuel.
|
||||
montant3ans: 0
|
||||
},
|
||||
|
||||
// ═══ 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('Donnees tarif collectees:', data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les activités complémentaires cochées pour un type d'activité.
|
||||
*/
|
||||
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('Pre-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(' Vehicules:', 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(' Activites:', 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 activee');
|
||||
|
||||
// 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 activee');
|
||||
|
||||
// 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 confie');
|
||||
}
|
||||
}
|
||||
|
||||
// ═══ SINISTRALITÉ ═══
|
||||
|
||||
if (tarifData.sinistralite) {
|
||||
if (tarifData.sinistralite.nombre3ans) {
|
||||
setValue('nbSinistres3ans', tarifData.sinistralite.nombre3ans);
|
||||
}
|
||||
if (tarifData.sinistralite.montant3ans) {
|
||||
setValue('montantSinistres3ans', tarifData.sinistralite.montant3ans);
|
||||
}
|
||||
console.log(' Sinistralite');
|
||||
}
|
||||
|
||||
// ═══ 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(' Resultats 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('Pre-remplissage Projet termine');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du pre-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('Pre-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('Pre-remplissage Tarif termine');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du pre-remplissage Tarif:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// EXPORT PUBLIC
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
window.RCDataManager = {
|
||||
collectAllTarifData,
|
||||
prefillProjetFromTarif,
|
||||
prefillTarifFromProjet,
|
||||
FIELD_MAPPING
|
||||
};
|
||||
|
||||
console.log('RC Data Manager loaded');
|
||||
|
||||
})(window);
|
||||
|
|
@ -1,377 +0,0 @@
|
|||
/**
|
||||
* Orchestrateur de synchro RC entre les écrans Tarif et Projet.
|
||||
*/
|
||||
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
// Attendre que les dépendances soient chargées
|
||||
if (!window.RCSync || !window.RCDataManager) {
|
||||
console.error('Dependances RC Sync manquantes');
|
||||
return;
|
||||
}
|
||||
|
||||
const { isFieldImpactingTarif, showReturnToTarifModal } = window.RCSync;
|
||||
const { collectAllTarifData, 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 si on est sur la page tarif ou projet.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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('Donnees tarif sauvegardees en session');
|
||||
} catch (error) {
|
||||
console.error('Erreur sauvegarde session:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// HOOK: APRÈS VALIDATION TARIF
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Hook appelé après validation tarif pour garder l'état en session.
|
||||
*/
|
||||
function onTarifValidated() {
|
||||
console.log('Hook tarif valide: collecte des donnees');
|
||||
|
||||
try {
|
||||
// Collecter toutes les données du tarif
|
||||
const tarifData = collectAllTarifData();
|
||||
|
||||
// Sauvegarder en session pour le pré-remplissage projet
|
||||
saveTarifDataToSession(tarifData);
|
||||
|
||||
console.log('Donnees tarif pretes pour le projet');
|
||||
} catch (error) {
|
||||
console.error('Erreur hook tarif valide:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// INITIALISATION PAGE PROJET
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Initialisation côté projet.
|
||||
* Le pré-remplissage reste géré dans projet-form-rc.js.
|
||||
*/
|
||||
function initProjetPage() {
|
||||
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
|
||||
// On configure juste la détection des changements
|
||||
|
||||
setTimeout(() => {
|
||||
setupProjetChangeDetection();
|
||||
}, 1000); // Attendre que prefillFromTarif() ait fini
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
// 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 detection');
|
||||
return;
|
||||
}
|
||||
|
||||
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 = [
|
||||
// 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 detecte: ${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 activites:', selectedValues);
|
||||
console.warn('Activites 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 geographiques impactent le tarif');
|
||||
showReturnToTarifModal('Zones géographiques');
|
||||
});
|
||||
});
|
||||
|
||||
console.log('Detection des changements configuree');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// 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('Pre-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 de 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 intercepte');
|
||||
}
|
||||
}, 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: demarrage');
|
||||
|
||||
const activePage = detectActivePage();
|
||||
console.log(`Page active detectee: ${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);
|
||||
|
|
@ -1,290 +0,0 @@
|
|||
/**
|
||||
* Fonctions utilitaires partagées entre les écrans RC Tarif et Projet.
|
||||
*/
|
||||
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// CONSTANTES
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* 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
|
||||
'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 texte/nombre en nombre JS.
|
||||
* Accepte les formats FR/EN (espaces, virgules, points).
|
||||
*/
|
||||
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 un élément par id, avec fallback querySelector.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lit la valeur d'un champ de formulaire selon son type.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ecrit une valeur dans un champ de formulaire selon son type.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// DÉTECTION DE CHANGEMENTS IMPACTANTS
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Dit si un nom de champ fait partie des champs qui influencent le tarif.
|
||||
*/
|
||||
function isFieldImpactingTarif(fieldName) {
|
||||
return TARIF_IMPACTING_FIELDS.some(field =>
|
||||
fieldName.includes(field) || field.includes(fieldName)
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// MODAL DE RETOUR AU TARIF
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Ouvre le modal "modif tarif" déjà présent dans la vue projet.
|
||||
*/
|
||||
function showReturnToTarifModal(fieldName) {
|
||||
const modal = document.getElementById('modalModif');
|
||||
if (!modal) {
|
||||
console.warn('Modal "modalModif" introuvable');
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldName) {
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
||||
if (window.M && window.M.Modal) {
|
||||
const instance = window.M.Modal.getInstance(modal) || window.M.Modal.init(modal);
|
||||
instance.open();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirige vers l'onglet Tarif depuis le Projet.
|
||||
*/
|
||||
function navigateToTarif() {
|
||||
// Fermer le modal
|
||||
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) {
|
||||
window.location.href = `/navParcours?numParcours=${numParcours}&submenu=tarif`;
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// EXPORT PUBLIC
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* API publique du module RC Sync.
|
||||
*/
|
||||
window.RCSync = {
|
||||
// Helpers
|
||||
toNumber,
|
||||
getValue,
|
||||
setValue,
|
||||
getElementByIdFlexible,
|
||||
|
||||
// Détection changements
|
||||
isFieldImpactingTarif,
|
||||
|
||||
// Modal
|
||||
showReturnToTarifModal,
|
||||
navigateToTarif,
|
||||
|
||||
// Constantes
|
||||
TARIF_IMPACTING_FIELDS
|
||||
};
|
||||
|
||||
console.log('RC Sync Utils loaded');
|
||||
|
||||
})(window);
|
||||
|
|
@ -1,645 +0,0 @@
|
|||
(function () {
|
||||
/**
|
||||
* Normalise numeric input.
|
||||
*/
|
||||
function normalizeNumericInput(raw) {
|
||||
if (raw == null) return '';
|
||||
return String(raw).trim().replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse loose number.
|
||||
*/
|
||||
function parseLooseNumber(raw) {
|
||||
const normalized = normalizeNumericInput(raw).replace(',', '.');
|
||||
if (!normalized) return NaN;
|
||||
const parsed = Number(normalized);
|
||||
return Number.isFinite(parsed) ? parsed : NaN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate french amount.
|
||||
*/
|
||||
function formatFrenchAmount(value, digits) {
|
||||
const number = Number(value);
|
||||
if (!Number.isFinite(number)) return '0.00';
|
||||
return number.toLocaleString('fr-FR', {
|
||||
minimumFractionDigits: digits,
|
||||
maximumFractionDigits: digits
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronise floating labels.
|
||||
*/
|
||||
function syncFloatingLabels(root) {
|
||||
const scope = root && root.querySelectorAll ? root : document;
|
||||
const labels = scope.querySelectorAll('.rc-field-label[for]');
|
||||
labels.forEach(function (label) {
|
||||
const fieldId = label.getAttribute('for');
|
||||
if (!fieldId) return;
|
||||
const field = document.getElementById(fieldId);
|
||||
if (!field) return;
|
||||
|
||||
const inputField = label.closest('.input-field');
|
||||
const container = inputField || label.parentElement;
|
||||
if (container) {
|
||||
container.classList.add('rc-has-floating-label');
|
||||
}
|
||||
|
||||
label.classList.add('rc-floating-label');
|
||||
if (label.dataset.rcFloatBound === 'true') {
|
||||
// Déjà lié: on force juste un resync visuel.
|
||||
const hasValue = String(field.value || '').trim() !== '';
|
||||
label.classList.toggle('active', hasValue || document.activeElement === field);
|
||||
return;
|
||||
}
|
||||
|
||||
const hasPrefix = Boolean(inputField && inputField.querySelector('.prefix'));
|
||||
label.style.left = hasPrefix ? '3rem' : '0.75rem';
|
||||
|
||||
const syncState = function () {
|
||||
const hasValue = String(field.value || '').trim() !== '';
|
||||
label.classList.toggle('active', hasValue || document.activeElement === field);
|
||||
};
|
||||
|
||||
field.addEventListener('focus', syncState);
|
||||
field.addEventListener('blur', syncState);
|
||||
field.addEventListener('input', syncState);
|
||||
field.addEventListener('change', syncState);
|
||||
label.dataset.rcFloatBound = 'true';
|
||||
syncState();
|
||||
});
|
||||
|
||||
if (window.M && typeof window.M.updateTextFields === 'function') {
|
||||
window.M.updateTextFields();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Securise error slot.
|
||||
*/
|
||||
function ensureErrorSlot(field, customErrorId) {
|
||||
if (!field) return null;
|
||||
|
||||
if (customErrorId) {
|
||||
const found = document.getElementById(customErrorId);
|
||||
if (found) return found;
|
||||
}
|
||||
|
||||
if (field.id) {
|
||||
const byConvention = document.getElementById(field.id + '-error');
|
||||
if (byConvention) return byConvention;
|
||||
}
|
||||
|
||||
if (field.dataset && field.dataset.errorTarget) {
|
||||
const byData = document.getElementById(field.dataset.errorTarget);
|
||||
if (byData) return byData;
|
||||
}
|
||||
|
||||
let sibling = field.parentElement ? field.parentElement.querySelector('.rc-inline-error') : null;
|
||||
if (sibling) return sibling;
|
||||
|
||||
sibling = document.createElement('span');
|
||||
sibling.className = 'helper-text red-text rc-inline-error';
|
||||
sibling.style.display = 'none';
|
||||
field.insertAdjacentElement('afterend', sibling);
|
||||
return sibling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere pick field label.
|
||||
*/
|
||||
function pickFieldLabel(field, fallbackLabel) {
|
||||
if (fallbackLabel) return fallbackLabel;
|
||||
if (!field) return 'Champ';
|
||||
|
||||
if (field.dataset && field.dataset.rcLabel) return field.dataset.rcLabel;
|
||||
|
||||
const directLabel = field.id ? document.querySelector('label[for="' + field.id + '"]') : null;
|
||||
if (directLabel && directLabel.textContent.trim()) return directLabel.textContent.trim();
|
||||
|
||||
const cardLabel = field.closest('.row, .input-field, td, .card-content')?.querySelector('.rc-field-label');
|
||||
if (cardLabel && cardLabel.textContent.trim()) return cardLabel.textContent.trim();
|
||||
|
||||
const th = field.closest('td')?.parentElement?.querySelector('th');
|
||||
if (th && th.textContent.trim()) return th.textContent.trim();
|
||||
|
||||
return field.name || field.id || 'Champ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Met a jour field error.
|
||||
*/
|
||||
function setFieldError(field, errorSlot, message) {
|
||||
if (!errorSlot) return;
|
||||
errorSlot.textContent = message || '';
|
||||
errorSlot.style.display = message ? 'block' : 'none';
|
||||
|
||||
if (field) {
|
||||
if (message) {
|
||||
field.classList.add('invalid');
|
||||
} else {
|
||||
field.classList.remove('invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifie forbidden numeric chars.
|
||||
*/
|
||||
function hasForbiddenNumericChars(value) {
|
||||
return /[A-Za-z€$£¥]/.test(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere validate numeric.
|
||||
*/
|
||||
function validateNumeric(value, options) {
|
||||
const raw = normalizeNumericInput(value);
|
||||
const decimals = Number.isInteger(options.decimals) ? options.decimals : 2;
|
||||
const label = options.label || 'Champ';
|
||||
const required = Boolean(options.required);
|
||||
|
||||
if (!raw) {
|
||||
if (required) {
|
||||
return { valid: false, message: label + ' est obligatoire.' };
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
if (hasForbiddenNumericChars(raw)) {
|
||||
return { valid: false, message: label + ' doit contenir uniquement des chiffres, avec virgule ou point décimal.' };
|
||||
}
|
||||
|
||||
if (/[^0-9.,-]/.test(raw)) {
|
||||
return { valid: false, message: label + ' contient des caractères interdits.' };
|
||||
}
|
||||
|
||||
if ((raw.match(/,/g) || []).length > 1 || (raw.match(/\./g) || []).length > 1) {
|
||||
return { valid: false, message: label + ' a un format invalide.' };
|
||||
}
|
||||
|
||||
if (raw.includes('-') && raw.indexOf('-') !== 0) {
|
||||
return { valid: false, message: label + ' a un format invalide.' };
|
||||
}
|
||||
|
||||
const decimalRegex = new RegExp('^-?\\d+(?:[.,]\\d{1,' + decimals + '})?$');
|
||||
if (!decimalRegex.test(raw)) {
|
||||
return { valid: false, message: label + ' doit être un nombre valide (max ' + decimals + ' décimales).' };
|
||||
}
|
||||
|
||||
const number = parseLooseNumber(raw);
|
||||
if (!Number.isFinite(number)) {
|
||||
return { valid: false, message: label + ' doit être un nombre valide.' };
|
||||
}
|
||||
|
||||
if (typeof options.min === 'number' && number < options.min) {
|
||||
return { valid: false, message: label + ' doit être supérieur ou égal à ' + options.min + '.' };
|
||||
}
|
||||
|
||||
if (typeof options.max === 'number' && number > options.max) {
|
||||
return { valid: false, message: label + ' doit être inférieur ou égal à ' + options.max + '.' };
|
||||
}
|
||||
|
||||
if (options.positive && number <= 0) {
|
||||
return { valid: false, message: label + ' doit être strictement supérieur à 0.' };
|
||||
}
|
||||
|
||||
return { valid: true, normalized: raw.replace(',', '.') };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere validate integer.
|
||||
*/
|
||||
function validateInteger(value, options) {
|
||||
const raw = normalizeNumericInput(value);
|
||||
const label = options.label || 'Champ';
|
||||
const required = Boolean(options.required);
|
||||
|
||||
if (!raw) {
|
||||
if (required) {
|
||||
return { valid: false, message: label + ' est obligatoire.' };
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(raw)) {
|
||||
return { valid: false, message: label + ' doit contenir uniquement des chiffres entiers.' };
|
||||
}
|
||||
|
||||
const number = Number(raw);
|
||||
if (typeof options.min === 'number' && number < options.min) {
|
||||
return { valid: false, message: label + ' doit être supérieur ou égal à ' + options.min + '.' };
|
||||
}
|
||||
if (typeof options.max === 'number' && number > options.max) {
|
||||
return { valid: false, message: label + ' doit être inférieur ou égal à ' + options.max + '.' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere validate immat.
|
||||
*/
|
||||
function validateImmat(value, options) {
|
||||
const raw = String(value || '').trim().toUpperCase();
|
||||
const label = options.label || 'Immatriculation';
|
||||
|
||||
if (!raw) {
|
||||
if (options.required) {
|
||||
return { valid: false, message: label + ' est obligatoire.' };
|
||||
}
|
||||
return { valid: true, normalized: '' };
|
||||
}
|
||||
|
||||
if (!/^[A-Z0-9-]+$/.test(raw)) {
|
||||
return { valid: false, message: label + ' doit contenir uniquement des lettres majuscules, chiffres et tirets.' };
|
||||
}
|
||||
|
||||
return { valid: true, normalized: raw };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere validate text safe.
|
||||
*/
|
||||
function validateTextSafe(value, options) {
|
||||
const raw = String(value || '').trim();
|
||||
const label = options.label || 'Champ';
|
||||
|
||||
if (!raw) {
|
||||
if (options.required) {
|
||||
return { valid: false, message: label + ' est obligatoire.' };
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
if (/[<>;&"]/g.test(raw)) {
|
||||
return { valid: false, message: label + ' contient des caractères interdits (<, >, &, ;, ").' };
|
||||
}
|
||||
|
||||
const pattern = options.pattern || /^[A-Za-zÀ-ÖØ-öø-ÿ0-9\s'.,:/()\-_]+$/;
|
||||
if (!pattern.test(raw)) {
|
||||
return { valid: false, message: label + ' contient des caractères non autorisés.' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere validate number or consult.
|
||||
*/
|
||||
function validateNumberOrConsult(raw, options) {
|
||||
const value = String(raw || '').trim();
|
||||
const lower = value.toLowerCase();
|
||||
|
||||
if (!value) {
|
||||
if (options.required) {
|
||||
return { valid: false, message: (options.label || 'Champ') + ' est obligatoire.' };
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
if (lower === 'nous consulter') {
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
return validateNumeric(value, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere evaluate rule.
|
||||
*/
|
||||
function evaluateRule(field, config) {
|
||||
const profile = config.profile;
|
||||
const label = pickFieldLabel(field, config.label);
|
||||
const required = typeof config.requiredWhen === 'function' ? Boolean(config.requiredWhen(field)) : Boolean(config.required);
|
||||
|
||||
let result;
|
||||
switch (profile) {
|
||||
case 'numeric':
|
||||
result = validateNumeric(field.value, {
|
||||
label: label,
|
||||
required: required,
|
||||
decimals: config.decimals,
|
||||
min: config.min,
|
||||
max: config.max,
|
||||
positive: config.positive
|
||||
});
|
||||
break;
|
||||
case 'integer':
|
||||
result = validateInteger(field.value, {
|
||||
label: label,
|
||||
required: required,
|
||||
min: config.min,
|
||||
max: config.max
|
||||
});
|
||||
break;
|
||||
case 'immat':
|
||||
result = validateImmat(field.value, {
|
||||
label: label,
|
||||
required: required
|
||||
});
|
||||
break;
|
||||
case 'text':
|
||||
result = validateTextSafe(field.value, {
|
||||
label: label,
|
||||
required: required,
|
||||
pattern: config.pattern
|
||||
});
|
||||
break;
|
||||
case 'number_or_consulter':
|
||||
result = validateNumberOrConsult(field.value, {
|
||||
label: label,
|
||||
required: required,
|
||||
decimals: config.decimals,
|
||||
min: config.min,
|
||||
max: config.max,
|
||||
positive: config.positive
|
||||
});
|
||||
break;
|
||||
default:
|
||||
result = { valid: true };
|
||||
}
|
||||
|
||||
if (result.normalized != null && config.normalize !== false && field.value !== result.normalized) {
|
||||
field.value = result.normalized;
|
||||
}
|
||||
|
||||
return {
|
||||
valid: result.valid,
|
||||
message: result.message || '',
|
||||
label: label
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere create summary element.
|
||||
*/
|
||||
function createSummaryElement(summaryId) {
|
||||
if (!summaryId) return null;
|
||||
return document.getElementById(summaryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere create guard.
|
||||
*/
|
||||
function createGuard(options) {
|
||||
const summary = createSummaryElement(options.summaryId);
|
||||
const summaryTitle = String(options.summaryTitle || 'Impossible de continuer car :');
|
||||
const state = {
|
||||
fieldRules: new Map(),
|
||||
fieldErrors: new Map(),
|
||||
externalChecks: new Map(),
|
||||
targetButtons: [],
|
||||
onChange: typeof options.onChange === 'function' ? options.onChange : null
|
||||
};
|
||||
|
||||
const targetSelectors = Array.isArray(options.blockTargets) ? options.blockTargets : [];
|
||||
targetSelectors.forEach(function (selector) {
|
||||
document.querySelectorAll(selector).forEach(function (button) {
|
||||
if (!button.dataset.rcOriginalDisabled) {
|
||||
button.dataset.rcOriginalDisabled = button.disabled ? 'true' : 'false';
|
||||
}
|
||||
state.targetButtons.push(button);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Gere apply summary.
|
||||
*/
|
||||
function applySummary(messages) {
|
||||
if (!summary) return;
|
||||
if (!messages.length) {
|
||||
summary.style.display = 'none';
|
||||
summary.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const uniqueMessages = Array.from(new Set(messages));
|
||||
const list = uniqueMessages.map(function (msg) {
|
||||
return '<li>' + msg + '</li>';
|
||||
}).join('');
|
||||
|
||||
summary.innerHTML = [
|
||||
'<div class="rc-blocking-title">' + summaryTitle + '</div>',
|
||||
'<ul class="rc-blocking-list">',
|
||||
list,
|
||||
'</ul>'
|
||||
].join('');
|
||||
summary.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere apply blocking.
|
||||
*/
|
||||
function applyBlocking(hasErrors) {
|
||||
state.targetButtons.forEach(function (button) {
|
||||
if (!button) return;
|
||||
if (hasErrors) {
|
||||
button.disabled = true;
|
||||
button.dataset.rcInvalid = 'true';
|
||||
return;
|
||||
}
|
||||
|
||||
if (button.dataset.rcInvalid === 'true') {
|
||||
button.dataset.rcInvalid = 'false';
|
||||
button.disabled = button.dataset.rcOriginalDisabled === 'true';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere recompute.
|
||||
*/
|
||||
function recompute() {
|
||||
const messages = [];
|
||||
|
||||
state.fieldErrors.forEach(function (error) {
|
||||
if (error && error.message) {
|
||||
messages.push(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
state.externalChecks.forEach(function (entry, key) {
|
||||
try {
|
||||
const checkResult = typeof entry.fn === 'function' ? entry.fn() : { valid: true };
|
||||
if (!checkResult || checkResult.valid === false) {
|
||||
const message = checkResult && checkResult.message ? checkResult.message : entry.message || key;
|
||||
messages.push(message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[RC guard] Check externe en erreur:', key, error);
|
||||
messages.push(entry.message || ('Validation en erreur: ' + key));
|
||||
}
|
||||
});
|
||||
|
||||
applySummary(messages);
|
||||
applyBlocking(messages.length > 0);
|
||||
|
||||
if (state.onChange) {
|
||||
state.onChange(messages);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere validate field.
|
||||
*/
|
||||
function validateField(field) {
|
||||
try {
|
||||
const config = state.fieldRules.get(field);
|
||||
if (!config) return;
|
||||
|
||||
if (!field || !field.isConnected || field.disabled) {
|
||||
if (config.errorSlot) setFieldError(field, config.errorSlot, '');
|
||||
state.fieldErrors.delete(config.key);
|
||||
recompute();
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof config.activeWhen === 'function' && !config.activeWhen(field)) {
|
||||
setFieldError(field, config.errorSlot, '');
|
||||
state.fieldErrors.delete(config.key);
|
||||
recompute();
|
||||
return;
|
||||
}
|
||||
|
||||
// Sécurité défaut: sans activeWhen explicite, on ignore un champ masqué.
|
||||
if (typeof config.activeWhen !== 'function' && field.type !== 'hidden' && field.offsetParent === null) {
|
||||
setFieldError(field, config.errorSlot, '');
|
||||
state.fieldErrors.delete(config.key);
|
||||
recompute();
|
||||
return;
|
||||
}
|
||||
|
||||
const result = evaluateRule(field, config);
|
||||
setFieldError(field, config.errorSlot, result.valid ? '' : result.message);
|
||||
|
||||
if (result.valid) {
|
||||
state.fieldErrors.delete(config.key);
|
||||
} else {
|
||||
state.fieldErrors.set(config.key, {
|
||||
message: result.message
|
||||
});
|
||||
}
|
||||
|
||||
recompute();
|
||||
} catch (error) {
|
||||
const config = state.fieldRules.get(field);
|
||||
const key = config && config.key ? config.key : 'champ-inconnu';
|
||||
console.warn('[RC guard] Validation en erreur:', key, error);
|
||||
if (config && config.errorSlot) {
|
||||
setFieldError(field, config.errorSlot, 'Erreur de validation, rechargez la page.');
|
||||
}
|
||||
state.fieldErrors.set(key, {
|
||||
message: 'Erreur de validation sur un champ du formulaire.'
|
||||
});
|
||||
recompute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere attach field.
|
||||
*/
|
||||
function attachField(field, config) {
|
||||
if (!field) return;
|
||||
|
||||
const key = config.key || (field.id ? field.id : (field.name ? field.name : ('field_' + state.fieldRules.size)));
|
||||
if (field.dataset && field.dataset.rcGuardAttached === 'true' && state.fieldRules.has(field)) return;
|
||||
|
||||
const fieldConfig = Object.assign({}, config, {
|
||||
key: key,
|
||||
errorSlot: ensureErrorSlot(field, config.errorId)
|
||||
});
|
||||
|
||||
state.fieldRules.set(field, fieldConfig);
|
||||
if (field.dataset) {
|
||||
field.dataset.rcGuardAttached = 'true';
|
||||
}
|
||||
|
||||
const handler = function () {
|
||||
validateField(field);
|
||||
};
|
||||
|
||||
field.addEventListener('input', handler);
|
||||
field.addEventListener('change', handler);
|
||||
field.addEventListener('blur', handler);
|
||||
|
||||
validateField(field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre field.
|
||||
*/
|
||||
function registerField(selectorOrElement, config) {
|
||||
if (!selectorOrElement || !config || !config.profile) return;
|
||||
if (typeof selectorOrElement === 'string') {
|
||||
document.querySelectorAll(selectorOrElement).forEach(function (element) {
|
||||
attachField(element, config);
|
||||
});
|
||||
return;
|
||||
}
|
||||
attachField(selectorOrElement, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere observe.
|
||||
*/
|
||||
function observe(selector, config) {
|
||||
if (!selector || !config || !config.profile) return;
|
||||
|
||||
registerField(selector, config);
|
||||
|
||||
const observer = new MutationObserver(function () {
|
||||
registerField(selector, config);
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
return observer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enregistre external.
|
||||
*/
|
||||
function registerExternal(key, fn, fallbackMessage) {
|
||||
if (!key || typeof fn !== 'function') return;
|
||||
state.externalChecks.set(key, {
|
||||
fn: fn,
|
||||
message: fallbackMessage || ''
|
||||
});
|
||||
recompute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gere refresh.
|
||||
*/
|
||||
function refresh() {
|
||||
state.fieldRules.forEach(function (_cfg, field) {
|
||||
validateField(field);
|
||||
});
|
||||
return recompute();
|
||||
}
|
||||
|
||||
return {
|
||||
registerField: registerField,
|
||||
observe: observe,
|
||||
registerExternal: registerExternal,
|
||||
refresh: refresh,
|
||||
formatFrenchAmount: formatFrenchAmount,
|
||||
parseLooseNumber: parseLooseNumber
|
||||
};
|
||||
}
|
||||
|
||||
window.RCValidationUtils = {
|
||||
createGuard: createGuard,
|
||||
parseLooseNumber: parseLooseNumber,
|
||||
formatFrenchAmount: formatFrenchAmount,
|
||||
syncFloatingLabels: syncFloatingLabels
|
||||
};
|
||||
})();
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
function isValidEmail(email) {
|
||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailPattern.test(email);
|
||||
}
|
||||
|
||||
function validateField(fieldId, showErrors = true) {
|
||||
try {
|
||||
const inputElement = document.getElementById(fieldId);
|
||||
|
||||
if (!inputElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const rawValue = inputElement.value;
|
||||
const value = typeof rawValue === 'string' ? rawValue.trim() : String(rawValue ?? '').trim();
|
||||
const hasValidationRules = typeof validationRules !== 'undefined' && validationRules;
|
||||
const rule = hasValidationRules ? validationRules[fieldId] : null; // Importé de js/json/json-verif-form.js
|
||||
const errorElement = document.getElementById(`${fieldId}-error`);
|
||||
|
||||
if (!rule) {
|
||||
if (showErrors && errorElement) {
|
||||
errorElement.textContent = "";
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
let isValid = true;
|
||||
let errorMessage = "";
|
||||
|
||||
// Vérifie si le champ est requis et vide
|
||||
if (rule.required && (!value || value == '')) {
|
||||
errorMessage = "Ce champ est obligatoire.";
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Vérifie la longueur de la saisie si spécifié et nécessaire
|
||||
if (isValid && value && rule.length && value.length !== rule.length) {
|
||||
errorMessage = `Veuillez saisir ${rule.length} caractères, il y en a actuellement ${value.length}.`;
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Vérifie si la saisie est un nombre sans décimale ou avec un maximum de deux chiffres après la virgule
|
||||
if (isValid && value && rule.numberFormat) {
|
||||
const isNumberFormatValid = /^(?:\d+|\d+\.\d{1,2})$/.test(value);
|
||||
|
||||
if (!isNumberFormatValid) {
|
||||
errorMessage = "Le champ doit être nombre de deux décimal maximum '00.00' ou entier '00'";
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifie si la saisie est un taux sans décimale ou avec un maximum de trois chiffres après la virgule
|
||||
if (isValid && value && rule.tauxFormat) {
|
||||
const isTauxFormatValid = /^(?:\d+|\d+\.\d{1,3})$/.test(value);
|
||||
|
||||
if (!isTauxFormatValid) {
|
||||
errorMessage = "Le champ doit être un nombre avec un maximum de trois décimales '00.000' ou entier '00'";
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifie si le champ doit être compris dans un certain intervalle
|
||||
if (isValid && value && rule.range) {
|
||||
const numberValue = parseFloat(value);
|
||||
|
||||
if (isNaN(numberValue) || numberValue < rule.range.min || numberValue > rule.range.max) {
|
||||
errorMessage = rule.errorMsg;
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Valide l'adresse e-mail seulement si le champ n'est pas vide
|
||||
if (isValid && value && rule.email) {
|
||||
if (!isValidEmail(value)) {
|
||||
errorMessage = rule.errorMsg;
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifie la présence de caractères potentiellement problématiques
|
||||
const specialCharsPattern = /[;">&<]/g;
|
||||
let invalidChars = value.match(specialCharsPattern);
|
||||
|
||||
if (isValid && value && invalidChars) {
|
||||
invalidChars = [...new Set(invalidChars)];
|
||||
errorMessage = `Caractère(s) invalide(s) détecté(s) [ ${invalidChars.join(" ")} ].`;
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Vérifie si le champ commence par le caractère spécifié
|
||||
if (isValid && value && rule.startsWith && !value.startsWith(rule.startsWith)) {
|
||||
errorMessage = `Le champ doit commencer par '${rule.startsWith}' majuscule.`;
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Vérifie si le champ date est correct
|
||||
if (isValid && value && rule.dateFormat) {
|
||||
const isDateFormatValid = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])\/\d{4}$|^00\/00\/0000$/.test(value);
|
||||
|
||||
if (!isDateFormatValid) {
|
||||
errorMessage = "Le champ doit être au format JJ//MM/AAAA ou 00/00/0000.";
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifie si la saisie ne contient pas d'espace
|
||||
if (isValid && value && rule.noSpace) {
|
||||
const isNoSpaceValid = /^\S*$/.test(value);
|
||||
|
||||
if (!isNoSpaceValid) {
|
||||
errorMessage = "La saisie ne doit pas contenir d'espaces.";
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifie si le champ date est correct au format JJ/MM
|
||||
if (isValid && value && rule.dateFormatShort) {
|
||||
const isDateFormatShortValid = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])$|^00\/00$/.test(value);
|
||||
|
||||
if (!isDateFormatShortValid) {
|
||||
errorMessage = "Le champ doit être au format JJ/MM ou 00/00.";
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifie la casse des caractères si spécifié
|
||||
if (isValid && value && rule.case) {
|
||||
if (rule.case === "upper" && value !== value.toUpperCase()) {
|
||||
errorMessage = "Le champ doit être en majuscules.";
|
||||
isValid = false;
|
||||
} else if (rule.case === "lower" && value !== value.toLowerCase()) {
|
||||
errorMessage = "Le champ doit être en minuscules.";
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifie si le champ doit être numérique
|
||||
if (isValid && value && rule.digit) {
|
||||
const isNumeric = /^\d+$/.test(value); // Vérifie si la chaîne est un nombre entier
|
||||
|
||||
if (!isNumeric) {
|
||||
errorMessage = "Le champ doit contenir uniquement des chiffres.";
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Affiche ou cache le message d'erreur selon la validité du champ
|
||||
if (showErrors && errorElement) {
|
||||
errorElement.textContent = errorMessage;
|
||||
errorElement.style.display = isValid ? 'none' : 'block';
|
||||
}
|
||||
|
||||
return isValid;
|
||||
} catch (error) {
|
||||
console.warn('Validation interrompue pour le champ:', fieldId, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateSubmitButtonState(formId) {
|
||||
|
||||
let allFieldsValid = true;
|
||||
const form = document.querySelector(`#${formId}`);
|
||||
const hasValidationRules = typeof validationRules !== 'undefined' && validationRules;
|
||||
|
||||
if (!form) {
|
||||
console.error('Formulaire non trouvé:', formId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasValidationRules) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const fieldId in validationRules) {
|
||||
const inputElement = document.getElementById(fieldId);
|
||||
|
||||
if (inputElement && form.contains(inputElement)) {
|
||||
try {
|
||||
if (!validateField(fieldId, false)) {
|
||||
allFieldsValid = false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Erreur pendant la validation du champ:', fieldId, error);
|
||||
allFieldsValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const submitButtons = form.querySelectorAll('button[type="submit"], input[type="submit"]');
|
||||
|
||||
if (submitButtons && submitButtons.length > 0) {
|
||||
submitButtons.forEach((button) => {
|
||||
button.disabled = !allFieldsValid;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,908 +0,0 @@
|
|||
const objModNatureMar = {
|
||||
"Toute autre marchandise non listée": {cat: "1", taux: 0.0004},
|
||||
"Produits en bonbonnes": {cat: "2", taux: 0.0005},
|
||||
"Liquides en bouteilles": {cat: "2", taux: 0.0005},
|
||||
"Matériel Electrique: accesoires et appareillages électriques": {cat: "2", taux: 0.0005},
|
||||
"Instruments de mesure": {cat: "2", taux: 0.0005},
|
||||
"Objets en pierre, cristal, marbre, bois, ivoire, verre, porcelaine, céramique, terre-cuite, plâtre": {cat: "2", taux: 0.0005},
|
||||
"Verres à vitres, Miroirs, marbre, tuiles, ardoises, carrelages, poterie, céramiques": {cat: "2", taux: 0.0005},
|
||||
"Lampes et Luminaires": {cat: "2", taux: 0.0005},
|
||||
"Meubles de consommation, meubles artisanaux massifs": {cat: "2", taux: 0.0005},
|
||||
"Produits pharmaceutiques ou hospitaliers": {cat: "2", taux: 0.0005},
|
||||
"Optiques": {cat: "2", taux: 0.0005},
|
||||
"Matériels médicaux": {cat: "2", taux: 0.0005},
|
||||
"Petit et gros électroménager": {cat: "2", taux: 0.0005},
|
||||
"textiles et habillements, accessoires de mode": {cat: "2", taux: 0.0005},
|
||||
"Matériel et équipement de sports et loisirs (vélos et trotinettes yc électriques) gyropodes": {cat: "2", taux: 0.0005},
|
||||
"Parfumerie, cosmétique": {cat: "2", taux: 0.0005},
|
||||
"Maroquinerie": {cat: "2", taux: 0.0005},
|
||||
"Chaussures et articles chaussants": {cat: "2", taux: 0.0005},
|
||||
"Téléviseurs, HiFi, vidéo, appareils photos, matériels informatiques y compris accessoires, consommables": {cat: "2", taux: 0.0005},
|
||||
"Jeux et consoles vidéo": {cat: "2", taux: 0.0005},
|
||||
"Pneumatiques": {cat: "2", taux: 0.0005},
|
||||
"Horlogerie": {cat: "2", taux: 0.0005},
|
||||
"Bijouterie fantaisie (non précieux max 300€ par unité)": {cat: "2", taux: 0.0005},
|
||||
"Alimentation de luxe et épicerie fine": {cat: "2", taux: 0.0005},
|
||||
"Instruments de musique, y compris pièces de rechange et accessoires": {cat: "2", taux: 0.0005},
|
||||
"Outillage": {cat: "2", taux: 0.0005},
|
||||
"Appareils et équipements sanitaires (robinetterie, droguerie, fonte ou faience)": {cat: "2", taux: 0.0005},
|
||||
"Equipements Aéronautiques": {cat: "2", taux: 0.0005},
|
||||
"Machines outils, Matériel agricole, travaux publics (matériaux de construction), câbles, chaudronnerie": {cat: "3", taux: 0.0005},
|
||||
"Peintures et produits chimiques en fûts": {cat: "3", taux: 0.0005},
|
||||
"Matériel Electrique": {cat: "3", taux: 0.0005},
|
||||
"Moteurs, groupes électrogènes, transformateurs": {cat: "3", taux: 0.0005},
|
||||
"Pièces détachées ou gros outillage industriel": {cat: "3", taux: 0.0005},
|
||||
"Matériels ferroviaires": {cat: "3", taux: 0.0005},
|
||||
"Produits frais ; Plantes vivantes": {cat: "4", taux: 0.0007},
|
||||
"Produits congelés, réfrigérés, surgelés": {cat: "4", taux: 0.0007},
|
||||
"Produits alimentaires et alimentation animale": {cat: "4", taux: 0.0007},
|
||||
"Tous véhicules (hors matériel agricole) à moteurs: auto, deux roues, camion": {cat: "5", taux: 0.0012},
|
||||
"Machines tractées / remorquées (Transportées sur leurs propres essieux)": {cat: "5", taux: 0.0012},
|
||||
"Bijoux, pierres et métaux précieux": {cat: "6", taux: 0.005},
|
||||
"Tableaux, dessins, esquisses": {cat: "6", taux: 0.005},
|
||||
"Articles et vêtements de marque de luxe, fourrure naturelle": {cat: "6", taux: 0.005},
|
||||
"Supports papiers, magnétiques, électroniques ou optiques de transfert de fonds ou de paiement": {cat: "6", taux: 0.005},
|
||||
"Effets et bagages personnels dont ordinateurs et téléphones portables": {cat: "6", taux: 0.005},
|
||||
"Toute marchandise dont la valeur marchande est sans commune mesure avec leur valeur intrinsèque: objets d'art, sculpture peinture, antiquités, objets de curiosité ou de collection, documents, échantillons, prototypes": {cat: "6", taux: 0.005},
|
||||
"Orfévrerie, monnaies": {cat: "6", taux: 0.005},
|
||||
"Billets de banque, actions, obligations, coupons, timbres poste, titres et valeurs de toute espèce": {cat: "6", taux: 0.005},
|
||||
"Véhicule de collection": {cat: "6", taux: 0.005},
|
||||
"Déménagements privés": {cat: "7", taux: 0.008},
|
||||
"Déménagements d'entreprises": {cat: "8", taux: 0.5},
|
||||
"Animaux vivants": {cat: "8", taux: 0.5},
|
||||
"Métaux bruts": {cat: "8", taux: 0.5},
|
||||
"Combustibles": {cat: "8", taux: 0.5},
|
||||
"Matières premières": {cat: "8", taux: 0.5},
|
||||
"Marchandises en vrac": {cat: "8", taux: 0.5}
|
||||
}
|
||||
|
||||
const objModMontantCA = {
|
||||
"250000": {
|
||||
"5000": 0.84, "10000": 0.84, "25000": 0.84, "50000": 0.84,
|
||||
"100000": 0.893, "310000": 0.945, "500000": 1.05, "750000": 1.155, "1000000": 1.208
|
||||
},
|
||||
"500000": {
|
||||
"5000": 0.8, "10000": 0.8, "25000": 0.8, "50000": 0.8,
|
||||
"100000": 0.85, "310000": 0.9, "500000": 1, "750000": 1.1, "1000000": 1.15
|
||||
},
|
||||
"1000000": {
|
||||
"5000": 0.76, "10000": 0.76, "25000": 0.76, "50000": 0.76,
|
||||
"100000": 0.808, "310000": 0.855, "500000": 0.95, "750000": 1.045, "1000000": 1.093
|
||||
},
|
||||
"2000000": {
|
||||
"5000": 0.72, "10000": 0.72, "25000": 0.72, "50000": 0.72,
|
||||
"100000": 0.765, "310000": 0.81, "500000": 0.9, "750000": 0.99, "1000000": 1.035
|
||||
},
|
||||
"3000000": {
|
||||
"5000": 0.68, "10000": 0.68, "25000": 0.68, "50000": 0.68,
|
||||
"100000": 0.723, "310000": 0.765, "500000": 0.85, "750000": 0.935, "1000000": 0.978
|
||||
},
|
||||
"4000000": {
|
||||
"5000": 0.64, "10000": 0.64, "25000": 0.64, "50000": 0.64,
|
||||
"100000": 0.68, "310000": 0.72, "500000": 0.8, "750000": 0.88, "1000000": 0.92
|
||||
},
|
||||
"5000000": {
|
||||
"5000": 0.6, "10000": 0.6, "25000": 0.6, "50000": 0.6,
|
||||
"100000": 0.638, "310000": 0.675, "500000": 0.75, "750000": 0.825, "1000000": 0.863
|
||||
},
|
||||
"7500000": {
|
||||
"5000": 0.56, "10000": 0.56, "25000": 0.56, "50000": 0.56,
|
||||
"100000": 0.595, "310000": 0.63, "500000": 0.7, "750000": 0.77, "1000000": 0.805
|
||||
},
|
||||
"10000000": {
|
||||
"5000": 0.52, "10000": 0.52, "25000": 0.52, "50000": 0.52,
|
||||
"100000": 0.553, "310000": 0.585, "500000": 0.65, "750000": 0.715, "1000000": 0.748
|
||||
},
|
||||
"15000000": {
|
||||
"5000": 0.456, "10000": 0.456, "25000": 0.456, "50000": 0.456,
|
||||
"100000": 0.485, "310000": 0.513, "500000": 0.57, "750000": 0.627, "1000000": 0.656
|
||||
},
|
||||
"20000000": {
|
||||
"5000": 0.36, "10000": 0.36, "25000": 0.36, "50000": 0.36,
|
||||
"100000": 0.383, "310000": 0.405, "500000": 0.45, "750000": 0.495, "1000000": 0.518
|
||||
},
|
||||
"30000000": {
|
||||
"5000": 0.296, "10000": 0.296, "25000": 0.296, "50000": 0.296,
|
||||
"100000": 0.315, "310000": 0.333, "500000": 0.37, "750000": 0.407, "1000000": 0.426
|
||||
},
|
||||
"40000000": {
|
||||
"5000": 0.28, "10000": 0.28, "25000": 0.28, "50000": 0.28,
|
||||
"100000": 0.298, "310000": 0.315, "500000": 0.35, "750000": 0.385, "1000000": 0.403
|
||||
},
|
||||
"50000000": {
|
||||
"5000": 0.216, "10000": 0.216, "25000": 0.216, "50000": 0.216,
|
||||
"100000": 0.23, "310000": 0.243, "500000": 0.27, "750000": 0.297, "1000000": 0.311
|
||||
}
|
||||
};
|
||||
|
||||
// const objModFranchiseTousCas = {
|
||||
// 149: 1,
|
||||
// 300: 0.9,
|
||||
// 750: 0.85,
|
||||
// 1500: 0.80,
|
||||
// 3000: 0.75,
|
||||
// 5000: 0.7
|
||||
// };
|
||||
|
||||
const objModAct = {
|
||||
"Activite de pre-presse": 1,
|
||||
"Activites artistiques": 2.5,
|
||||
"Activites comptables": 1,
|
||||
"Activites d'architecture": 1,
|
||||
"Activites de banques de donnees": 1.86,
|
||||
"Activites de nettoyage": 1,
|
||||
"Activites des auxiliaires medicaux": 1,
|
||||
"Activites diverses liees au sport": 1,
|
||||
"Activites graphiques auxiliaires": 1,
|
||||
"Activites hospitalieres": 1,
|
||||
"Activites juridiques": 1,
|
||||
"Administration d'autres biens immobiliers": 1,
|
||||
"Administration d'entreprises": 1.86,
|
||||
"Administration d'immeubles residentiels": 1,
|
||||
"Administration publique generale": 1,
|
||||
"Affretement": 1,
|
||||
"Agencement de lieux de vente": 1,
|
||||
"Agences de presse": 1,
|
||||
"Agences de voyage": 1,
|
||||
"Agences immobilieres": 1,
|
||||
"Agences, conseil en publicite": 1,
|
||||
"Aide par le travail, ateliers proteges": 1,
|
||||
"Analyses, essais et inspections techniques": 1,
|
||||
"Appret et tannage des cuirs": 1,
|
||||
"Assemblage de cartes electroniques pour compte de": 1,
|
||||
"Assurance relevant du code de la mutualite": 1,
|
||||
"Autre hebergement touristique": 1,
|
||||
"Autre imprimerie (labeur)": 1,
|
||||
"Autres activites de courrier": 1,
|
||||
"Autres activites de realisation de logiciels": 1.86,
|
||||
"Autres activites d'edition": 1,
|
||||
"Autres activites manufacturieres n.c.a": 1,
|
||||
"Autres activites recreatives": 1,
|
||||
"Autres auxiliaires financiers": 1,
|
||||
"Autres commerces de detail en magasin non speciali": 1,
|
||||
"Autres commerces de gros de biens de consommation": 1,
|
||||
"Autres commerces de gros specialises": 1,
|
||||
"Autres enseignements": 1,
|
||||
"Autres formes d'action sociale": 1,
|
||||
"Autres intermediaires specialises du commerce": 1,
|
||||
"Autres services personnels": 1,
|
||||
"Autres transports routiers de voyageurs": 1,
|
||||
"Autres travaux d'installation": 1,
|
||||
"Autres travaux specialises de construction": 1.01,
|
||||
"Auxiliaires d'assurance": 1,
|
||||
"Bijouterie fantaisie": 1,
|
||||
"Bijouterie, joaillerie, orfevrerie": 2.5,
|
||||
"Biscotterie, biscuiterie, patisserie de conservati": 1.86,
|
||||
"Blanchisserie, teinturerie de detail": 1,
|
||||
"Boulangerie et boulangerie-patisserie": 1,
|
||||
"Brasserie": 1,
|
||||
"Captage, traitement et distribution d'eau": 1,
|
||||
"Centrales d'achats alimentaires": 1,
|
||||
"Centrales d'achats non alimentaires": 2.5,
|
||||
"Centres de collecte et banques d'organes": 1,
|
||||
"Champagnisation": 1,
|
||||
"Charcuterie": 1,
|
||||
"Chaudronnerie nucleaire": 1,
|
||||
"Chaudronnerie -tuyauterie": 1.99,
|
||||
"Chocolaterie, confiserie": 1,
|
||||
"Cidrerie": 1,
|
||||
"Commerce d'alimentation generale": 1,
|
||||
"Commerce de detail alimentaire sur eventaires et m": 1,
|
||||
"Commerce de detail d'appareils electromenagers et": 1.86,
|
||||
"Commerce de detail d'articles de sport de loisirs": 1,
|
||||
"Commerce de detail d'articles medicaux et orthoped": 1,
|
||||
"Commerce de détail de biens d'occasion": 1,
|
||||
"Commerce de detail de boissons": 1.01,
|
||||
"Commerce de detail de carburants": 1,
|
||||
"Commerce de detail de charbons et combustibles": 1,
|
||||
"Commerce de detail de fleurs": 1.99,
|
||||
"Commerce de detail de fruits et legumes": 1.01,
|
||||
"Commerce de detail de la chaussure": 1.86,
|
||||
"Commerce de detail de livres, journaux et papeteri": 1,
|
||||
"Commerce de detail de maroquinerie et articles de": 1,
|
||||
"Commerce de detail de meubles": 1,
|
||||
"Commerce de detail de parfumerie et de produits de": 1.86,
|
||||
"Commerce de detail de poissons, crustaces et mollu": 1,
|
||||
"Commerce de detail de produits pharmaceutiques": 1,
|
||||
"Commerce de detail de quincaillerie": 1,
|
||||
"Commerce de detail de viandes et produits a base d": 1,
|
||||
"Commerce de detail d'equipements automobiles": 1,
|
||||
"Commerce de detail d'equipements du foyer": 1.86,
|
||||
"Commerce de detail d'habillement": 1.86,
|
||||
"Commerce de detail d'horlogerie et de bijouterie": 1,
|
||||
"Commerce de detail divers en magasin specialise": 1,
|
||||
"Commerce de detail d'optique, et de photographie": 1,
|
||||
"Commerce de detail non alimentaire sur eventaires": 1,
|
||||
"Commerce de gros alimentaire non specialise": 1.01,
|
||||
"Commerce de gros d'animaux vivants": 1.86,
|
||||
"Commerce de gros d'appareils electromenagers et de": 1,
|
||||
"Commerce de gros d'autres machines et equipements": 1.86,
|
||||
"Commerce de gros d'autres produits intermediaires": 1,
|
||||
"Commerce de gros de bois et de produits derives": 1,
|
||||
"Commerce de gros de boissons": 1.86,
|
||||
"Commerce de gros de cafe, the, cacao et epices": 1,
|
||||
"Commerce de gros de cereales et aliments pour le b": 1.86,
|
||||
"Commerce de gros de combustibles": 1,
|
||||
"Commerce de gros de composants et d'autres equipem": 1,
|
||||
"Commerce de gros de cuirs et peaux": 1,
|
||||
"Commerce de gros de dechets et debris": 1,
|
||||
"Commerce de gros de fournitures et equipements div": 1,
|
||||
"Commerce de gros de fournitures et equipements ind": 1.99,
|
||||
"Commerce de gros de fournitures pour plomberie et": 1,
|
||||
"Commerce de gros de fruits et legumes": 1.01,
|
||||
"Commerce de gros de jouets": 1,
|
||||
"Commerce de gros de la chaussure": 1.86,
|
||||
"Commerce de gros de machines pour l'extraction, la": 1,
|
||||
"Commerce de gros de machines pour l'industrie text": 1,
|
||||
"Commerce de gros de machines-outils": 1.86,
|
||||
"Commerce de gros de materiaux de construction et a": 1,
|
||||
"Commerce de gros de materiel agricole": 1.99,
|
||||
"Commerce de gros de materiel electrique": 1.01,
|
||||
"Commerce de gros de minerais et metaux": 1,
|
||||
"Commerce de gros de papeterie": 1,
|
||||
"Commerce de gros de parfumerie et produits de beau": 1.86,
|
||||
"Commerce de gros de poissons, crustaces et mollusq": 1,
|
||||
"Commerce de gros de produits chimiques": 1.01,
|
||||
"Commerce de gros de produits laitiers, œufs, huile": 1,
|
||||
"Commerce de gros de produits pharmaceutiques": 1,
|
||||
"Commerce de gros de produits pour entretien et ame": 1,
|
||||
"Commerce de gros de produits surgeles": 1.86,
|
||||
"Commerce de gros de quincaillerie": 1.86,
|
||||
"Commerce de gros de sucre, chocolat et confiserie": 1,
|
||||
"Commerce de gros de textiles": 1.99,
|
||||
"Commerce de gros de vaisselle et verrerie de menag": 2.5,
|
||||
"Commerce de gros de viandes de boucherie": 1,
|
||||
"Commerce de gros d'equipements automobiles": 1.99,
|
||||
"Commerce de gros d'habillement": 1.86,
|
||||
"Commerce de gros d'ordinateurs, d'equipements info": 1.86,
|
||||
"Commerce de gros non specialise": 1,
|
||||
"Commerce de vehicules automobiles": 1,
|
||||
"Commerce et reparation de motocycles": 1,
|
||||
"Commerces de gros alimentaires specialises divers": 1.86,
|
||||
"Conditionnement a facon": 1,
|
||||
"Conseil en systemes informatiques": 1,
|
||||
"Conseil pour les affaires et la gestion": 1,
|
||||
"Construction de bateaux de plaisance": 1,
|
||||
"Construction de batiments divers": 1,
|
||||
"Construction de cellules d'aeronefs": 1,
|
||||
"Construction de chaussees routieres et de sols spo": 1,
|
||||
"Construction de maisons individuelles": 1,
|
||||
"Construction de navires civiles": 1,
|
||||
"Construction de vehicules automobiles": 1,
|
||||
"Creches et garderies d'enfants": 1,
|
||||
"Culture de cereales ; cultures industrielles": 2.5,
|
||||
"Culture de legumes ; maraichage": 1,
|
||||
"Culture et elevage associes": 1.01,
|
||||
"Culture fruitiere": 1.86,
|
||||
"Debits de boissons": 1,
|
||||
"Decolletage": 1,
|
||||
"Decoupage, emboutissage": 1,
|
||||
"Demenagement": 2.5,
|
||||
"Edition de chaines thematiques": 1,
|
||||
"edition de journaux": 1,
|
||||
"Edition de logiciels (non personnalises)": 1,
|
||||
"edition de revues et periodiques": 1,
|
||||
"edition d'enregistrements sonores": 1,
|
||||
"Elevage d'autres animaux": 1,
|
||||
"Elevage de bovins": 1,
|
||||
"Elevage de porcins": 1,
|
||||
"Elevage de volailles": 1,
|
||||
"Enlevement et traitement des ordures menageres": 1,
|
||||
"Ennoblissement textile": 1,
|
||||
"Enquetes et securite": 1,
|
||||
"Enseignement superieur": 1,
|
||||
"Entreposage frigorifique": 1,
|
||||
"Entreposage non frigorifique": 1,
|
||||
"Entretien d'espaces verts": 1,
|
||||
"Entretien et reparation de vehicules automobiles": 1,
|
||||
"Entretien, reparation machines de bureau et materi": 1.86,
|
||||
"epuration des eaux usees": 1,
|
||||
"etudes de marche et sondage": 1,
|
||||
"Exploitation de terrains de camping": 1,
|
||||
"Exploitation forestiere": 1,
|
||||
"Extraction de pierres pour la construction": 1,
|
||||
"Fabrication d'aliments adaptes a l'enfant et diete": 1,
|
||||
"Fabrication d'aliments pour animaux de ferme": 1,
|
||||
"Fabrication d'appareils de pesage": 1.86,
|
||||
"Fabrication d'appareils de reception, d'enregistre": 1.86,
|
||||
"Fabrication d'appareils de telephonie": 1.86,
|
||||
"Fabrication d'appareils d'eclairage": 1,
|
||||
"Fabrication d'appareils medicochirurgicaux": 1,
|
||||
"Fabrication d'articles ceramiques a usage domestiq": 1,
|
||||
"Fabrication d'articles chaussants a maille": 1,
|
||||
"Fabrication d'articles de papeterie": 1,
|
||||
"Fabrication d'articles de robinetterie": 1.01,
|
||||
"Fabrication d'articles de sport": 1.01,
|
||||
"Fabrication d'articles de voyage et de maroquineri": 1,
|
||||
"Fabrication d'articles divers en matieres plastiqu": 1,
|
||||
"Fabrication d'articles en films metalliques": 1,
|
||||
"Fabrication d'articles en papier a usage sanitaire": 1,
|
||||
"Fabrication d'articles metalliques divers": 1,
|
||||
"Fabrication d'articles metalliques menagers": 1,
|
||||
"Fabrication d'autres articles confectionnes en tex": 1.01,
|
||||
"Fabrication d'autres articles en caoutchouc": 1,
|
||||
"Fabrication d'autres articles en papier ou en cart": 1.86,
|
||||
"Fabrication d'autres machines d'usage general": 1.86,
|
||||
"Fabrication d'autres machines-outils": 1,
|
||||
"Fabrication d'autres produits chimiques inorganiqu": 1.86,
|
||||
"Fabrication d'autres produits chimiques organiques": 1,
|
||||
"Fabrication d'autres produits laitiers": 1.01,
|
||||
"Fabrication d'autres produits pharmaceutiques": 1,
|
||||
"Fabrication d'autres vetements et accessoires": 1,
|
||||
"Fabrication de bicyclettes": 1,
|
||||
"Fabrication de caravanes et vehicules de loisirs": 1,
|
||||
"Fabrication de cartonnages": 1,
|
||||
"Fabrication de charpentes et de menuiseries": 1.86,
|
||||
"Fabrication de chaussures": 1,
|
||||
"Fabrication de composants electroniques actifs": 1,
|
||||
"Fabrication de composants passifs et de condensate": 1.86,
|
||||
"Fabrication de compresseurs": 1,
|
||||
"Fabrication de condiments et assaisonnements": 1.86,
|
||||
"Fabrication de constructions metalliques": 1,
|
||||
"Fabrication de coutellerie": 1,
|
||||
"Fabrication de fibres artificielles ou synthetique": 1,
|
||||
"Fabrication de fils a coudre": 1,
|
||||
"Fabrication de fils et cables isoles": 1,
|
||||
"Fabrication de fours et brûleurs": 1,
|
||||
"Fabrication de glaces et sorbets": 1,
|
||||
"Fabrication de jeux et jouets": 1,
|
||||
"Fabrication de linge de maison et d'article d'ameu": 1,
|
||||
"Fabrication de lunettes": 1.01,
|
||||
"Fabrication de machines d'assemblage automatique": 1,
|
||||
"Fabrication de machines de bureau": 1,
|
||||
"Fabrication de machines d'imprimerie": 2.5,
|
||||
"Fabrication de machines pour les industriels du pa": 1,
|
||||
"Fabrication de machines pour les industries textil": 1,
|
||||
"Fabrication de machines pour l'industrie agroalime": 1.01,
|
||||
"Fabrication de machines pour travail du caoutchouc": 1.86,
|
||||
"Fabrication de machines-outils a bois": 1,
|
||||
"Fabrication de machines-outils a metaux": 1,
|
||||
"Fabrication de machines-outils portative a moteur": 1,
|
||||
"Fabrication de matelas": 1,
|
||||
"Fabrication de materiel agricole": 1,
|
||||
"Fabrication de materiel de sondage": 1,
|
||||
"Fabrication de materiel d'imagerie medicale et de": 1,
|
||||
"Fabrication de materiel electromagnetique industri": 1,
|
||||
"Fabrication de materiel pour les industries chimiq": 2.5,
|
||||
"Fabrication de materiels de distribution et de com": 1.99,
|
||||
"Fabrication de materiels de travaux publics": 1,
|
||||
"Fabrication de materiels electriques n.c.a": 1,
|
||||
"Fabrication de materiels electriques pour moteurs": 1,
|
||||
"Fabrication de medicaments": 1,
|
||||
"Fabrication de menuiseries et fermetures metalliqu": 1.86,
|
||||
"Fabrication de meubles de bureau et de magasin": 2.5,
|
||||
"Fabrication de meubles meublants": 1,
|
||||
"Fabrication de meubles n.c.a": 1.86,
|
||||
"Fabrication de moteurs, generatrices et transforma": 2.5,
|
||||
"Fabrication de motocycles": 1.01,
|
||||
"Fabrication de moules et modeles": 1,
|
||||
"Fabrication de non tisses": 1,
|
||||
"Fabrication de panneaux de bois": 1.99,
|
||||
"Fabrication de papier ou de carton": 1,
|
||||
"Fabrication de papiers peints": 1.86,
|
||||
"Fabrication de parfums et de produits pour la toil": 1,
|
||||
"Fabrication de pates alimentaires": 1,
|
||||
"Fabrication de peintures et de vernis": 1.01,
|
||||
"Fabrication de petits articles metalliques": 1,
|
||||
"Fabrication de petits articles textiles de literie": 1.01,
|
||||
"Fabrication de pieces techniques en matieres plast": 1.86,
|
||||
"Fabrication de plaques, feuilles, tubes et profile": 1,
|
||||
"Fabrication de pompes": 1,
|
||||
"Fabrication de produits agrochimiques": 1,
|
||||
"Fabrication de produits chimiques a usage industri": 2.5,
|
||||
"Fabrication de produits mineraux non metalliques n": 1,
|
||||
"Fabrication de produits pharmaceutiques de base": 1.99,
|
||||
"Fabrication de pull-overs et articles similaires": 2.5,
|
||||
"Fabrication de radiateurs et de chaudieres pour le": 1,
|
||||
"Fabrication de reservoirs, citernes et conteneurs": 1,
|
||||
"Fabrication de ressorts": 1.99,
|
||||
"Fabrication de roulements": 1,
|
||||
"Fabrication de serrures et ferrures": 1,
|
||||
"Fabrication de sieges": 1,
|
||||
"Fabrication de spiritueux": 1,
|
||||
"Fabrication de tapis et moquettes": 1,
|
||||
"Fabrication de transmissions hydrauliques et pneum": 1.01,
|
||||
"Fabrication de tubes en acier": 1,
|
||||
"Fabrication de verre plat": 1,
|
||||
"Fabrication de vetements de dessous": 2.5,
|
||||
"Fabrication de vetements de dessus pour femmes": 1.86,
|
||||
"Fabrication de vetements de dessus pour hommes": 1,
|
||||
"Fabrication de vetements de travail": 1,
|
||||
"Fabrication de vetements en cuir": 1.86,
|
||||
"Fabrication de vetements sur mesure": 1,
|
||||
"Fabrication d'elements en beton pour la constructi": 1,
|
||||
"Fabrication d'elements en matieres plastiques pour": 1,
|
||||
"Fabrication d'emballages en bois": 1.99,
|
||||
"Fabrication d'emballages en matieres plastiques": 1.86,
|
||||
"Fabrication d'emballages en papier": 1,
|
||||
"Fabrication d'emballages metalliques legers": 1,
|
||||
"Fabrication d'equipements aerauliques et frigorifi": 1,
|
||||
"Fabrication d'equipements automobiles": 1,
|
||||
"Fabrication d'equipements de controle des processu": 1,
|
||||
"Fabrication d'equipements de levage et de manutent": 1,
|
||||
"Fabrication d'equipements d'emballage et de condit": 1.01,
|
||||
"Fabrication d'equipements d'emission et de transmi": 1,
|
||||
"Fabrication d'etoffes a maille": 1,
|
||||
"Fabrication d'huiles essentielles": 1,
|
||||
"Fabrication d'instrumentation scientifique et tech": 1,
|
||||
"Fabrication d'instruments d'aide a la navigation": 1,
|
||||
"Fabrication d'instruments de musiques": 1,
|
||||
"Fabrication d'instruments d'optique et de materiel": 1,
|
||||
"Fabrication d'isolateurs en verre": 1.86,
|
||||
"Fabrication d'isolateurs et pieces isolantes en ce": 1,
|
||||
"Fabrication d'objets divers en bois": 1,
|
||||
"Fabrication d'objets en liège, vannerie ou sparter": 1,
|
||||
"Fabrication d'ordinateurs et d'autres equipements": 1,
|
||||
"Fabrication d'organes mecaniques de transmissions": 2.5,
|
||||
"Fabrication d'outillage a main": 1,
|
||||
"Fabrication d'outillage mecanique": 1,
|
||||
"Fabrication du verre creux": 1,
|
||||
"Fabrication et faconnage d'articles techniques en": 1,
|
||||
"Fabrication et transformation du verre plat": 2.5,
|
||||
"Fabrication industrielle de pain et de patisserie": 1.01,
|
||||
"Fabrication machines specialisees diverses": 1,
|
||||
"Ficellerie, corderie, fabrication de filets": 1,
|
||||
"Fonderie d'acier": 1,
|
||||
"Fonderie d'autres metaux non ferreux": 1,
|
||||
"Fonderie de fonte": 1,
|
||||
"Fonderie de metaux legers": 1,
|
||||
"Forages et sondages": 1,
|
||||
"Forge, estampage, matricage": 1,
|
||||
"Formation des adultes et formation continue": 1,
|
||||
"Gestion de portefeuilles": 2.5,
|
||||
"Gestion de salles de spectacle": 1,
|
||||
"Gestion de supports de publicite": 1,
|
||||
"Gestion d'installations sportives": 1,
|
||||
"Gestion du patrimoine culturel": 1,
|
||||
"Horlogerie": 1.86,
|
||||
"Horticulture ; pepinieres": 1,
|
||||
"Hotels avec restaurant": 1,
|
||||
"Hotels de tourisme sans restaurant": 1,
|
||||
"Industrie de la brosserie": 1,
|
||||
"Industrie du carton ondule": 1,
|
||||
"Industrie du poisson": 1,
|
||||
"Industries alimentaires n.c.a": 1.86,
|
||||
"Industries connexes de l'ameublement": 1,
|
||||
"Industries textiles n.c.a": 1,
|
||||
"Ingenierie, etudes techniques": 1,
|
||||
"Installation d'eau et de gaz": 2.5,
|
||||
"Enlevement et traitement des ordures menageres": 1,
|
||||
"Ennoblissement textile": 1,
|
||||
"Enquetes et securite": 1,
|
||||
"Enseignement superieur": 1,
|
||||
"Entreposage frigorifique": 1,
|
||||
"Entreposage non frigorifique": 1,
|
||||
"Entretien d'espaces verts": 1,
|
||||
"Entretien et reparation de vehicules automobiles": 1,
|
||||
"Entretien, reparation machines de bureau et materi": 1.86,
|
||||
"epuration des eaux usees": 1,
|
||||
"etudes de marche et sondage": 1,
|
||||
"Exploitation de terrains de camping": 1,
|
||||
"Exploitation forestiere": 1,
|
||||
"Extraction de pierres pour la construction": 1,
|
||||
"Fabrication d'aliments adaptes a l'enfant et diete": 1,
|
||||
"Fabrication d'aliments pour animaux de ferme": 1,
|
||||
"Fabrication d'appareils de pesage": 1.86,
|
||||
"Fabrication d'appareils de reception, d'enregistre": 1.86,
|
||||
"Fabrication d'appareils de telephonie": 1.86,
|
||||
"Fabrication d'appareils d'eclairage": 1,
|
||||
"Fabrication d'appareils medicochirurgicaux": 1,
|
||||
"Fabrication d'articles ceramiques a usage domestiq": 1,
|
||||
"Fabrication d'articles chaussants a maille": 1,
|
||||
"Fabrication d'articles de papeterie": 1,
|
||||
"Fabrication d'articles de robinetterie": 1.01,
|
||||
"Fabrication d'articles de sport": 1.01,
|
||||
"Fabrication d'articles de voyage et de maroquineri": 1,
|
||||
"Fabrication d'articles divers en matieres plastiqu": 1,
|
||||
"Fabrication d'articles en films metalliques": 1,
|
||||
"Fabrication d'articles en papier a usage sanitaire": 1,
|
||||
"Fabrication d'articles metalliques divers": 1,
|
||||
"Fabrication d'articles metalliques menagers": 1,
|
||||
"Fabrication d'autres articles confectionnes en tex": 1.01,
|
||||
"Fabrication d'autres articles en caoutchouc": 1,
|
||||
"Fabrication d'autres articles en papier ou en cart": 1.86,
|
||||
"Fabrication d'autres machines d'usage general": 1.86,
|
||||
"Fabrication d'autres machines-outils": 1,
|
||||
"Fabrication d'autres produits chimiques inorganiqu": 1.86,
|
||||
"Fabrication d'autres produits chimiques organiques": 1,
|
||||
"Fabrication d'autres produits laitiers": 1.01,
|
||||
"Fabrication d'autres produits pharmaceutiques": 1,
|
||||
"Fabrication d'autres vetements et accessoires": 1,
|
||||
"Fabrication de bicyclettes": 1,
|
||||
"Fabrication de caravanes et vehicules de loisirs": 1,
|
||||
"Fabrication de cartonnages": 1,
|
||||
"Fabrication de charpentes et de menuiseries": 1.86,
|
||||
"Fabrication de chaussures": 1,
|
||||
"Fabrication de composants electroniques actifs": 1,
|
||||
"Fabrication de composants passifs et de condensate": 1.86,
|
||||
"Fabrication de compresseurs": 1,
|
||||
"Fabrication de condiments et assaisonnements": 1.86,
|
||||
"Fabrication de constructions metalliques": 1,
|
||||
"Fabrication de coutellerie": 1,
|
||||
"Fabrication de fibres artificielles ou synthetique": 1,
|
||||
"Fabrication de fils a coudre": 1,
|
||||
"Fabrication de fils et cables isoles": 1,
|
||||
"Fabrication de fours et brûleurs": 1,
|
||||
"Fabrication de glaces et sorbets": 1,
|
||||
"Fabrication de jeux et jouets": 1,
|
||||
"Fabrication de linge de maison et d'article d'ameu": 1,
|
||||
"Fabrication de lunettes": 1.01,
|
||||
"Fabrication de machines d'assemblage automatique": 1,
|
||||
"Fabrication de machines de bureau": 1,
|
||||
"Fabrication de machines d'imprimerie": 2.5,
|
||||
"Fabrication de machines pour les industriels du pa": 1,
|
||||
"Fabrication de machines pour les industries textil": 1,
|
||||
"Fabrication de machines pour l'industrie agroalime": 1.01,
|
||||
"Fabrication de machines pour travail du caoutchouc": 1.86,
|
||||
"Fabrication de machines-outils a bois": 1,
|
||||
"Fabrication de machines-outils a metaux": 1,
|
||||
"Fabrication de machines-outils portative a moteur": 1,
|
||||
"Fabrication de matelas": 1,
|
||||
"Fabrication de materiel agricole": 1,
|
||||
"Fabrication de materiel de sondage": 1,
|
||||
"Fabrication de materiel d'imagerie medicale et de": 1,
|
||||
"Fabrication de materiel electromagnetique industri": 1,
|
||||
"Fabrication de materiel pour les industries chimiq": 2.5,
|
||||
"Fabrication de materiels de distribution et de com": 1.99,
|
||||
"Fabrication de materiels de travaux publics": 1,
|
||||
"Fabrication de materiels electriques n.c.a": 1,
|
||||
"Fabrication de materiels electriques pour moteurs": 1,
|
||||
"Fabrication de medicaments": 1,
|
||||
"Fabrication de menuiseries et fermetures metalliqu": 1.86,
|
||||
"Fabrication de meubles de bureau et de magasin": 2.5,
|
||||
"Fabrication de meubles meublants": 1,
|
||||
"Fabrication de meubles n.c.a": 1.86,
|
||||
"Fabrication de moteurs, generatrices et transforma": 2.5,
|
||||
"Fabrication de motocycles": 1.01,
|
||||
"Fabrication de moules et modeles": 1,
|
||||
"Fabrication de non tisses": 1,
|
||||
"Fabrication de panneaux de bois": 1.99,
|
||||
"Fabrication de papier ou de carton": 1,
|
||||
"Fabrication de papiers peints": 1.86,
|
||||
"Fabrication de parfums et de produits pour la toil": 1,
|
||||
"Fabrication de pates alimentaires": 1,
|
||||
"Fabrication de peintures et de vernis": 1.01,
|
||||
"Fabrication de petits articles metalliques": 1,
|
||||
"Fabrication de petits articles textiles de literie": 1.01,
|
||||
"Fabrication de pieces techniques en matieres plast": 1.86,
|
||||
"Fabrication de plaques, feuilles, tubes et profile": 1,
|
||||
"Fabrication de pompes": 1,
|
||||
"Fabrication de produits agrochimiques": 1,
|
||||
"Fabrication de produits chimiques a usage industri": 2.5,
|
||||
"Fabrication de produits mineraux non metalliques n": 1,
|
||||
"Fabrication de produits pharmaceutiques de base": 1.99,
|
||||
"Fabrication de pull-overs et articles similaires": 2.5,
|
||||
"Fabrication de radiateurs et de chaudieres pour le": 1,
|
||||
"Fabrication de reservoirs, citernes et conteneurs": 1,
|
||||
"Fabrication de ressorts": 1.99,
|
||||
"Fabrication de roulements": 1,
|
||||
"Fabrication de serrures et ferrures": 1,
|
||||
"Fabrication de sieges": 1,
|
||||
"Fabrication de spiritueux": 1,
|
||||
"Fabrication de tapis et moquettes": 1,
|
||||
"Fabrication de transmissions hydrauliques et pneum": 1.01,
|
||||
"Fabrication de tubes en acier": 1,
|
||||
"Fabrication de verre plat": 1,
|
||||
"Fabrication de vetements de dessous": 2.5,
|
||||
"Fabrication de vetements de dessus pour femmes": 1.86,
|
||||
"Fabrication de vetements de dessus pour hommes": 1,
|
||||
"Fabrication de vetements de travail": 1,
|
||||
"Fabrication de vetements en cuir": 1.86,
|
||||
"Fabrication de vetements sur mesure": 1,
|
||||
"Fabrication d'elements en beton pour la constructi": 1,
|
||||
"Fabrication d'elements en matieres plastiques pour": 1,
|
||||
"Fabrication d'emballages en bois": 1.99,
|
||||
"Fabrication d'emballages en matieres plastiques": 1.86,
|
||||
"Fabrication d'emballages en papier": 1,
|
||||
"Fabrication d'emballages metalliques legers": 1,
|
||||
"Fabrication d'equipements aerauliques et frigorifi": 1,
|
||||
"Fabrication d'equipements automobiles": 1,
|
||||
"Fabrication d'equipements de controle des processu": 1,
|
||||
"Fabrication d'equipements de levage et de manutent": 1,
|
||||
"Fabrication d'equipements d'emballage et de condit": 1.01,
|
||||
"Fabrication d'equipements d'emission et de transmi": 1,
|
||||
"Fabrication d'etoffes a maille": 1,
|
||||
"Fabrication d'huiles essentielles": 1,
|
||||
"Fabrication d'instrumentation scientifique et tech": 1,
|
||||
"Fabrication d'instruments d'aide a la navigation": 1,
|
||||
"Fabrication d'instruments de musiques": 1,
|
||||
"Fabrication d'instruments d'optique et de materiel": 1,
|
||||
"Fabrication d'isolateurs en verre": 1.86,
|
||||
"Fabrication d'isolateurs et pieces isolantes en ce": 1,
|
||||
"Fabrication d'objets divers en bois": 1,
|
||||
"Fabrication d'objets en liège, vannerie ou sparter": 1,
|
||||
"Fabrication d'ordinateurs et d'autres equipements": 1,
|
||||
"Fabrication d'organes mecaniques de transmissions": 2.5,
|
||||
"Fabrication d'outillage a main": 1,
|
||||
"Fabrication d'outillage mecanique": 1,
|
||||
"Fabrication du verre creux": 1,
|
||||
"Fabrication et faconnage d'articles techniques en": 1,
|
||||
"Fabrication et transformation du verre plat": 2.5,
|
||||
"Fabrication industrielle de pain et de patisserie": 1.01,
|
||||
"Fabrication machines specialisees diverses": 1,
|
||||
"Ficellerie, corderie, fabrication de filets": 1,
|
||||
"Fonderie d'acier": 1,
|
||||
"Fonderie d'autres metaux non ferreux": 1,
|
||||
"Fonderie de fonte": 1,
|
||||
"Fonderie de metaux legers": 1,
|
||||
"Forages et sondages": 1,
|
||||
"Forge, estampage, matricage": 1,
|
||||
"Formation des adultes et formation continue": 1,
|
||||
"Gestion de portefeuilles": 2.5,
|
||||
"Gestion de salles de spectacle": 1,
|
||||
"Gestion de supports de publicite": 1,
|
||||
"Gestion d'installations sportives": 1,
|
||||
"Gestion du patrimoine culturel": 1,
|
||||
"Horlogerie": 1.86,
|
||||
"Horticulture ; pepinieres": 1,
|
||||
"Hotels avec restaurant": 1,
|
||||
"Hotels de tourisme sans restaurant": 1,
|
||||
"Industrie de la brosserie": 1,
|
||||
"Industrie du carton ondule": 1,
|
||||
"Industrie du poisson": 1,
|
||||
"Industries alimentaires n.c.a": 1.86,
|
||||
"Industries connexes de l'ameublement": 1,
|
||||
"Industries textiles n.c.a": 1,
|
||||
"Ingenierie, etudes techniques": 1,
|
||||
"Installation d'eau et de gaz": 2.5,
|
||||
"Installation d'equipements thermiques et de climat": 2.5,
|
||||
"Intermediaires du commerce en bois et materiaux de": 1,
|
||||
"Intermediaires du commerce en combustibles, metaux": 1,
|
||||
"Intermediaires du commerce en machines, equipement": 1,
|
||||
"Intermediaires du commerce en matieres premieres a": 1,
|
||||
"Intermediaires du commerce en meubles, articles de": 1,
|
||||
"Intermediaires du commerce en produits alimentaire": 1,
|
||||
"Intermediaires du commerce en textiles, habillemen": 1,
|
||||
"Intermediaires non specialises du commerce": 1.86,
|
||||
"Laboratoires d'analyses medicales": 1,
|
||||
"Levage, montage": 1,
|
||||
"Location avec operateur de materiel de constructio": 1,
|
||||
"Location d'autres biens immobiliers": 1.86,
|
||||
"Location d'autres biens personnels et domestiques": 1,
|
||||
"Location d'autres materiels de transport terrestre": 1.01,
|
||||
"Location de camions avec conducteur": 1,
|
||||
"Location de logements": 1,
|
||||
"Location de longue duree de vehicules automobiles": 1,
|
||||
"Location de machines de bureau et de materiel info": 1,
|
||||
"Location de machines et equipements divers": 1,
|
||||
"Location de machines et equipements pour la constr": 2.5,
|
||||
"Location de materiels de transports aerien": 1,
|
||||
"Location de terrains": 1,
|
||||
"Maneges forains et parcs d'attractions": 1,
|
||||
"Manutention non portuaire": 1.86,
|
||||
"Marchand de biens immobiliers sans execution de tr": 1,
|
||||
"Mecanique generale": 1,
|
||||
"Menuiserie bois et matieres plastiques": 1,
|
||||
"Menuiserie metallique ; serrurerie": 1.01,
|
||||
"Messagerie, fret express": 1,
|
||||
"Metallurgie des autres metaux non ferreux": 1,
|
||||
"Metreurs, geometres": 1,
|
||||
"Meunerie": 1,
|
||||
"Moulinage et texturation de soie et textiles artif": 1.86,
|
||||
"Organisation de foires et salons": 1.01,
|
||||
"Organisation des transports internationaux": 2.5,
|
||||
"Organisations associatives n.c.a": 1,
|
||||
"Organisations patronales et consulaires": 1,
|
||||
"Organismes de placement en valeurs immobilieres": 1,
|
||||
"Patisserie": 1,
|
||||
"Peinture": 1,
|
||||
"Pisciculture, aquaculture": 1,
|
||||
"Platrerie": 2.5,
|
||||
"Pratique medicale": 1.01,
|
||||
"Premiere transformation de l'aluminium": 1.86,
|
||||
"Premiere transformation du plomb, du zinc ou de l'": 1,
|
||||
"Preparation de la laine": 1,
|
||||
"Preparation et filature du lin": 1,
|
||||
"Preparation industrielle de produits a base de via": 1,
|
||||
"Prestations techniques pour le cinema et la televi": 1,
|
||||
"Production de films institutionnels et publicitair": 1,
|
||||
"Production de films pour le cinema": 1,
|
||||
"Production de programmes de television": 1,
|
||||
"Production de viandes de boucherie": 1,
|
||||
"Production de viandes de volaille": 1.86,
|
||||
"Production d'eaux de vie naturelles": 1.86,
|
||||
"Production d'electricite": 1,
|
||||
"Production et distribution de chaleur": 1,
|
||||
"Projection de films cinematographiques": 1,
|
||||
"Promotion immobiliere de logements": 1,
|
||||
"Raffinage de petrole": 1,
|
||||
"Realisation de couvertures par elements": 1,
|
||||
"Realisation de logiciel": 1,
|
||||
"Realisation de reseaux": 1,
|
||||
"Recherche-developpement en sciences humaines et so": 1,
|
||||
"Recherche-developpement en sciences physiques et n": 1,
|
||||
"Recuperation de matieres metalliques recyclables": 1,
|
||||
"Recuperation de matieres non metalliques recyclabl": 1,
|
||||
"Reliure": 1.99,
|
||||
"Reparation d'articles personnels et domestiques n.": 1,
|
||||
"Reparation de materiel electrique": 1,
|
||||
"Reparation de materiel electronique grand public": 1,
|
||||
"Reparation de montres, horloges et bijoux": 1,
|
||||
"Reparation navale": 1,
|
||||
"Restaurant de type rapide": 1,
|
||||
"Restaurant de type traditionnel": 2.5,
|
||||
"Revetement des sols et des murs": 1,
|
||||
"Routage": 1,
|
||||
"Sciage et rabotage du bois": 1,
|
||||
"Secretariat et traduction": 1,
|
||||
"Services annexes a la production": 1,
|
||||
"Services annexes a l'elevage": 1,
|
||||
"Services annexes a l'extraction d'hydrocarbures": 1,
|
||||
"Services annexes aux spectacles": 1.86,
|
||||
"Services aux cultures productives": 1,
|
||||
"Services portuaires, maritimes et fluviaux": 1,
|
||||
"Siderurgie": 1,
|
||||
"Soins aux defunts": 1,
|
||||
"Studios et autres activites photographiques": 1,
|
||||
"Superettes": 1,
|
||||
"Supermarches": 1,
|
||||
"Supports juridiques de gestion de patrimoine": 1,
|
||||
"Telecommunications (hors transmissions audiovisuel": 1,
|
||||
"Terrassements divers, demolition": 1,
|
||||
"Terrassements en grande masse": 1,
|
||||
"Tissage de l'industrie cotonniere": 1,
|
||||
"Tissage de l'industrie lainiere-cycle carde": 1,
|
||||
"Tissage de soieries": 2.5,
|
||||
"Traitement de donnees": 1,
|
||||
"Traitement et revetement des metaux": 1,
|
||||
"Traitements des autres dechets solides": 1,
|
||||
"Transformation et conservation de fruits": 2.5,
|
||||
"Transformation et conservation de legumes": 1,
|
||||
"Transport de voyageurs par taxis": 1,
|
||||
"Transports aeriens non reguliers": 1,
|
||||
"Transports maritimes": 1,
|
||||
"Transports par conduites": 1,
|
||||
"Transports routiers de marchandises de proximite": 1,
|
||||
"Transports routiers de marchandises interurbains": 1.01,
|
||||
"Transports routiers reguliers de voyageurs": 1,
|
||||
"Transports urbains de voyageurs": 2.5,
|
||||
"Travail de la pierre": 1.01,
|
||||
"Travaux de charpente": 1,
|
||||
"Travaux de finition n.c.a": 1,
|
||||
"Travaux de maconnerie general": 1,
|
||||
"Travaux d'installation electrique": 1.99,
|
||||
"Travaux d'isolation": 1,
|
||||
"Tutelles des activites economiques": 1,
|
||||
"Vente a domicile": 2.5,
|
||||
"Vente par automate": 1,
|
||||
"Vente par correspondance specialisee": 2.5,
|
||||
"Vente par correspondance sur catalogue general": 1.01,
|
||||
"Vinification": 1,
|
||||
"Visserie et boulonnerie": 1,
|
||||
"Viticulture": 1,
|
||||
}
|
||||
|
||||
const objModCond = {
|
||||
usuel: {
|
||||
nom: "Cartons, Palettes, Conteneurs",
|
||||
modulo: 1,
|
||||
fluxDetailles: true,
|
||||
fluxGlobal: true
|
||||
},
|
||||
nu: {
|
||||
nom: "A nu",
|
||||
modulo: 1.2,
|
||||
fluxDetailles: true,
|
||||
fluxGlobal: false
|
||||
|
||||
},
|
||||
bennes: {
|
||||
nom: "Vrac en Benne ou Citerne",
|
||||
modulo: 1.2,
|
||||
fluxDetailles: true,
|
||||
fluxGlobal: false
|
||||
|
||||
},
|
||||
sacs: {
|
||||
nom: "En Sacs",
|
||||
modulo: 1.2,
|
||||
fluxDetailles: true,
|
||||
fluxGlobal: false
|
||||
|
||||
},
|
||||
conteneur: {
|
||||
nom: "En Conteneur sur Ligne Régulière",
|
||||
modulo: 1.2,
|
||||
fluxDetailles: false,
|
||||
fluxGlobal: true
|
||||
}
|
||||
}
|
||||
|
||||
const objModZoneTransp = {
|
||||
"zone1": {
|
||||
"terrestre": 1,
|
||||
"maritime": 1.25,
|
||||
"aerien": 0.5,
|
||||
"multimodal": 1.25
|
||||
},
|
||||
"zone2": {
|
||||
"terrestre": 1.1,
|
||||
"maritime": 1.43,
|
||||
"aerien": 0.5,
|
||||
"multimodal": 1.43
|
||||
},
|
||||
"zone3": {
|
||||
"terrestre": 1.1,
|
||||
"maritime": 1.43,
|
||||
"aerien": 0.5,
|
||||
"multimodal": 1.43
|
||||
},
|
||||
"zone4": {
|
||||
"terrestre": 1,
|
||||
"maritime": 1.75,
|
||||
"aerien": 0.5,
|
||||
"multimodal": 1.75
|
||||
},
|
||||
"zone5": {
|
||||
"terrestre": 1,
|
||||
"maritime": 2.06,
|
||||
"aerien": 0.5,
|
||||
"multimodal": 2.06
|
||||
},
|
||||
"zone6":
|
||||
{
|
||||
"terrestre": 1,
|
||||
"maritime": 2.5,
|
||||
"aerien": 0.75,
|
||||
"multimodal": 2.5
|
||||
}
|
||||
}
|
||||
|
||||
const objModTPPC = {
|
||||
// 15000: {modulo: 0, franchise: "150"},
|
||||
// 50000: {modulo: 0.05, franchise: "10% Mini 150€ et Maxi 300€"},
|
||||
// 100000: {modulo: 0.1, franchise: "10% Mini 150€ et Maxi 750€"},
|
||||
15000: {modulo: 0, franchise: 150},
|
||||
50000: {modulo: 0.05, franchise: 300},
|
||||
100000: {modulo: 0.1, franchise: 750}
|
||||
}
|
||||
|
||||
const objMarEnExpo =
|
||||
{
|
||||
"Marchandises Ordinaires": 0.2,
|
||||
"Marchandises à risques": 0.25,
|
||||
"Marchandises périssables": 0.35,
|
||||
"Matériels et Equipements lourds": 0.35,
|
||||
"Marchandises exclues sauf accord aux CP": 0.35,
|
||||
"Véhicules roulants": 0.35,
|
||||
}
|
||||
|
||||
const objModRG = {
|
||||
zone1: {
|
||||
etendue: 0.0005,
|
||||
waterborne: 0.00019
|
||||
},
|
||||
zone2: {
|
||||
etendue: 0.001,
|
||||
waterborne: 0.00032
|
||||
},
|
||||
zone3: {
|
||||
etendue: 0.0016,
|
||||
waterborne: 0.00048
|
||||
},
|
||||
zone4: {
|
||||
etendue: 0.002,
|
||||
waterborne: 0.0006
|
||||
},
|
||||
zone5: {
|
||||
etendue: 0.0032,
|
||||
waterborne: 0.001
|
||||
},
|
||||
zone6: {
|
||||
etendue: 0.008,
|
||||
waterborne: 0.0025
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
objModNatureMar,
|
||||
objModMontantCA,
|
||||
// objModFranchiseTousCas,
|
||||
objModAct,
|
||||
objModCond,
|
||||
objModZoneTransp,
|
||||
objModTPPC,
|
||||
objMarEnExpo,
|
||||
objModRG
|
||||
};
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const facService = require('../services/facService');
|
||||
const constantesJSON = require("../constantes/json-modulateur-fac");
|
||||
|
||||
|
||||
router.post('/create', async (req, res) => {
|
||||
const data = req.body;
|
||||
const fac = await facService.createFAC(data);
|
||||
|
||||
res.json({valid: Boolean(fac), fac});
|
||||
});
|
||||
|
||||
router.post('/createProjet', async (req, res) => {
|
||||
const data = req.body;
|
||||
const fac = await facService.createFacProjet(data);
|
||||
|
||||
res.json({valid: Boolean(fac), fac});
|
||||
});
|
||||
|
||||
router.post('/createTarif', async (req, res) => {
|
||||
const data = req.body;
|
||||
const fac = await facService.createFacTarif(data);
|
||||
|
||||
res.json({valid: Boolean(fac), fac});
|
||||
});
|
||||
|
||||
router.get("/read/id/:id", async (req, res) => {
|
||||
const id = req.params.id;
|
||||
|
||||
try {
|
||||
const fac = await facService.getFACbyId(id);
|
||||
res.json({valid: Boolean(fac), fac});
|
||||
} catch (error) {
|
||||
res.status(500).json({valid: false, error: "Internal Server Error"});
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/modulo/:objDemande", async (req, res) => {
|
||||
|
||||
const objDemande = req.params.objDemande;
|
||||
var objRetourne
|
||||
|
||||
switch (objDemande) {
|
||||
|
||||
case "natureMar":
|
||||
objRetourne = constantesJSON.objModNatureMar;
|
||||
break;
|
||||
|
||||
case "montantCA":
|
||||
objRetourne = constantesJSON.objModMontantCA;
|
||||
break;
|
||||
|
||||
case "franchiseTousCas":
|
||||
objRetourne = constantesJSON.objModFranchiseTousCas;
|
||||
break;
|
||||
|
||||
case "activite":
|
||||
objRetourne = constantesJSON.objModAct;
|
||||
break;
|
||||
|
||||
case "conditionnement":
|
||||
objRetourne = constantesJSON.objModCond;
|
||||
break;
|
||||
|
||||
case "zoneTransport":
|
||||
objRetourne = constantesJSON.objModZoneTransp;
|
||||
break;
|
||||
|
||||
case "tppc":
|
||||
objRetourne = constantesJSON.objModTPPC;
|
||||
break;
|
||||
|
||||
case "marEnExpo":
|
||||
objRetourne = constantesJSON.objMarEnExpo;
|
||||
break;
|
||||
|
||||
case "rg":
|
||||
objRetourne = constantesJSON.objModRG;
|
||||
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;
|
||||
|
|
@ -1,521 +0,0 @@
|
|||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const fs = require("fs");
|
||||
const PizZip = require("pizzip");
|
||||
const Docxtemplater = require("docxtemplater");
|
||||
const logger = require("../utils/logger");
|
||||
const path = require("path");
|
||||
const moment = require("moment");
|
||||
const parcoursService = require("../services/parcoursService");
|
||||
const contratService = require("../services/contratService");
|
||||
const globalService = require("../services/globalService");
|
||||
const userService = require("../services/userService");
|
||||
const constantesFAC = require("../constantes/json-modulateur-fac");
|
||||
|
||||
|
||||
require("moment/locale/fr");
|
||||
moment.locale("fr");
|
||||
|
||||
|
||||
|
||||
|
||||
router.post("/fac/projet/:numParcours", async (req, res) => {
|
||||
const content = fs.readFileSync(
|
||||
path.resolve("src/templates/template-projet-fac.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 fac = contrat?.["@expand"]?.enCours || null;
|
||||
const projet = fac?.["@expand"]?.projet || null;
|
||||
|
||||
|
||||
const moyenTransportList = ["terrestre", "maritime", "aerien", "postal", "fluvial"]
|
||||
const selectedTransportList = []
|
||||
|
||||
try {
|
||||
moyenTransportList.forEach((transport) => {
|
||||
if (projet[transport] !== "") {
|
||||
selectedTransportList.push(transport)
|
||||
}
|
||||
})
|
||||
} catch (error) {}
|
||||
|
||||
|
||||
var transportListVirguleEt
|
||||
|
||||
if (selectedTransportList.length > 1) {
|
||||
transportListVirguleEt = selectedTransportList
|
||||
.slice(0, selectedTransportList.length - 1)
|
||||
.join(', ')
|
||||
+ ' et ' + selectedTransportList[selectedTransportList.length - 1]
|
||||
} else {
|
||||
transportListVirguleEt = selectedTransportList.toString()
|
||||
}
|
||||
|
||||
|
||||
//au féminin
|
||||
const transportListOuEt = selectedTransportList.join(" et/ou ")
|
||||
.replace('fluvial', "fluviale")
|
||||
.replace('postal', 'postale')
|
||||
.replace('aerien', 'aerienne')
|
||||
|
||||
|
||||
const listAssAdd = [];
|
||||
|
||||
try {
|
||||
projet.assureAdditionnel.forEach((objet) => {
|
||||
listAssAdd.push(objet.nom + " - Adresse : " + objet.adresse + " - Siret : " + objet.siret);
|
||||
});
|
||||
} catch (error) {}
|
||||
|
||||
const risqueTransport = {
|
||||
"achat": "des contrats d'achat",
|
||||
"vente": "des contrats de vente",
|
||||
"sav": "des opérations de SAV",
|
||||
"demo": "des démonstrations",
|
||||
"transfert": "des transferts inter-usines"
|
||||
}
|
||||
|
||||
Object.keys(risqueTransport).forEach((risque) => {
|
||||
if (!projet.risqueTransport.includes(risque)) {
|
||||
delete risqueTransport[risque]
|
||||
}
|
||||
})
|
||||
|
||||
const listRisqueTransport = Object.keys(risqueTransport).map((key) => risqueTransport[key])
|
||||
|
||||
const mondeEntier = (fac.zones.includes('zone1') &&
|
||||
fac.zones.includes('zone2') &&
|
||||
fac.zones.includes('zone3') &&
|
||||
fac.zones.includes('zone4') &&
|
||||
fac.zones.includes('zone5') &&
|
||||
fac.zones.includes('zone6'))
|
||||
|
||||
const hasZone456 = (fac.zones.includes('zone4') &&
|
||||
fac.zones.includes('zone5') &&
|
||||
fac.zones.includes('zone6'))
|
||||
|
||||
const hasGarOptAuto = projet.garOpt.includes('auto')
|
||||
const hasGarOptEmballage = projet.garOpt.includes('emballage')
|
||||
const hasGarOptEtiquette = projet.garOpt.includes('etiquette')
|
||||
const hasGarOptTemperature = projet.garOpt.includes('temperature')
|
||||
const hasGarOptMarque = projet.garOpt.includes('marque')
|
||||
const hasTPPC = fac.tppc
|
||||
const hasMarchandiseExposition = fac.nbVehicExpo > 0
|
||||
|
||||
const condition4 = (hasGarOptAuto || hasGarOptEmballage || hasGarOptEtiquette || hasGarOptMarque || hasTPPC || hasMarchandiseExposition)
|
||||
const hasGarOpt = projet.garOpt.length > 0
|
||||
const condition2 = hasGarOpt || hasTPPC || hasMarchandiseExposition
|
||||
|
||||
const anneeProchaine = moment().add(1, 'years').format('YYYY')
|
||||
const dateFin = (projet.dateFin == "00/00" || projet.dateFin == "00/00/0000") ? "A PRECISER" : projet.dateFin
|
||||
const dateEffet = (projet.dateEffet == "00/00" || projet.dateEffet == "00/00/0000") ? "A PRECISER" : projet.dateEffet
|
||||
const dateEcheance = (projet.dateEcheance == "00/00" || projet.dateEcheance == "00/00/0000") ? "A PRECISER" : projet.dateEcheance + '/' + anneeProchaine
|
||||
|
||||
const renderObject = {
|
||||
nomClient: client.nom,
|
||||
adrClient: client.adresse,
|
||||
postalClient: client.codePostal,
|
||||
villeClient: client.ville,
|
||||
numClient: client.numClient,
|
||||
|
||||
nomInter: intermediaire.nom,
|
||||
adrInter: intermediaire.adresse,
|
||||
postalInter: intermediaire.codePostal,
|
||||
villeInter: intermediaire.ville,
|
||||
oriasInter: intermediaire.numOrias,
|
||||
hasOrias: intermediaire.numOrias == "" ? false : true,
|
||||
numInter: intermediaire.numTelephone,
|
||||
mailInter: intermediaire.mail,
|
||||
hasMail: intermediaire.mail ? true : false,
|
||||
numProjet: contrat.numSaisine ? contrat.numSaisine : contrat.numContrat,
|
||||
|
||||
hasCP: (contrat.type == "AN" || contrat.type == "TEMPORAIRE"),
|
||||
hasRemplacement: (contrat.type == "REMPLACEMENT"),
|
||||
hasRGAuto: (fac.rg == "auto"),
|
||||
hasRGDemande: (fac.rg == 'demande'),
|
||||
hasRG: (fac.rg == 'demande' || fac.rg == 'auto'),
|
||||
hasEtendue: (fac.typeRG == "etendue"),
|
||||
hasWaterborne: (fac.typeRG == "waterborne"),
|
||||
hasProgrammeInternational: projet.programmeInternational,
|
||||
|
||||
hasAssuresAdditionnels: listAssAdd.length > 0,
|
||||
listAssAdd: listAssAdd,
|
||||
hasTemporaire: contrat.type == "TEMPORAIRE",
|
||||
actAssure: fac.actAssure,
|
||||
typeMar: fac.typeMar,
|
||||
|
||||
listMoyenTransportEtOu: transportListOuEt,
|
||||
listMoyenTransportVirguleEt: transportListVirguleEt,
|
||||
|
||||
listRisqueTransport: listRisqueTransport,
|
||||
|
||||
depart: projet.lieuDepart,
|
||||
arrivee: projet.lieuArrivee,
|
||||
dateJour: moment().format("DD MMMM YYYY"),
|
||||
dateEffet: dateEffet,
|
||||
dateFin: dateFin,
|
||||
dateEcheance: dateEcheance,
|
||||
|
||||
hasMarchandiseExposition: hasMarchandiseExposition,
|
||||
hasMondeEntier: mondeEntier,
|
||||
hasZone1: fac.zones.includes('zone1'),
|
||||
hasZone2: fac.zones.includes('zone2'),
|
||||
hasZone3: fac.zones.includes('zone3'),
|
||||
hasZone4: fac.zones.includes('zone4'),
|
||||
hasZone5: fac.zones.includes('zone5'),
|
||||
hasZone6: fac.zones.includes('zone6'),
|
||||
hasZone456: hasZone456,
|
||||
hasTPPC: hasTPPC,
|
||||
hasTPPCTousRisques: projet.typeTPPC.includes('tousRisques'),
|
||||
hasTPPCFlotteND: projet.typeTPPC.includes('flotteND'),
|
||||
|
||||
capitalMax: projet.capitalMax,
|
||||
franchiseTransport: projet.franchiseTransport,
|
||||
condition4: condition4,
|
||||
hasGarOpt: hasGarOpt,
|
||||
condition2: condition2,
|
||||
hasGarOptAuto: hasGarOptAuto,
|
||||
hasGarOptEmballage: hasGarOptEmballage,
|
||||
hasGarOptEtiquette: hasGarOptEtiquette,
|
||||
hasGarOptMarque: hasGarOptMarque,
|
||||
hasGarOptTemperature: hasGarOptTemperature,
|
||||
capitalTPPC: fac.capitalTPPC,
|
||||
franchiseExpo: fac.franchiseExpo,
|
||||
hasCG: projet.valeurAssuree.includes('cg'),
|
||||
hasDerogation: projet.valeurAssuree.includes('derogation'),
|
||||
hasVaBasePrix: projet.valeurAssureeBase.includes('prix'),
|
||||
hasVaBaseAchat: projet.valeurAssureeBase.includes('achat'),
|
||||
hasVaBaseVente: projet.valeurAssureeBase.includes('vente'),
|
||||
|
||||
hasAerienTousRisques: fac.aerien.includes('tousRisques'),
|
||||
hasAerienEventMaj: fac.aerien.includes('eventMaj'),
|
||||
hasMaritimeTousRisques: fac.maritime.includes('tousRisques'),
|
||||
hasMaritimeEventMaj: fac.maritime.includes('eventMaj'),
|
||||
hasTerrestreTousRisques: fac.terrestre.includes('tousRisques'),
|
||||
hasTerrestreEventMaj: fac.terrestre.includes('eventMaj'),
|
||||
hasPostalTousRisques: fac.postal.includes('tousRisques'),
|
||||
hasPostalEventMaj: fac.postal.includes('eventMaj'),
|
||||
hasFluvialTousRisques: fac.fluvial.includes('tousRisques'),
|
||||
hasFluvialEventMaj: fac.fluvial.includes('eventMaj'),
|
||||
hasFranchiseTransport: projet.franchiseTransport !== "",
|
||||
|
||||
cotRO: globalService.customFormatNumber(fac.cotRO),
|
||||
cotRG: globalService.customFormatNumber(fac.cotRG),
|
||||
cotProvRO: globalService.customFormatNumber(fac.cotProvRO),
|
||||
cotProvRG: globalService.customFormatNumber(fac.cotProvRG),
|
||||
cotComptant: globalService.customFormatNumber(projet.cotComptant),
|
||||
tauxCotRO: globalService.customFormatNumber(fac.tauxCotRO, true),
|
||||
tauxCotRG: globalService.customFormatNumber(fac.tauxCotRG, true),
|
||||
cotAnnuelle: globalService.customFormatNumber(fac.primeHT),
|
||||
cotIrred: globalService.customFormatNumber(fac.primeMini),
|
||||
capitalTPPC: globalService.customFormatNumber(fac.capitalTPPC),
|
||||
capitalExpo: globalService.customFormatNumber(fac.capitalExpo),
|
||||
franchiseTPPC: globalService.customFormatNumber(fac.franchiseTPPC),
|
||||
chiffreAffaires: globalService.customFormatNumber(fac.ca),
|
||||
|
||||
hasMensuel: (projet.tempo == "mensuel"),
|
||||
hasChiffreAffaires: (projet.typeContrat == "chiffreAffaires"),
|
||||
hasAvisAliments: (projet.typeContrat == "avisAliments"),
|
||||
hasPartResultat: projet.participationResultat,
|
||||
hasAgentMutualiste: (intermediaire.type == "AGENT MUTUALISTE"),
|
||||
hasAgent: (intermediaire.type == "AGENT MUTUALISTE" || intermediaire.type == "AGENT NON MUTUALISTE"),
|
||||
hasCourtier: (intermediaire.type == "COURTIER"),
|
||||
tempo: projet.tempo,
|
||||
}
|
||||
|
||||
try {
|
||||
doc.render(renderObject)
|
||||
} 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");
|
||||
}
|
||||
|
||||
const buf = doc.getZip().generate({type: "nodebuffer"});
|
||||
const formattedDate = moment().format('DD-MM-YYYY-HH-mm-ss')
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
//generate declinaison tarifaire FAC
|
||||
router.post("/fac/tarif/:numParcours", async (req, res) => {
|
||||
// TODO Attention conditionner en fonction du type de CP
|
||||
|
||||
const content = fs.readFileSync(
|
||||
path.resolve("src/templates/template-declinaison-tarifaire-fac.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 fac = contrat?.["@expand"]?.enCours || {};
|
||||
const tarif = fac?.["@expand"]?.tarif || {};
|
||||
const user = await userService.getUserById(parcours.dernierUtilisateur);
|
||||
|
||||
function getSelectedFranchise(franchiseId) {
|
||||
switch (franchiseId) {
|
||||
case "sansFranchise":
|
||||
return tarif.sansFranchise
|
||||
case "franchise350":
|
||||
return tarif.franchise350
|
||||
case "franchise750":
|
||||
return tarif.franchise750
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedFranchiseTitre(franchiseId) {
|
||||
switch (franchiseId) {
|
||||
case "sansFranchise":
|
||||
return "Sans Franchise"
|
||||
case "franchise350":
|
||||
return "Franchise 350 €"
|
||||
case "franchise750":
|
||||
return "Franchise 750 €"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function initialeMaj(str) {
|
||||
return typeof str == "string" ? str.charAt(0).toUpperCase() + str.slice(1) : ""
|
||||
}
|
||||
|
||||
|
||||
const selectedFranchiseTitre = getSelectedFranchiseTitre(tarif.selectedFranchise)
|
||||
const selectedFranchise = getSelectedFranchise(tarif.selectedFranchise)
|
||||
|
||||
const transports = []
|
||||
if (fac.terrestre !== "") {
|
||||
transports.push("Terrestre")
|
||||
}
|
||||
if (fac.maritime !== "") {
|
||||
transports.push("Maritime")
|
||||
}
|
||||
if (fac.aerien !== "") {
|
||||
transports.push("Aerien")
|
||||
}
|
||||
if (fac.postal !== "") {
|
||||
transports.push("Postal")
|
||||
}
|
||||
if (fac.fluvial !== "") {
|
||||
transports.push("Fluvial")
|
||||
}
|
||||
if (fac.multimodal !== "") {
|
||||
transports.push('Multimodal')
|
||||
}
|
||||
|
||||
const listTransports = transports.join(', ')
|
||||
|
||||
const hasMondeEntier = (
|
||||
fac.zones.includes("zone1") &&
|
||||
fac.zones.includes("zone2") &&
|
||||
fac.zones.includes("zone3") &&
|
||||
fac.zones.includes("zone4") &&
|
||||
fac.zones.includes("zone5") &&
|
||||
fac.zones.includes("zone6")
|
||||
)
|
||||
|
||||
const typeRO = tarif.typeRO == "tousRisques" ? "Tous Risques" : "Evenements Majeurs"
|
||||
var typePolice
|
||||
switch (tarif.typePolice) {
|
||||
case "ca":
|
||||
typePolice = "Police au Chiffre d'Affaires";
|
||||
break;
|
||||
|
||||
case "national":
|
||||
typePolice = "Police au Voyage National";
|
||||
break;
|
||||
|
||||
case "international":
|
||||
typePolice = "Police au Voyage International";
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
doc.render({
|
||||
|
||||
matricule: user.matricule,
|
||||
hasContrat: contrat.numContrat || false,
|
||||
numContrat: contrat.numContrat,
|
||||
hasSaisine: contrat.numSaisine || false,
|
||||
numSaisine: contrat.numSaisine,
|
||||
nomClient: client.nom,
|
||||
|
||||
actAssuree: fac.actAssuree,
|
||||
montantSin: tarif.sinistres,
|
||||
franchiseSelected: selectedFranchiseTitre,
|
||||
typeMar: fac.typeMar,
|
||||
ca: fac.ca,
|
||||
|
||||
montantGarantir: tarif.montantGarantir,
|
||||
conditionnement: constantesFAC.objModCond?.[tarif.conditionnement]?.nom ?? "",
|
||||
typeMar: fac.typeMar,
|
||||
|
||||
transports: listTransports,
|
||||
hasMondeEntier: hasMondeEntier,
|
||||
hasZone1: fac.zones.includes("zone1"),
|
||||
hasZone2: fac.zones.includes("zone2"),
|
||||
hasZone3: fac.zones.includes("zone3"),
|
||||
hasZone4: fac.zones.includes("zone4"),
|
||||
hasZone5: fac.zones.includes("zone5"),
|
||||
hasZone6: fac.zones.includes("zone6"),
|
||||
|
||||
hasZone1Achats: tarif.fluxAchats?.zone == "zone1",
|
||||
hasZone2Achats: tarif.fluxAchats?.zone == "zone2",
|
||||
hasZone3Achats: tarif.fluxAchats?.zone == "zone3",
|
||||
hasZone4Achats: tarif.fluxAchats?.zone == "zone4",
|
||||
hasZone5Achats: tarif.fluxAchats?.zone == "zone5",
|
||||
hasZone6Achats: tarif.fluxAchats?.zone == "zone6",
|
||||
conditionnementAchats: constantesFAC.objModCond[tarif.fluxAchats?.conditionnement]?.nom ?? "",
|
||||
|
||||
hasZone1Ventes: tarif.fluxVentes?.zone == "zone1",
|
||||
hasZone2Ventes: tarif.fluxVentes?.zone == "zone2",
|
||||
hasZone3Ventes: tarif.fluxVentes?.zone == "zone3",
|
||||
hasZone4Ventes: tarif.fluxVentes?.zone == "zone4",
|
||||
hasZone5Ventes: tarif.fluxVentes?.zone == "zone5",
|
||||
hasZone6Ventes: tarif.fluxVentes?.zone == "zone6",
|
||||
conditionnementVentes: constantesFAC.objModCond[tarif.fluxVentes?.conditionnement]?.nom ?? "",
|
||||
|
||||
hasFluxGlobal: tarif.typeFlux == "global",
|
||||
hasFluxDetailles: tarif.typeFlux == "detailles",
|
||||
hasFluxVentes: (tarif.fluxVentes),
|
||||
hasFluxAchats: (tarif.fluxAchats),
|
||||
hasFluxIntersites: tarif.fluxIntersites,
|
||||
fluxAchats: tarif.fluxAchats,
|
||||
fluxVentes: tarif.fluxVentes,
|
||||
transportVentes: initialeMaj(tarif.fluxVentes?.transport),
|
||||
transportAchats: initialeMaj(tarif.fluxAchats?.transport),
|
||||
|
||||
|
||||
typeRO: typeRO,
|
||||
typePolice: typePolice,
|
||||
hasTPPC: fac.tppc,
|
||||
hasMarExpo: (fac.nbVehicExpo > 0),
|
||||
hasRG: fac.rg == "auto",
|
||||
hasRGAchats: (tarif.fluxAchats?.typeRG),
|
||||
hasRGVentes: (tarif.fluxVentes?.typeRG),
|
||||
|
||||
|
||||
tarif350: tarif.franchise350.proposition,
|
||||
// pourcentAct350: parseFloat(tarif.franchise350.pourcentAct) * 100,
|
||||
// pourcentCA350: parseFloat(tarif.franchise350.pourcentCA) * 100,
|
||||
// pourcentMar350: parseFloat(tarif.franchise350.pourcentMar) * 100,
|
||||
// pourcentFranchise350: parseFloat(tarif.franchise350.pourcentFranchise) * 100,
|
||||
tauxRO350: tarif.franchise350.tauxRO,
|
||||
tauxRG350: tarif.franchise350.tauxRG,
|
||||
franchiseTPPC350: tarif.franchise350.franchiseTPPC ?? "",
|
||||
franchiseExpo350: tarif.franchise350.franchiseExpo ?? "",
|
||||
|
||||
tarif750: tarif.franchise750.proposition,
|
||||
// pourcentAct750: parseFloat(tarif.franchise750.pourcentAct) * 100,
|
||||
// pourcentCA750: parseFloat(tarif.franchise750.pourcentCA) * 100,
|
||||
// pourcentMar750: parseFloat(tarif.franchise750.pourcentMar) * 100,
|
||||
// pourcentFranchise750: parseFloat(tarif.franchise750.pourcentFranchise) * 100,
|
||||
tauxRO750: tarif.franchise750.tauxRO,
|
||||
tauxRG750: tarif.franchise750.tauxRG,
|
||||
franchiseTPPC750: tarif.franchise750.franchiseTPPC ?? "",
|
||||
franchiseExpo750: tarif.franchise750.franchiseExpo ?? "",
|
||||
|
||||
tarifSansFranchise: tarif.sansFranchise.proposition,
|
||||
// pourcentActSansFranchise: parseFloat(tarif.sansFranchise.pourcentAct) * 100,
|
||||
// pourcentCASansFranchise: parseFloat(tarif.sansFranchise.pourcentCA) * 100,
|
||||
// pourcentMarSansFranchise: parseFloat(tarif.sansFranchise.pourcentMar) * 100,
|
||||
// pourcentFranchiseSansFranchise: parseFloat(tarif.sansFranchise.pourcentFranchise) * 100,
|
||||
franchiseTPPCSansFranchise: tarif.sansFranchise.franchiseTPPC ?? "",
|
||||
franchiseExpoSansFranchise: tarif.sansFranchise.franchiseExpo ?? "",
|
||||
tauxROSansFranchise: tarif.sansFranchise.tauxRO,
|
||||
tauxRGSansFranchise: tarif.sansFranchise.tauxRG,
|
||||
|
||||
tarifSelected: selectedFranchise.proposition,
|
||||
tarifCommercial: fac.primeHT,
|
||||
franchiseTPPCSelected: selectedFranchise.franchiseTPPC ?? "",
|
||||
franchiseExpoSelected: selectedFranchise.franchiseExpo ?? "",
|
||||
tauxROSelected: fac.tauxCotRO,
|
||||
tauxRGSelected: fac.tauxCotRG
|
||||
});
|
||||
} 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");
|
||||
}
|
||||
|
||||
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 = `Tarif-${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);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,651 +0,0 @@
|
|||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const fs = require("fs");
|
||||
const PizZip = require("pizzip");
|
||||
const Docxtemplater = require("docxtemplater");
|
||||
const logger = require("../utils/logger");
|
||||
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) => {
|
||||
try {
|
||||
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
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Cas Activité multimodal + Au minimum une zone
|
||||
if (rc.actMultimodal) {
|
||||
if (hasZone1 || hasZone2 || hasZone3 || hasZone456 || hasMondeEntier) {
|
||||
hasActiviteButNotMultimodal = true;
|
||||
}
|
||||
}
|
||||
|
||||
let hasMondeEntierOrMultimodal, hasNotMondeEntierOrMultimodal;
|
||||
if (rc.actMultimodal || hasMondeEntier) {
|
||||
hasMondeEntierOrMultimodal = true;
|
||||
hasNotMondeEntierOrMultimodal = false;
|
||||
} else {
|
||||
hasMondeEntierOrMultimodal = false;
|
||||
hasNotMondeEntierOrMultimodal = 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;
|
||||
}
|
||||
|
||||
// Conditions Extensions RCE
|
||||
let extRCE = false;
|
||||
|
||||
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";
|
||||
|
||||
doc.render({
|
||||
// Client
|
||||
nomClient: client.nom,
|
||||
adrClient: client.adresse,
|
||||
postalClient: client.codePostal,
|
||||
villeClient: client.ville,
|
||||
numClient: client.numClient,
|
||||
|
||||
// Intermédiaire
|
||||
nomInter: intermediaire.nom,
|
||||
adrInter: intermediaire.adresse,
|
||||
postalInter: intermediaire.codePostal,
|
||||
villeInter: intermediaire.ville,
|
||||
oriasInter: intermediaire.numOrias,
|
||||
hasOrias: intermediaire.numOrias == "" ? false : true,
|
||||
numInter: intermediaire.numTelephone,
|
||||
mailInter: intermediaire.mail,
|
||||
hasMail: intermediaire.mail ? true : false,
|
||||
|
||||
// Contrat
|
||||
numSaisine: contrat.numSaisine,
|
||||
numContrat: contrat.numContrat,
|
||||
numProjet: contrat.numSaisine ? contrat.numSaisine : contrat.numContrat,
|
||||
hasCP: contrat.type == "AN" || contrat.type == "TEMPORAIRE" ? true : false,
|
||||
hasRemplacement: contrat.type == "REMPLACEMENT" ? true : false,
|
||||
hasPJ: rc.pj,
|
||||
hasParticipationResultat: rc.participationResultat,
|
||||
hasRCE: rc.autresRC,
|
||||
hasExtRCC: extRCC,
|
||||
hasExtRCE: extRCE,
|
||||
hasAssAdd: listAssAdd.length > 0 ? true : false,
|
||||
listAssAdd: listAssAdd,
|
||||
listDesiVehicule: rc.designationVehicule,
|
||||
hasForfaitaire: rc.typeCot == "forfaitaire" ? true : false,
|
||||
hasRevisable: rc.typeCot == "revisable" ? true : false,
|
||||
hasCourtier: intermediaire.type == "COURTIER" ? true : false,
|
||||
hasAgent: intermediaire.type == "COURTIER" ? false : true,
|
||||
hasAgentMutualiste: intermediaire.type == "AGENT MUTUALISTE" ? true : false,
|
||||
|
||||
// Temporalité contrat
|
||||
dateJour: dateNow,
|
||||
typeFractionnement: rc.tempo,
|
||||
hasTypeTemporaire: contrat.type == "TEMPORAIRE" ? true : false,
|
||||
hasTypeAutreTempo: contrat.type == "AN" || contrat.type == "REMPLACEMENT" ? true : false,
|
||||
dateDebutEffet: rc.dateEffet == "00/00/0000" ? "A PRECISER" : rc.dateEffet,
|
||||
dateFinEffet: rc.dateFin == "00/00/0000" ? "A PRECISER" : rc.dateFin,
|
||||
dateEcheance: rc.dateEcheance == "00/00" ? "A PRECISER" : rc.dateEcheance,
|
||||
|
||||
// Activite
|
||||
hasActiviteVoiturier: rc.actVoiturier,
|
||||
garActiviteVoiturier: globalService.customFormatNumber(rc.valueActVoiturier, false),
|
||||
hasActiviteLoueur: rc.actLoueur,
|
||||
garActiviteLoueur: globalService.customFormatNumber(rc.valueActLoueur, false),
|
||||
hasActiviteMultimodal: rc.actMultimodal,
|
||||
garActiviteMultimodal: globalService.customFormatNumber(rc.valueActMultimodal, false),
|
||||
hasActiviteDouane: rc.actDouane,
|
||||
garActiviteDouane: globalService.customFormatNumber(rc.valueActDouane, false),
|
||||
hasOneOfActiviteDemenageurParticulier: oneOfActiviteDemenageurParticulier,
|
||||
hasActiviteDemenageurParticulier: rc.actDemPar,
|
||||
garActiviteDemenageurParticulier: globalService.customFormatNumber(rc.valueActDemPar, false),
|
||||
hasActiviteDPEtendue: rc.actDemParDom,
|
||||
garActiviteDPEtendue: globalService.customFormatNumber(rc.valueActDemParDom, false),
|
||||
hasActiviteDemenageurParticulierAdvalorem: rc.actDemParAdv,
|
||||
garActiviteDemenageurParticulierAdvalorem: globalService.customFormatNumber(rc.valueActDemParAdv, false),
|
||||
hasActiviteDemenageurEntreprises: rc.actDemEntr,
|
||||
garActiviteDemenageurEntreprises: globalService.customFormatNumber(rc.valueActDemEntr, false),
|
||||
hasActiviteDemenageurInterne: rc.actDemInterne,
|
||||
garActiviteDemenageurInterne: globalService.customFormatNumber(rc.valueActDemInterne, false),
|
||||
hasActiviteGardeMeubles: rc.actGardeMeuble,
|
||||
garActiviteGardeMeubles: globalService.customFormatNumber(rc.valueActGardeMeuble, false),
|
||||
hasActiviteEntrepositaireDepositaire: rc.actEntDep,
|
||||
garActiviteEntrepositaireDepositaire: globalService.customFormatNumber(rc.valueActEntDep, false),
|
||||
hasActivitePrestataireLogistique: rc.actPrestaLog,
|
||||
garActivitePrestataireLogistique: globalService.customFormatNumber(rc.valueActPrestaLog, false),
|
||||
hasActiviteManutentionnaireLevageur: rc.actLevageur,
|
||||
garActiviteManutentionnaireLevageur: globalService.customFormatNumber(rc.valueActLevageur, false),
|
||||
hasActiviteTransitaire: rc.actTransitaire,
|
||||
garActiviteTransitaire: globalService.customFormatNumber(rc.valueActTransitaire, false),
|
||||
hasActiviteDemenageurGardeMeuble: hasActiviteDemenageurGardeMeuble,
|
||||
varCoorDem: coorDem,
|
||||
|
||||
// Marchandises
|
||||
hasMarchandiseOrdinaire: rc.marOrdinaire,
|
||||
hasMarchandiseVehiculesRoulants: rc.marRoulant,
|
||||
hasMarchandiseEngins: rc.marEngins,
|
||||
hasMarchandiseVehiculesRoulantsDemenagement: rc.marRoulantDem,
|
||||
hasMarchandiseMobiliersUsages: rc.marMobilerUsag,
|
||||
hasMarchandisePerissables: rc.marPerissable,
|
||||
hasMarchandiseAnimauxVivants: rc.marAnimaux,
|
||||
hasMarchandiseCiterne: rc.marCiterne,
|
||||
hasMarchandiseBeton: rc.marBeton,
|
||||
hasMarchandiseExceptionnels: rc.marExceptionnels,
|
||||
hasMarchandiseVrac: rc.marVrac,
|
||||
|
||||
// Zone
|
||||
hasMondeEntier: hasMondeEntier,
|
||||
hasZone1: hasZone1,
|
||||
hasZone2: hasZone2,
|
||||
hasZone3: hasZone3,
|
||||
hasZone4: hasZone4,
|
||||
hasZone5: hasZone5,
|
||||
hasZone6: hasZone6,
|
||||
hasActiviteButNotMultimodal: hasActiviteButNotMultimodal,
|
||||
hasMondeEntierOrMultimodal: hasMondeEntierOrMultimodal,
|
||||
hasNotMondeEntierOrMultimodal: hasNotMondeEntierOrMultimodal,
|
||||
hasZone456: hasZone456,
|
||||
|
||||
// Extensions de garanties RCC
|
||||
hasExtModifCallageArrimage: rc.extRCCModifCalArrim,
|
||||
hasExtFerroutage: rc.extRCCFerroutage,
|
||||
hasExtReconstitution: rc.extRCCFraisRecons,
|
||||
hasExtConfies: rc.extRCCConfie,
|
||||
hasCCValeurDecla: rc.typeExtConfies == "VALEUR DECLAREE" || rc.typeExtConfies == "" ? true : false,
|
||||
hasCCAdValo: rc.typeExtConfies == "ADVALOREM" ? true : false,
|
||||
hasExtTPPC: rc.extRCCTPPC,
|
||||
hasExtRegie: rc.extRCCRegie,
|
||||
hasExtSansMontageDemontage: rc.extRCCSansMontageDemontage,
|
||||
|
||||
// Extensions de garanties RCC
|
||||
hasExtBranchementDebranchement: rc.extRCEBraDebra,
|
||||
hasExtMontageDemontage: rc.extRCEMontageDemontage,
|
||||
|
||||
// Advalo
|
||||
grMultimodal: rc.grilleMultimodal,
|
||||
grTerrestre: rc.grilleTerrestre,
|
||||
grAerien: rc.grilleAerien,
|
||||
|
||||
// Variables numérique
|
||||
franchiseTarif: franchiseTarif,
|
||||
cotisationRCCHT: globalService.customFormatNumber(rc.cotRCCHT, true),
|
||||
tauxRCCHT: globalService.customFormatNumber(rc.tauxRCCHT, true, true),
|
||||
cotisationRCCTTC: globalService.customFormatNumber(rc.cotRCCTTC, true),
|
||||
tauxRCCTTC: globalService.customFormatNumber(rc.tauxRCCTTC, true, true),
|
||||
cotisationRCEHT: globalService.customFormatNumber(rc.cotRCEHT, true),
|
||||
tauxRCEHT: globalService.customFormatNumber(rc.tauxRCEHT, true, true),
|
||||
cotisationRCETTC: globalService.customFormatNumber(rc.cotRCETTC, true),
|
||||
tauxRCETTC: globalService.customFormatNumber(rc.tauxRCETTC, true, true),
|
||||
PJHT: globalService.customFormatNumber(rc.cotPJHT, true),
|
||||
PJTTC: globalService.customFormatNumber(rc.cotPJTTC, true),
|
||||
cotisationMini: globalService.customFormatNumber(rc.cotIrreductible, true),
|
||||
cotisationTotale: globalService.customFormatNumber(rc.cotTotalTTC, true),
|
||||
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) {
|
||||
logger.log('error', 'Error in RC projet generation:', error);
|
||||
return res.status(500).send("Erreur lors de la génération du projet RC");
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
|
|
@ -1,647 +0,0 @@
|
|||
// controllers/historiqueParcoursController.js
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const renderPage = require("../utils/renderHelper");
|
||||
const logger = require("../utils/logger");
|
||||
const parcoursService = require("../services/parcoursService");
|
||||
const clientService = require("../services/clientService");
|
||||
const { fmtDateFR, xmlEsc, cellXml, rowXml } = require("../services/globalService");
|
||||
|
||||
/**
|
||||
* Construit les filtres et le tri PocketBase à partir des paramètres DataTables
|
||||
* @param {Object} params - Paramètres de recherche et filtrage
|
||||
* @param {string[]} params.regions - Liste des régions à filtrer
|
||||
* @param {Object} params.search - Objet de recherche globale
|
||||
* @param {Array} params.columns - Colonnes avec leurs filtres individuels
|
||||
* @param {Array} params.order - Ordre de tri
|
||||
* @returns {Object} - {filter: string, sort: string}
|
||||
*/
|
||||
function buildPocketBaseFilterAndSort({ regions = [], search = { value: "" }, columns = [], order = [] }) {
|
||||
const parts = [];
|
||||
|
||||
/**
|
||||
* Recherche globale : recherche dans tous les champs pertinents
|
||||
*/
|
||||
const q = (search?.value || "").trim();
|
||||
if (q) {
|
||||
const esc = q.replace(/"/g, '\\"');
|
||||
parts.push(`(
|
||||
numParcours ~ "${esc}"
|
||||
|| contrat.numSaisine ~ "${esc}"
|
||||
|| contrat.numContrat ~ "${esc}"
|
||||
|| contrat.produit ~ "${esc}"
|
||||
|| contrat.type ~ "${esc}"
|
||||
|| contrat.intermediaire.nom ~ "${esc}"
|
||||
|| contrat.intermediaire.numPortefeuille ~ "${esc}"
|
||||
|| contrat.client.nom ~ "${esc}"
|
||||
|| contrat.client.numClient ~ "${esc}"
|
||||
|| dernierUtilisateur.prenom ~ "${esc}"
|
||||
|| dernierUtilisateur.nom ~ "${esc}"
|
||||
|| dernierUtilisateur.matricule ~ "${esc}"
|
||||
|| dernierUtilisateur.region.nom ~ "${esc}"
|
||||
)`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche par colonne : filtre spécifique pour chaque colonne
|
||||
*/
|
||||
const colFilter = (idx, fieldPaths) => {
|
||||
const v = (columns[idx]?.search?.value || "").trim();
|
||||
if (!v) return null;
|
||||
const esc = v.replace(/"/g, '\\"');
|
||||
return `(${fieldPaths.map(fp => `${fp} ~ "${esc}"`).join(" || ")})`;
|
||||
};
|
||||
|
||||
const pushIf = (v) => { if (v) parts.push(v); };
|
||||
|
||||
// Filtres par colonne (index correspondant à l'ordre des colonnes DataTables)
|
||||
pushIf(colFilter(0, ["numParcours"]));
|
||||
pushIf(colFilter(1, ["created"]));
|
||||
pushIf(colFilter(2, ["dernierUtilisateur.matricule"]));
|
||||
pushIf(colFilter(3, ["dernierUtilisateur.prenom", "dernierUtilisateur.nom"]));
|
||||
pushIf(colFilter(4, ["dernierUtilisateur.region.nom"]));
|
||||
pushIf(colFilter(5, ["contrat.numSaisine"]));
|
||||
pushIf(colFilter(6, ["contrat.numContrat"]));
|
||||
pushIf(colFilter(7, ["contrat.produit"]));
|
||||
pushIf(colFilter(8, ["contrat.type"]));
|
||||
pushIf(colFilter(9, ["contrat.intermediaire.numPortefeuille"]));
|
||||
pushIf(colFilter(10, ["contrat.intermediaire.nom"]));
|
||||
pushIf(colFilter(11, ["contrat.client.numClient"]));
|
||||
pushIf(colFilter(12, ["contrat.client.nom"]));
|
||||
|
||||
const filter = parts.length ? parts.join(" && ") : "";
|
||||
|
||||
/**
|
||||
* Construction du tri PocketBase
|
||||
* Mapping des index de colonnes DataTables vers les champs PocketBase
|
||||
* Le préfixe "-" indique un tri décroissant
|
||||
*/
|
||||
const sortMap = {
|
||||
0: "numParcours",
|
||||
1: "created",
|
||||
2: "dernierUtilisateur.matricule",
|
||||
4: "dernierUtilisateur.region.nom",
|
||||
6: "contrat.numContrat",
|
||||
7: "contrat.produit",
|
||||
10: "contrat.intermediaire.nom",
|
||||
12: "contrat.client.nom"
|
||||
};
|
||||
|
||||
let sort = "-created"; // Tri par défaut : date de création décroissante
|
||||
if (order && order.length > 0) {
|
||||
const { column, dir } = order[0];
|
||||
const field = sortMap[column];
|
||||
if (field) {
|
||||
sort = (dir === "desc" ? "-" : "") + field;
|
||||
}
|
||||
}
|
||||
|
||||
return { filter, sort };
|
||||
}
|
||||
|
||||
/**
|
||||
* Route GET / : Affichage de la page Historique des parcours
|
||||
*/
|
||||
router.get("/", (req, res) => {
|
||||
renderPage("historiqueParcours.ejs", res);});
|
||||
|
||||
/**
|
||||
* /regionUser : requête sur la region de l'user actuel
|
||||
*/
|
||||
router.get("/:regionUser", async (req, res) => {
|
||||
try {
|
||||
const { regionUser } = req.params;
|
||||
const data = await parcoursService.getParcoursByRegionsPage([regionUser], 1, 10, { filter: "", sort: "-created" });
|
||||
if (data) {
|
||||
res.json({ valid: true, data });
|
||||
} else {
|
||||
res.json({ valid: false });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log("error", error);
|
||||
res.status(500).json({
|
||||
valid: false,
|
||||
error: "Erreur lors de la récupération des parcours.",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* /datatable : DataTables server-side (gestion de pagination)
|
||||
*/
|
||||
router.post("/datatable", async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
draw = 1,
|
||||
start = 0,
|
||||
length = 10,
|
||||
regions = [],
|
||||
search = { value: "" },
|
||||
columns = [],
|
||||
order = []
|
||||
} = req.body || {};
|
||||
|
||||
const page = Math.floor(start / length) + 1; // nb de page
|
||||
const perPage = Number(length) || 10; //nb d'éléments par page
|
||||
|
||||
const { filter, sort } = buildPocketBaseFilterAndSort({ search, columns, order }); // construction du filtrage côté Back
|
||||
|
||||
const result = await parcoursService.getParcoursByRegionsPage([], page, perPage, { filter, sort });
|
||||
|
||||
/**
|
||||
* Construction des lignes de données pour DataTables
|
||||
* Traitement séquentiel pour garantir la récupération des clients
|
||||
*/
|
||||
const rows = [];
|
||||
for (const parcours of result.items) {
|
||||
try {
|
||||
const contrat = parcours["@expand"]?.contrat || null;
|
||||
|
||||
/**
|
||||
* Récupération du client avec fallback
|
||||
* L'expand PocketBase ne fonctionne pas toujours pour contrat.client,
|
||||
* donc on récupère directement via l'ID si nécessaire
|
||||
*/
|
||||
let client = null;
|
||||
if (contrat) {
|
||||
// Tentative via expand (si disponible)
|
||||
client = contrat["@expand"]?.client || null;
|
||||
|
||||
// Fallback : récupération directe via l'ID du client
|
||||
if (!client && contrat.client) {
|
||||
const clientId = typeof contrat.client === 'string'
|
||||
? contrat.client
|
||||
: (contrat.client?.id || contrat.client);
|
||||
|
||||
if (clientId) {
|
||||
try {
|
||||
client = await clientService.getClient(clientId);
|
||||
} catch (err) {
|
||||
// Erreur silencieuse : client non trouvé ou erreur de récupération
|
||||
client = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cas où contrat.client est déjà un objet (expand réussi mais pas dans @expand)
|
||||
if (!client && contrat.client && typeof contrat.client === 'object' && contrat.client.numClient) {
|
||||
client = contrat.client;
|
||||
}
|
||||
}
|
||||
const lastUser = parcours["@expand"]?.dernierUtilisateur;
|
||||
const region = lastUser?.["@expand"]?.region;
|
||||
const produit = contrat ? (contrat.produit || "NC") : "NC";
|
||||
|
||||
/**
|
||||
* Construction de la ligne DataTables
|
||||
* Ordre des colonnes : Numéro Parcours, Date Création, Matricule, Utilisateur, Région,
|
||||
* Numéro Saisine, Numéro Contrat, Produit, Type, Portefeuille, Intermédiaire,
|
||||
* Numéro Client, Nom Client, Bouton Reprendre, Bouton Générer
|
||||
*/
|
||||
rows.push([
|
||||
parcours.numParcours,
|
||||
fmtDateFR(parcours.created),
|
||||
lastUser?.matricule || "NC",
|
||||
lastUser ? `${lastUser.prenom} ${lastUser.nom}`.trim() || "NC" : "NC",
|
||||
region ? region.nom : "NC",
|
||||
contrat ? (contrat.numSaisine || "NC") : "NC",
|
||||
contrat ? (contrat.numContrat || "NC") : "NC",
|
||||
produit,
|
||||
contrat ? (contrat.type || "NC") : "NC",
|
||||
contrat ? (contrat["@expand"]?.intermediaire?.numPortefeuille || "NC") : "NC",
|
||||
contrat ? (contrat["@expand"]?.intermediaire?.nom || "NC") : "NC",
|
||||
client ? (client.numClient || "NC") : "NC",
|
||||
client ? (client.nom || "NC") : "NC",
|
||||
`<button type="button" id="btnReprendre" class="btn" onclick="window.location.href='${(!contrat || (!contrat.numSaisine && !contrat.numContrat)) ? `/contrat?numParcours=${parcours.numParcours}` : `/navParcours?numParcours=${parcours.numParcours}&submenu=client`}'"><i class="fas fa-arrow-right"></i></button>`,
|
||||
`<button type="button" id="btnGenerate" class="btn" data-produit="${produit}" data-num-parcours="${parcours.numParcours}" ${( !contrat || contrat.produit == "" || (contrat.fac == "" && contrat.rc == "" && contrat.tppc == "") || contrat.client == "" || contrat.intermediaire == "" ) ? 'disabled' : ''}><i class="fa-solid fa-file-arrow-down"></i></button>`
|
||||
]);
|
||||
} catch (err) {
|
||||
logger.log("error", `Erreur traitement parcours ${parcours?.numParcours || 'inconnu'}:`, err);
|
||||
// Ligne par défaut en cas d'erreur
|
||||
rows.push(["NC", "NC", "NC", "NC", "NC", "NC", "NC", "NC", "NC", "NC", "NC", "NC", "NC", "", ""]);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
draw: Number(draw),
|
||||
recordsTotal: result.totalItems,
|
||||
recordsFiltered: result.totalItems,
|
||||
data: rows
|
||||
});
|
||||
} catch (error) {
|
||||
// Ignorer silencieusement les erreurs d'abort (requête annulée côté client)
|
||||
if (error && (error.name === "AbortError" || error.name === "DOMException" || error.message?.includes("aborted"))) {
|
||||
return;
|
||||
}
|
||||
// Pour toutes les autres erreurs, on les log et on renvoie une réponse d'erreur
|
||||
logger.log("error", "ça ne marche pas mais, aucune erreur" + error);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ draw: 0, recordsTotal: 0, recordsFiltered: 0, data: [] });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* EXPORT CSV
|
||||
* Exporte l'historique des parcours au format CSV
|
||||
* Supporte l'export complet ou filtré selon les paramètres de la requête
|
||||
*/
|
||||
router.post("/export/csv", async (req, res) => {
|
||||
let aborted = false;
|
||||
req.on("aborted", () => {
|
||||
aborted = true;
|
||||
logger.log("warn", "Client a interrompu la connexion pendant l'export CSV");
|
||||
});
|
||||
res.on("finish", () => {
|
||||
logger.log("info", "Export CSV terminé");
|
||||
});
|
||||
|
||||
try {
|
||||
const {
|
||||
regions = [],
|
||||
search = { value: "" },
|
||||
columns = [],
|
||||
order = [],
|
||||
mode = "filtered",
|
||||
} = req.body || {};
|
||||
|
||||
const effective = (mode === "full")
|
||||
? { regions: [], search: { value: "" }, columns: [], order }
|
||||
: { regions, search, columns, order };
|
||||
|
||||
const { filter, sort } = buildPocketBaseFilterAndSort(effective);
|
||||
|
||||
res.setHeader("Content-Type", "text/csv; charset=utf-8");
|
||||
res.setHeader("Content-Disposition", `attachment; filename="historique_parcours.csv"`);
|
||||
|
||||
// BOM UTF-8 pour Excel
|
||||
res.write("\uFEFF");
|
||||
|
||||
const headers = [
|
||||
"Numéro du Parcours","Date de Création","Matricule","Dernier Utilisateur","Region",
|
||||
"Numéro Saisine","Numéro Contrat","Produit","Type","Numéro de Portefeuille",
|
||||
"Nom Intermediaire","Numéro de Client","Nom Client"
|
||||
];
|
||||
res.write(headers.join(";") + "\n");
|
||||
|
||||
/**
|
||||
* OPTIMISATION : getFullList pour récupérer tous les parcours en une requête
|
||||
* + batch client pour récupérer tous les clients manquants en une requête
|
||||
*/
|
||||
|
||||
// Construction du filtre régions (identique à getParcoursByRegionsPage)
|
||||
let regFilter = "";
|
||||
if (Array.isArray(effective.regions) && effective.regions.length > 0) {
|
||||
const ors = effective.regions.map(r => `dernierUtilisateur.region.nom = "${r}"`);
|
||||
regFilter = `(${ors.join(" || ")})`;
|
||||
}
|
||||
const finalFilter = [regFilter, filter].filter(Boolean).join(" && ");
|
||||
const expandFields = "contrat, contrat.client, contrat.intermediaire, dernierUtilisateur.region";
|
||||
|
||||
// Récupération de tous les parcours en une seule requête
|
||||
let allParcours;
|
||||
try {
|
||||
allParcours = await parcoursService.getParcoursFullList({
|
||||
filter: finalFilter,
|
||||
sort: sort || "-created",
|
||||
expand: expandFields,
|
||||
batch: 500,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
logger.log("error", "Erreur récupération parcours pour export CSV:", err);
|
||||
if (!res.headersSent) {
|
||||
return res.status(500).send("Erreur lors de la récupération des données");
|
||||
}
|
||||
try { res.end(); } catch {}
|
||||
return;
|
||||
}
|
||||
|
||||
// Collecte des IDs clients manquants (l'expand contrat.client ne fonctionne pas en SDK 0.7 pocket)
|
||||
const missingClientIds = [];
|
||||
for (const parcours of allParcours) {
|
||||
const contrat = parcours["@expand"]?.contrat;
|
||||
if (contrat && contrat.client && !contrat["@expand"]?.client) {
|
||||
missingClientIds.push(contrat.client);
|
||||
}
|
||||
}
|
||||
|
||||
// Récupération batch de tous les clients manquants en une seule requête
|
||||
const clientsMap = await clientService.getClientsBatch(missingClientIds);
|
||||
|
||||
// Traitement des parcours
|
||||
for (const parcours of allParcours) {
|
||||
if (aborted) break;
|
||||
|
||||
const contrat = parcours["@expand"]?.contrat || null;
|
||||
const intermediaire = contrat ? (contrat["@expand"]?.intermediaire || null) : null;
|
||||
|
||||
// Client : d'abord depuis l'expand, sinon depuis le batch
|
||||
let client = contrat ? (contrat["@expand"]?.client || null) : null;
|
||||
if (!client && contrat && contrat.client) {
|
||||
client = clientsMap.get(contrat.client) || null;
|
||||
}
|
||||
|
||||
const lastUser = parcours["@expand"]?.dernierUtilisateur;
|
||||
const region = lastUser?.["@expand"]?.region;
|
||||
|
||||
const row = [
|
||||
parcours.numParcours,
|
||||
fmtDateFR(parcours.created),
|
||||
lastUser?.matricule || "NC",
|
||||
lastUser ? `${lastUser.prenom || ""} ${lastUser.nom || ""}`.trim() || "NC" : "NC",
|
||||
region ? (region.nom || "NC") : "NC",
|
||||
contrat ? (contrat.numSaisine || "NC") : "NC",
|
||||
contrat ? (contrat.numContrat || "NC") : "NC",
|
||||
contrat ? (contrat.produit || "NC") : "NC",
|
||||
contrat ? (contrat.type || "NC") : "NC",
|
||||
intermediaire ? (intermediaire.numPortefeuille || "NC") : "NC",
|
||||
intermediaire ? (intermediaire.nom || "NC") : "NC",
|
||||
client ? (client.numClient || "NC") : "NC",
|
||||
client ? (client.nom || "NC") : "NC",
|
||||
];
|
||||
|
||||
const safe = row.map(v => String(v).replaceAll(";", ",").replace(/\r?\n/g, " "));
|
||||
try {
|
||||
res.write(safe.join(";") + "\n");
|
||||
}
|
||||
catch (werr) {
|
||||
logger.log("error", werr);
|
||||
aborted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!aborted) {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
logger.log("error", error);
|
||||
if (!res.headersSent) {
|
||||
return res.status(500).send("Erreur export CSV");
|
||||
}
|
||||
try { res.end(); } catch {}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ====== UTILITAIRES XML/XLS ======
|
||||
|
||||
/**
|
||||
* EXPORT XLS (SpreadsheetML 2003)
|
||||
* Format XLS utilisé car XLSX est trop complexe à générer manuellement.
|
||||
* Le format XLS est toujours supporté par Excel sans perte de données.
|
||||
*/
|
||||
router.post("/export/xls", async (req, res) => {
|
||||
let aborted = false;
|
||||
req.on("aborted", () => {
|
||||
aborted = true;
|
||||
logger.log("warn", "Client a interrompu la connexion pendant l'export XLS");
|
||||
});
|
||||
res.on("finish", () => {
|
||||
logger.log("info", "Export XLS terminé");
|
||||
});
|
||||
|
||||
try {
|
||||
const {
|
||||
regions = [],
|
||||
search = { value: "" },
|
||||
columns = [],
|
||||
order = [],
|
||||
mode = "filtered"
|
||||
} = req.body || {};
|
||||
|
||||
const effective = (mode === "full")
|
||||
? { regions: [], search: { value: "" }, columns, order }
|
||||
: { regions, search, columns, order };
|
||||
|
||||
const { filter, sort } = buildPocketBaseFilterAndSort(effective);
|
||||
|
||||
const headers = [
|
||||
"Numéro du Parcours","Date de Création","Matricule","Dernier Utilisateur","Region",
|
||||
"Numéro Saisine","Numéro Contrat","Produit","Type","Numéro de Portefeuille",
|
||||
"Nom Intermediaire","Numéro de Client","Nom Client"
|
||||
];
|
||||
|
||||
/**
|
||||
* OPTIMISATION : getFullList pour récupérer tous les parcours en une requête
|
||||
* + batch client pour récupérer tous les clients manquants en une requête
|
||||
*/
|
||||
|
||||
// Construction du filtre régions (identique à getParcoursByRegionsPage)
|
||||
let regFilter = "";
|
||||
if (Array.isArray(effective.regions) && effective.regions.length > 0) {
|
||||
const ors = effective.regions.map(r => `dernierUtilisateur.region.nom = "${r}"`);
|
||||
regFilter = `(${ors.join(" || ")})`;
|
||||
}
|
||||
const finalFilter = [regFilter, filter].filter(Boolean).join(" && ");
|
||||
const expandFields = "contrat, contrat.client, contrat.intermediaire, dernierUtilisateur.region";
|
||||
|
||||
// Récupération de tous les parcours en une seule requête
|
||||
let allParcours;
|
||||
try {
|
||||
allParcours = await parcoursService.getParcoursFullList({
|
||||
filter: finalFilter,
|
||||
sort: sort || "-created",
|
||||
expand: expandFields,
|
||||
batch: 500,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
logger.log("error", "Erreur récupération parcours pour export XLS:", err);
|
||||
if (!res.headersSent) {
|
||||
return res.status(500).send("Erreur lors de la récupération des données");
|
||||
}
|
||||
try { res.end(); } catch {}
|
||||
return;
|
||||
}
|
||||
|
||||
// Collecte des IDs clients manquants (l'expand contrat.client ne fonctionne pas en SDK 0.7.x Pocket)
|
||||
const missingClientIds = [];
|
||||
for (const parcours of allParcours) {
|
||||
const contrat = parcours["@expand"]?.contrat;
|
||||
if (contrat && contrat.client && !contrat["@expand"]?.client) {
|
||||
missingClientIds.push(contrat.client);
|
||||
}
|
||||
}
|
||||
|
||||
// Récupération batch de tous les clients manquants en une seule requête
|
||||
const clientsMap = await clientService.getClientsBatch(missingClientIds);
|
||||
|
||||
const fileName = (mode === "full")
|
||||
? "historique_parcours_complet.xls"
|
||||
: "historique_parcours_filtre.xls";
|
||||
|
||||
res.setHeader("Content-Type", "application/vnd.ms-excel; charset=utf-8");
|
||||
res.setHeader("Content-Disposition", `attachment; filename="${fileName}"`);
|
||||
|
||||
// En-tête SpreadsheetML 2003
|
||||
res.write(
|
||||
`<?xml version="1.0"?>
|
||||
<?mso-application progid="Excel.Sheet"?>
|
||||
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
|
||||
xmlns:html="http://www.w3.org/TR/REC-html40">
|
||||
<Styles>
|
||||
<Style ss:ID="Default" ss:Name="Normal">
|
||||
<Alignment ss:Vertical="Center"/>
|
||||
<Borders/>
|
||||
<Font ss:FontName="Calibri" ss:Size="11"/>
|
||||
<Interior/>
|
||||
<NumberFormat/>
|
||||
<Protection/>
|
||||
</Style>
|
||||
<Style ss:ID="Header">
|
||||
<Font ss:Bold="1"/>
|
||||
<Alignment ss:Horizontal="Center" ss:Vertical="Center"/>
|
||||
</Style>
|
||||
</Styles>
|
||||
<Worksheet ss:Name="Historique">
|
||||
<Table>
|
||||
`
|
||||
);
|
||||
|
||||
res.write(
|
||||
`<Row ss:StyleID="Header">` +
|
||||
headers.map(h => `<Cell><Data ss:Type="String">${xmlEsc(h)}</Data></Cell>`).join("") +
|
||||
`</Row>\n`
|
||||
);
|
||||
|
||||
// Traitement des parcours
|
||||
for (const parcours of allParcours) {
|
||||
const contrat = parcours["@expand"]?.contrat || null;
|
||||
const intermediaire = contrat ? (contrat["@expand"]?.intermediaire || null) : null;
|
||||
|
||||
// Client : d'abord depuis l'expand, sinon depuis le batch
|
||||
let client = contrat ? (contrat["@expand"]?.client || null) : null;
|
||||
if (!client && contrat && contrat.client) {
|
||||
client = clientsMap.get(contrat.client) || null;
|
||||
}
|
||||
|
||||
const lastUser = parcours["@expand"]?.dernierUtilisateur;
|
||||
const region = lastUser?.["@expand"]?.region;
|
||||
|
||||
const row = [
|
||||
parcours.numParcours,
|
||||
fmtDateFR(parcours.created),
|
||||
lastUser?.matricule || "NC",
|
||||
lastUser ? `${lastUser.prenom || ""} ${lastUser.nom || ""}`.trim() || "NC" : "NC",
|
||||
region ? (region.nom || "NC") : "NC",
|
||||
contrat ? (contrat.numSaisine || "NC") : "NC",
|
||||
contrat ? (contrat.numContrat || "NC") : "NC",
|
||||
contrat ? (contrat.produit || "NC") : "NC",
|
||||
contrat ? (contrat.type || "NC") : "NC",
|
||||
intermediaire ? (intermediaire.numPortefeuille || "NC") : "NC",
|
||||
intermediaire ? (intermediaire.nom || "NC") : "NC",
|
||||
client ? (client.numClient || "NC") : "NC",
|
||||
client ? (client.nom || "NC") : "NC",
|
||||
].map(v => String(v).replace(/\r?\n/g, " "));
|
||||
|
||||
res.write(rowXml(row) + "\n");
|
||||
}
|
||||
|
||||
// Fermeture du fichier XML SpreadsheetML
|
||||
res.write(
|
||||
` </Table>
|
||||
</Worksheet>
|
||||
</Workbook>`
|
||||
);
|
||||
res.end();
|
||||
|
||||
}
|
||||
catch (error) {
|
||||
logger.log("error", error);
|
||||
if (!res.headersSent) return res.status(500).send("Erreur export XLS");
|
||||
try { res.end(); } catch {}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Route GET /details/:numParcours
|
||||
* Récupère les détails complets d'un parcours (parcours + contrat + fiche produit)
|
||||
* Utilisé pour afficher le panneau de détails dans la datatable
|
||||
*/
|
||||
router.get("/details/:numParcours", async (req, res) => {
|
||||
try {
|
||||
const { numParcours } = req.params;
|
||||
const parcours = await parcoursService.getDeepDetailsByNumParcours(numParcours);
|
||||
if (!parcours) return res.json({ valid: false, error: "Parcours introuvable" });
|
||||
|
||||
/**
|
||||
* Extraction des données pour faciliter l'accès côté frontend
|
||||
*/
|
||||
const contrat = parcours?.["@expand"]?.contrat || null;
|
||||
const produit = (contrat?.produit || "").toUpperCase();
|
||||
|
||||
/**
|
||||
* Récupération de la fiche produit selon le type (TPPC, RC, FAC)
|
||||
* Si l'expand n'a pas fonctionné, on essaie de récupérer directement via l'ID
|
||||
*/
|
||||
let produitRecord = null;
|
||||
if (produit === "TPPC") {
|
||||
produitRecord = contrat?.["@expand"]?.tppc || null;
|
||||
}
|
||||
else if (produit === "RC") {
|
||||
produitRecord = contrat?.["@expand"]?.rc || null;
|
||||
// Si l'expand n'a pas fonctionné mais qu'on a l'ID, on récupère directement
|
||||
if (!produitRecord && contrat?.rc) {
|
||||
try {
|
||||
const rcId = typeof contrat.rc === 'string' ? contrat.rc : contrat.rc?.id || contrat.rc;
|
||||
if (rcId) {
|
||||
const rcService = require("../services/rcService");
|
||||
const rcData = await rcService.getRCbyId(rcId);
|
||||
// fetchInfoByCriteria retourne directement l'item, pas un objet avec items
|
||||
produitRecord = rcData || null;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
logger.log("info", `Erreur récupération RC directe pour ${numParcours}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (produit === "FAC") {
|
||||
produitRecord = contrat?.["@expand"]?.fac || null;
|
||||
// Si l'expand n'a pas fonctionné mais qu'on a l'ID, on récupère directement
|
||||
if (!produitRecord && contrat?.fac) {
|
||||
try {
|
||||
const facId = typeof contrat.fac === 'string' ? contrat.fac : contrat.fac?.id || contrat.fac;
|
||||
if (facId) {
|
||||
const facService = require("../services/facService");
|
||||
const facData = await facService.getFACbyId(facId);
|
||||
// fetchInfoByCriteria retourne directement l'item, pas un objet avec items
|
||||
produitRecord = facData || null;
|
||||
if (!produitRecord) {
|
||||
logger.log("warn", `FAC non trouvé pour ID ${facId} (parcours ${numParcours})`);
|
||||
}
|
||||
} else {
|
||||
logger.log("warn", `Pas d'ID FAC dans contrat pour parcours ${numParcours}`);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
logger.log("warn", `Erreur récupération FAC directe pour ${numParcours}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
else if (!produitRecord && !contrat?.fac) {
|
||||
logger.log("warn", `Contrat FAC sans relation FAC pour parcours ${numParcours}`);
|
||||
}
|
||||
}
|
||||
|
||||
return res.json({
|
||||
valid: true,
|
||||
produit,
|
||||
parcours,
|
||||
contrat,
|
||||
produitRecord,
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
logger.log("error", e);
|
||||
return res.status(500).json({ valid: false, error: "Erreur recherche détails" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
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) => {
|
||||
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" });
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
Pocketbase_0.7.5.exe serve --http="127.0.0.1:8091"
|
||||
pause
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
const { db } = require('../db/db-connect');
|
||||
const logger = require('../utils/logger');
|
||||
const globalService = require("../services/globalService");
|
||||
|
||||
async function createClient() {
|
||||
return await db.records.create('client', {});
|
||||
}
|
||||
|
||||
async function getClient(id) {
|
||||
const criteria = {
|
||||
filter: `id='${id}'`,
|
||||
};
|
||||
|
||||
return globalService.fetchInfoByCriteria("client", criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère plusieurs clients en plusieurs requêtes batch (optimisation pour exports)
|
||||
* Découpe en chunks de 50 IDs pour éviter les filtres trop longs
|
||||
* avec getFullList(collection, batchSize, options)
|
||||
* @param {string[]} clientIds - Tableau d'IDs de clients
|
||||
* @returns {Map<string, Object>} - Map des clients par ID
|
||||
*/
|
||||
async function getClientsBatch(clientIds) {
|
||||
const clientMap = new Map();
|
||||
if (!clientIds || clientIds.length === 0) return clientMap;
|
||||
|
||||
// Filtrer les IDs valides et uniques
|
||||
const uniqueIds = [...new Set(clientIds.filter(id => id && typeof id === 'string'))];
|
||||
if (uniqueIds.length === 0) return clientMap;
|
||||
|
||||
// Découper en chunks de 50 pour éviter les filtres trop longs
|
||||
const CHUNK_SIZE = 50;
|
||||
const chunks = [];
|
||||
for (let i = 0; i < uniqueIds.length; i += CHUNK_SIZE) {
|
||||
chunks.push(uniqueIds.slice(i, i + CHUNK_SIZE));
|
||||
}
|
||||
|
||||
// Traiter chaque chunk
|
||||
for (const chunk of chunks) {
|
||||
try {
|
||||
// Construire le filtre OR pour ce chunk
|
||||
const filter = chunk.map(id => `id = "${id}"`).join(" || ");
|
||||
|
||||
// getFullList(collection, batchSize, options)
|
||||
const clients = await db.records.getFullList("client", 500, {
|
||||
filter: filter,
|
||||
});
|
||||
|
||||
// Ajouter à la map
|
||||
clients.forEach(client => {
|
||||
if (client && client.id) {
|
||||
clientMap.set(client.id, client);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
logger.log("warn", `Erreur récupération clients chunk (${chunk.length} IDs):`, err?.message || String(err));
|
||||
}
|
||||
}
|
||||
|
||||
return clientMap;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createClient,
|
||||
getClient,
|
||||
getClientsBatch,
|
||||
};
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
const { db } = require("../db/db-connect");
|
||||
const logger = require("../utils/logger");
|
||||
const numeral = require("numeral");
|
||||
|
||||
require("numeral/locales/fr");
|
||||
numeral.locale("fr");
|
||||
|
||||
async function getRecordIdFromFieldValue(collection, field, value) {
|
||||
try {
|
||||
const resultList = await db.records.getList(collection, 1, 1, {
|
||||
filter: `${field} = '${value}'`,
|
||||
});
|
||||
|
||||
if (resultList.totalItems > 0) {
|
||||
return resultList.items[0].id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log("error", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchInfoByCriteria(collection, criteria) {
|
||||
try {
|
||||
const resultList = await db.records.getList(collection, 1, 1, criteria);
|
||||
|
||||
if (resultList.totalItems > 0) {
|
||||
return resultList.items[0];
|
||||
}
|
||||
} catch (error) {
|
||||
/**
|
||||
* Gestion silencieuse des erreurs d'abort (requêtes interrompues)
|
||||
* Ces erreurs sont normales lors de requêtes parallèles et ne doivent pas être loggées
|
||||
*/
|
||||
if (error?.isAbort || error?.name?.includes("Abort") || error?.status === 0) {
|
||||
return null;
|
||||
}
|
||||
// Autres erreurs loggées en info (pas en error) pour éviter le bruit dans les logs
|
||||
logger.log("info", `Erreur récupération ${collection}:`, error?.message || error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function updateRecordFromData(collection, recordID, data) {
|
||||
try {
|
||||
const record = await db.records.update(collection, recordID, data);
|
||||
|
||||
return record;
|
||||
} catch (error) {
|
||||
logger.log("error", error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function customFormatNumber(number, decimal, taux=false) {
|
||||
if (Number.isInteger(number)) {
|
||||
if (decimal == true) {
|
||||
return numeral(number).format("0,0.00");
|
||||
} else {
|
||||
return numeral(number).format("0,0");
|
||||
}
|
||||
} else {
|
||||
if (taux == true) {
|
||||
return numeral(number).format("0,0.000");
|
||||
} else {
|
||||
return numeral(number).format("0,0.00");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cleanDoubleSpaces(inputString) {
|
||||
return inputString.replace(/\s{2,}/g, " ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate une date ISO en format français (jj/mm/aaaa)
|
||||
* @param {string|Date} iso - Date au format ISO
|
||||
* @param {boolean} withTime - Inclure l'heure (défaut: false)
|
||||
* @returns {string} Date formatée (dd/mm/yyyy ou dd/mm/yyyy hh:mm)
|
||||
*/
|
||||
function fmtDateFR(iso, withTime = false) {
|
||||
if (!iso) return "NC";
|
||||
|
||||
const d = new Date(iso);
|
||||
if (isNaN(d.getTime())) return "NC";
|
||||
|
||||
const dd = String(d.getDate()).padStart(2, "0");
|
||||
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
||||
const yyyy = d.getFullYear();
|
||||
|
||||
if (!withTime) {
|
||||
return `${dd}/${mm}/${yyyy}`;
|
||||
}
|
||||
|
||||
const hh = String(d.getHours()).padStart(2, "0");
|
||||
const mi = String(d.getMinutes()).padStart(2, "0");
|
||||
return `${dd}/${mm}/${yyyy} ${hh}:${mi}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Échappe les caractères spéciaux XML
|
||||
* @param {string} s - Chaîne à échapper
|
||||
* @returns {string} Chaîne échappée
|
||||
*/
|
||||
function xmlEsc(s) {
|
||||
return String(s ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une cellule XML pour Excel
|
||||
* @param {string} v - Valeur de la cellule
|
||||
* @returns {string} Cellule XML formatée
|
||||
*/
|
||||
function cellXml(v) {
|
||||
return `<Cell><Data ss:Type="String">${xmlEsc(v)}</Data></Cell>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère une ligne XML pour Excel
|
||||
* @param {Array<string>} cells - Tableau de valeurs pour les cellules
|
||||
* @returns {string} Ligne XML formatée
|
||||
*/
|
||||
function rowXml(cells) {
|
||||
return `<Row>${cells.map(cellXml).join("")}</Row>`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRecordIdFromFieldValue,
|
||||
fetchInfoByCriteria,
|
||||
updateRecordFromData,
|
||||
cleanDoubleSpaces,
|
||||
customFormatNumber,
|
||||
fmtDateFR,
|
||||
xmlEsc,
|
||||
cellXml,
|
||||
rowXml,
|
||||
};
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
// services/parcoursService.js
|
||||
const { db } = require("../db/db-connect");
|
||||
const logger = require("../utils/logger");
|
||||
const globalService = require("../services/globalService");
|
||||
|
||||
/**
|
||||
* Récupère un parcours par son numéro (avec expand utiles)
|
||||
*/
|
||||
async function getParcoursByNumParcours(numParcours) {
|
||||
const criteria = {
|
||||
filter: `numParcours='${numParcours}'`,
|
||||
expand: [
|
||||
"dernierUtilisateur.region",
|
||||
"contrat",
|
||||
"contrat.client",
|
||||
"contrat.intermediaire"
|
||||
].join(",")
|
||||
};
|
||||
return globalService.fetchInfoByCriteria("parcours", criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full list (batch côté PocketBase). | Fetch l'ensemble de la BD via chunk "batch"
|
||||
* avec getFullList(collection, batchSize, options)
|
||||
*/
|
||||
async function getParcoursFullList({ filter, sort, expand, fields, batch = 500 }) {
|
||||
const options = {
|
||||
sort: sort || "-created",
|
||||
};
|
||||
|
||||
// Ajouter expand si défini
|
||||
if (expand) {
|
||||
options.expand = expand;
|
||||
}
|
||||
|
||||
// Ajouter fields si défini
|
||||
if (fields) {
|
||||
options.fields = fields;
|
||||
}
|
||||
|
||||
// Ajouter filter SEULEMENT s'il n'est pas vide (Pocketbase 0.7 rejette les filtres vides)
|
||||
if (filter && filter.trim() !== "") {
|
||||
options.filter = filter;
|
||||
}
|
||||
|
||||
// getFullList(collection, batchSize, options)
|
||||
return db.records.getFullList("parcours", batch, options);
|
||||
}
|
||||
/**
|
||||
* Pagination multi-régions + filtres/tri optionnels (server-side DataTables)
|
||||
* – Parcours une seule fois db par requête
|
||||
* @param {string[]} regions
|
||||
* @param {number} page
|
||||
* @param {number} perPage
|
||||
* @param {{filter?: string, sort?: string}} opts
|
||||
*/
|
||||
async function getParcoursByRegionsPage(regions = [], page = 1, perPage = 10, opts = {}) {
|
||||
try {
|
||||
let regFilter = "";
|
||||
if (Array.isArray(regions) && regions.length > 0) {
|
||||
const ors = regions.map(r => `dernierUtilisateur.region.nom = "${r}"`);
|
||||
regFilter = `(${ors.join(" || ")})`;
|
||||
}
|
||||
|
||||
const filter = [regFilter, opts.filter].filter(Boolean).join(" && ");
|
||||
|
||||
/**
|
||||
* Récupération des parcours avec expands nécessaires
|
||||
* Note: L'expand de contrat.client ne fonctionne pas toujours,
|
||||
* d'où la nécessité d'un fallback dans le contrôleur
|
||||
*/
|
||||
const list = await db.records.getList("parcours", page, perPage, {
|
||||
sort: opts.sort || "-created",
|
||||
filter: filter || "",
|
||||
expand: [
|
||||
"contrat",
|
||||
"contrat.client",
|
||||
"contrat.intermediaire",
|
||||
"dernierUtilisateur.region"
|
||||
].join(","),
|
||||
});
|
||||
|
||||
return {
|
||||
page: list.page,
|
||||
perPage: list.perPage,
|
||||
totalItems: list.totalItems,
|
||||
totalPages: list.totalPages,
|
||||
items: list.items,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
logger.log('error', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Création d'un parcours vide
|
||||
*/
|
||||
async function createNewEmptyParcours(numParcours) {
|
||||
try {
|
||||
const data = { ["numParcours"]: numParcours };
|
||||
const record = await db.records.create("parcours", data);
|
||||
if (record) {
|
||||
return record.id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
logger.log("error", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MAJ d'un champ d'un parcours
|
||||
*/
|
||||
async function updateFieldValueParcours(id, field, value) {
|
||||
try {
|
||||
const data = { [field]: value };
|
||||
const record = await db.records.update("parcours", id, data);
|
||||
if (record) {
|
||||
return record.id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
logger.log("error", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère le prochain numéro de parcours
|
||||
*/
|
||||
async function getNewParcoursNumber() {
|
||||
try {
|
||||
const list = await db.records.getList("parcours", 1, 1, { sort: "-numParcours" });
|
||||
const last = list?.items?.[0];
|
||||
if (!last?.numParcours) return null;
|
||||
|
||||
const numericValue = parseInt(String(last.numParcours).substring(1), 10);
|
||||
if (Number.isNaN(numericValue)) return null;
|
||||
|
||||
const next = numericValue + 1;
|
||||
return "P" + next.toString().padStart(9, "0");
|
||||
}
|
||||
catch (error) {
|
||||
logger.log("error", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Section détails profonds (contrat + fiche produit) --- //
|
||||
|
||||
/**
|
||||
* Récupère un parcours (via numParcours) avec les expands utiles pour détails.
|
||||
*/
|
||||
async function getParcoursForDetails(numParcours) {
|
||||
try {
|
||||
const list = await db.records.getList("parcours", 1, 1, {
|
||||
filter: `numParcours='${numParcours}'`,
|
||||
expand: [
|
||||
"contrat",
|
||||
"contrat.client",
|
||||
"contrat.intermediaire",
|
||||
"dernierUtilisateur.region"
|
||||
].join(","),
|
||||
});
|
||||
return list?.items?.[0] || null;
|
||||
}
|
||||
catch (e) {
|
||||
logger.log("error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mappe un libellé produit vers la collection PocketBase (à ajuster si changement de parcours).
|
||||
*/
|
||||
function mapProduitToCollection(produitRaw = "") {
|
||||
const p = String(produitRaw || "").trim().toUpperCase();
|
||||
const map = {
|
||||
"TPPC": "tppc",
|
||||
"RC": "rc",
|
||||
"FAC": "fac",
|
||||
};
|
||||
return map[p] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la fiche produit pour un contrat donné.
|
||||
* On tente d'abord par relation "contrat = contratId" si elle existe,
|
||||
* sinon fallback par "numContrat = x" si jamais la fiche stocke le numéro.
|
||||
*/
|
||||
async function getProduitRecordForContrat(contrat, opts = {}) {
|
||||
try {
|
||||
if (!contrat) return null;
|
||||
const collection = mapProduitToCollection(contrat.produit);
|
||||
if (!collection) return null;
|
||||
|
||||
// Tente via une relation directe "contrat" (champ le plus propre)
|
||||
try {
|
||||
const record = await db.records.getFirstListItem(collection, `contrat='${contrat.id}'`, {
|
||||
});
|
||||
if (record) return record;
|
||||
}
|
||||
catch (_) { /* ignore, on tente le fallback */ }
|
||||
|
||||
// Fallback
|
||||
if (contrat.numContrat) {
|
||||
try {
|
||||
const record = await db.records.getFirstListItem(collection, `numContrat='${contrat.numContrat}'`, {});
|
||||
if (record) return record;
|
||||
}
|
||||
catch (_) { /* ignore */ }
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
logger.log("error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* reformatage texte - a virer
|
||||
*/
|
||||
function escPB(s = "") {
|
||||
return String(s).replace(/"/g, '\\"');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Détails complets: parcours + contrat + fiche produit
|
||||
*/
|
||||
async function getDeepDetailsByNumParcours(numParcours) {
|
||||
try {
|
||||
const filter = `numParcours = "${escPB(numParcours)}"`;
|
||||
|
||||
const list = await db.records.getList("parcours", 1, 1, {
|
||||
filter,
|
||||
expand: [
|
||||
"contrat",
|
||||
"contrat.client",
|
||||
"contrat.intermediaire",
|
||||
"dernierUtilisateur.region",
|
||||
// produit lié
|
||||
"contrat.tppc",
|
||||
"contrat.rc",
|
||||
"contrat.fac",
|
||||
// sous-relations TPPC
|
||||
"contrat.tppc.tarif",
|
||||
"contrat.tppc.projet",
|
||||
].join(","),
|
||||
});
|
||||
|
||||
return list?.items?.[0] || null;
|
||||
} catch (e) {
|
||||
logger.log("error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getNewParcoursNumber,
|
||||
getParcoursByNumParcours,
|
||||
createNewEmptyParcours,
|
||||
updateFieldValueParcours,
|
||||
getParcoursByRegionsPage,
|
||||
getParcoursFullList,
|
||||
getParcoursForDetails,
|
||||
getProduitRecordForContrat,
|
||||
getDeepDetailsByNumParcours,
|
||||
mapProduitToCollection,
|
||||
};
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
const { db } = require('../db/db-connect');
|
||||
const logger = require('../utils/logger');
|
||||
const globalService = require("../services/globalService");
|
||||
|
||||
// ===== 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,
|
||||
getRcById,
|
||||
updateRc,
|
||||
createTarifRc,
|
||||
getTarifRcById,
|
||||
updateTarifRc,
|
||||
createProjetRc,
|
||||
getProjetRcById,
|
||||
updateProjetRc
|
||||
};
|
||||
|
|
@ -6,8 +6,7 @@
|
|||
"scripts": {
|
||||
"start": "nodemon ./src/server.js",
|
||||
"build": "pkg ./src/server.js -o EasyTransport",
|
||||
"test": "jest",
|
||||
"db" : "cd ./src/db && start cmd /c Lancement_Pocketbase.cmd"
|
||||
"test": "jest"
|
||||
},
|
||||
"pkg": {
|
||||
"assets": [
|
||||
|
|
@ -1,16 +1,8 @@
|
|||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
background-color: white;
|
||||
color: black;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: darkblue !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
|
@ -121,11 +113,6 @@ hr.form {
|
|||
padding: 0 5% !important;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.circle {
|
||||
cursor: pointer;
|
||||
width: 35px;
|
||||
|
|
@ -196,17 +183,6 @@ hr.form {
|
|||
padding: 5px 12px !important;
|
||||
}
|
||||
|
||||
.chip-info{
|
||||
padding: 5px;
|
||||
width: 30%;
|
||||
text-align: center;
|
||||
background: darkblue;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#goodParcoursModal {
|
||||
width: 80%;
|
||||
max-height: 80%;
|
||||
|
|
@ -242,10 +218,7 @@ hr.form {
|
|||
content: none;
|
||||
}
|
||||
|
||||
#selectHistory,
|
||||
#selectHistory ul,
|
||||
#selectHistory li,
|
||||
#selectHistory span {
|
||||
#selectHistory, #selectHistory ul, #selectHistory li, #selectHistory span {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
|
|
@ -301,14 +274,6 @@ hr.form {
|
|||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.moyenTransportTarif {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.moyenTransportTarif td i {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.select-chip {
|
||||
display: none;
|
||||
color: darkblue
|
||||
|
|
@ -343,13 +308,6 @@ hr.form {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.step {
|
||||
background-color: #f44336 ;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.modulo-resume {
|
||||
font-size: 20px;
|
||||
font-weight: normal;
|
||||
|
|
@ -369,7 +327,6 @@ hr.form {
|
|||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
margin-bottom: 20px;
|
||||
align-items: flex-start;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -439,7 +396,8 @@ a.grille-garanties:hover {
|
|||
|
||||
.modalAlert .modal-content .modalRed {
|
||||
list-style-type: square;
|
||||
color: red
|
||||
color : red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modalAlert .modal-content h4 {
|
||||
|
|
@ -450,110 +408,6 @@ a.grille-garanties:hover {
|
|||
color : white
|
||||
}
|
||||
|
||||
#rcProjetBlockingSummary,
|
||||
#rcTarifBlockingSummary {
|
||||
margin: 1rem 0 1.5rem 0;
|
||||
}
|
||||
|
||||
.rc-blocking-summary {
|
||||
display: none;
|
||||
border-left: 6px solid #c62828;
|
||||
background-color: #ffebee;
|
||||
color: #b71c1c;
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.rc-blocking-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.rc-blocking-list {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.rc-blocking-list li {
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.rc-field-label {
|
||||
display: block;
|
||||
color: #1a237e;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.rc-has-floating-label {
|
||||
position: relative;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.rc-has-floating-label .rc-field-label.rc-floating-label {
|
||||
position: static;
|
||||
top: auto;
|
||||
left: auto;
|
||||
margin: 0 0 6px 0;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
color: #1a237e;
|
||||
pointer-events: none;
|
||||
transition: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
.rc-has-floating-label .rc-field-label.rc-floating-label.active {
|
||||
top: auto;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
color: #1a237e;
|
||||
}
|
||||
|
||||
.rc-tarifettes-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.rc-three-col-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.rc-three-col-grid > [class*="col"] {
|
||||
width: 100% !important;
|
||||
margin-left: 0 !important;
|
||||
padding: 0 !important;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.rc-three-col-grid .card {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rc-three-col-grid .card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.rc-equal-card-row > [class*="col"] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.rc-equal-card-row .card {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rc-equal-card-row .card-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#modalTarifCom span.material-icons {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
|
@ -571,106 +425,6 @@ a.grille-garanties:hover {
|
|||
z-index: 20 !important;
|
||||
}
|
||||
|
||||
.flux-card {
|
||||
width: 41rem !important;
|
||||
padding: 2rem 1.4rem;
|
||||
border-radius: 20px;
|
||||
box-shadow: 5px 5px 7px -3px rgba(0, 0, 0, 0.173);
|
||||
}
|
||||
|
||||
.flux-card .flux-card-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin: 0 0 20px 0;
|
||||
color: darkblue
|
||||
}
|
||||
|
||||
#div-fluxAchats {
|
||||
background-color: #d5ecd4;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
#div-fluxVentes {
|
||||
background-color: #ddddfb;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
#div-fluxAchats:hover {
|
||||
background-color: #e5f0e5;
|
||||
}
|
||||
|
||||
#div-fluxVentes:hover {
|
||||
background-color: #f0f0ff;
|
||||
}
|
||||
|
||||
.listNatureMar input:disabled {
|
||||
color: black !important
|
||||
}
|
||||
|
||||
.flux-card hr {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.flux-card hr.divider {
|
||||
margin: 3rem 0px;
|
||||
border-color: #9b9b9b17;
|
||||
}
|
||||
|
||||
.flux-card .select-wrapper {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.listNatureMar {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
#div-fluxAchats .listNatureMar>div {
|
||||
background-color: #aad8a7;
|
||||
}
|
||||
|
||||
#div-fluxVentes .listNatureMar>div {
|
||||
background-color: rgb(193, 189, 236);
|
||||
}
|
||||
|
||||
.listNatureMar>div {
|
||||
border-radius: 5px;
|
||||
margin: 5px 0;
|
||||
padding: 6px 0;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.listNatureMar .btn-floating {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.flux-card .input-field {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.checkedCheckbox::before {
|
||||
background-color: rgba(0, 0, 0, 0.42) !important;
|
||||
border-color: #949494 !important;
|
||||
|
||||
}
|
||||
|
||||
.propositions-flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.propositions-flex>div {
|
||||
width: 100%;
|
||||
margin: 0px 20px
|
||||
}
|
||||
|
||||
#row-sinistresAsIf > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.flex-adaptable {
|
||||
flex-direction: column;
|
||||
|
|
@ -758,49 +512,3 @@ a.grille-garanties:hover {
|
|||
@keyframes l13 {
|
||||
100% { transform: rotate(1turn); }
|
||||
}
|
||||
|
||||
/* Message d'erreur du loader */
|
||||
#error-message {
|
||||
display: none;
|
||||
color: #ff4444;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
white-space: pre-line;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Message de timeout du loader */
|
||||
#timeout-message {
|
||||
display: none;
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
color: #d0d0d0;
|
||||
font-size: 14px;
|
||||
max-width: 420px;
|
||||
line-height: 1.6;
|
||||
padding: 0 20px;
|
||||
animation: fadeIn 0.5s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Lien cliquable pour annuler le chargement */
|
||||
#cancel-loading-link {
|
||||
color: #66B2FF;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
text-decoration-thickness: 1px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#cancel-loading-link:hover {
|
||||
color: #90CAF9;
|
||||
text-decoration-thickness: 2px;
|
||||
text-shadow: 0 0 10px rgba(102, 178, 255, 0.6);
|
||||
}
|
||||
|
|
@ -0,0 +1,208 @@
|
|||
/* Historique Parcours */
|
||||
body>main>div>div.section.center-align {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
body>main>div>div.section.center-align>div.container {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#historiqueParcours_wrapper {
|
||||
width: 100% !important;
|
||||
overflow-x: auto !important;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
table#historiqueParcours {
|
||||
width: 150%;
|
||||
border-collapse: collapse !important;
|
||||
border: 2px solid darkblue !important;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#historiqueParcours th,
|
||||
#historiqueParcours td {
|
||||
color: black;
|
||||
text-align: left;
|
||||
font-size: 11px;
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
table.dataTable thead th {
|
||||
padding-right: 18px !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
/* Div pour encapsuler le texte dans les cellules d'en-tête */
|
||||
table.dataTable thead th>div {
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
#historiqueParcours_filter label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_filter input[type="search"] {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid #26a69a;
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
height: 3rem;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
margin: 0 0 8px 0;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
box-sizing: content-box;
|
||||
transition: box-shadow .3s, border .3s, -webkit-box-shadow .3s;
|
||||
}
|
||||
|
||||
#historiqueParcours_length>label {
|
||||
font-size: 14px;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.dataTables_length select[name="historiqueParcours_length"] {
|
||||
display: block !important;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
padding: 8px;
|
||||
margin: 0 0.5em;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 5px;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
/* Style Input search by row */
|
||||
#historiqueParcours>thead>tr:nth-child(2)>th>input {
|
||||
font-size: 13px !important;
|
||||
padding: 6px !important;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* icone de tri sur les colonnes */
|
||||
table.dataTable thead .sorting,
|
||||
table.dataTable thead .sorting_asc,
|
||||
table.dataTable thead .sorting_desc {
|
||||
background-position: right center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* Ajouter un espacement entre le texte et l'icône */
|
||||
table.dataTable thead .sorting:before,
|
||||
table.dataTable thead .sorting_asc:before,
|
||||
table.dataTable thead .sorting_desc:before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* boutons de navigationw */
|
||||
.dataTables_wrapper .dataTables_paginate .paginate_button {
|
||||
background-color: white !important;
|
||||
border: darkblue solid 1.5px !important;
|
||||
color: black !important;
|
||||
padding: 6px 12px !important;
|
||||
margin: 0 2px !important;
|
||||
cursor: pointer !important;
|
||||
border-radius: 4px !important;
|
||||
transition: background-color 0.3s, color 0.3s, border-color 0.3s !important;
|
||||
}
|
||||
|
||||
#historiqueParcours_paginate>span>a.paginate_button.current,
|
||||
.dataTables_wrapper .dataTables_paginate:hover .paginate_button:hover {
|
||||
background: none !important;
|
||||
background-color: darkblue !important;
|
||||
border-color: white !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* NC value */
|
||||
td.nc-value {
|
||||
color: lightgray !important;
|
||||
}
|
||||
|
||||
/* Les bouton pour le filtres et les extraction */
|
||||
#divBtnFilter {
|
||||
display: inline-grid;
|
||||
width: 100%;
|
||||
grid-template-columns: auto;
|
||||
column-gap: 3%;
|
||||
row-gap: 10%;
|
||||
}
|
||||
|
||||
#checkRegionAdmin {
|
||||
grid-column: 1 / span 3;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
#divToggleSearch {
|
||||
width: 300px;
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
#divExtractAll {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
#divExtractFilter {
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
#divBtnFilter button {
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(16, 0, 75, 0.2),
|
||||
0 4px 8px rgba(16, 0, 75, 0.1);
|
||||
}
|
||||
|
||||
/* Bouton Reprendre */
|
||||
#btnReprendre,
|
||||
#btnGenerate {
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 0px 15px;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
#btnReprendre i {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* checkbox Filter Region Admin */
|
||||
#checkRegionAdmin {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
[class^="checkbox-wrapper-"] {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#checkRegionAdmin input[type="checkbox"] {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#checkRegionAdmin label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/* Overlay loader (css pris du site https://css-loaders.com/)*/
|
||||
#loader-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: linear-gradient(
|
||||
rgba(10, 20, 60, 0.2),
|
||||
rgba(0, 0, 0, 0.4)
|
||||
);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
opacity: 0;
|
||||
backdrop-filter: blur(0px);
|
||||
pointer-events: none;
|
||||
transition: opacity 0.5s ease, backdrop-filter 0.5s ease;
|
||||
}
|
||||
#loader-overlay.active {
|
||||
opacity: 1;
|
||||
backdrop-filter: blur(3px);
|
||||
pointer-events: all;
|
||||
}
|
||||
#loader-overlay.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Spinner wrapper (fade/slide) */
|
||||
.loader-spin-wrap {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
||||
transition-delay: 0.5s; /* apparaît après 0.5s */
|
||||
}
|
||||
#loader-overlay.active .loader-spin-wrap {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Spinner circulaire */
|
||||
.loader-spin {
|
||||
width: 50px;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 50%;
|
||||
mask:1;
|
||||
background:
|
||||
radial-gradient(farthest-side,darkblue 94%,transparent) top/8px 8px no-repeat,
|
||||
conic-gradient(transparent 30%,darkblue);
|
||||
-webkit-mask: radial-gradient(farthest-side,transparent calc(100% - 8px),#000 0);
|
||||
animation: l13 1s infinite linear;
|
||||
}
|
||||
@keyframes l13 {
|
||||
100% { transform: rotate(1turn); }
|
||||
}
|
||||
|
||||
/* Erreur */
|
||||
#error-message {
|
||||
display: none;
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
white-space: pre-line;
|
||||
}
|
||||