total FAC TTPC RC

This commit is contained in:
Alexis Burnaz 2026-06-11 17:07:54 +02:00
parent 7b484f34c9
commit 3ad5d2f3d3
53 changed files with 18995 additions and 2897 deletions

View File

@ -1,5 +1,6 @@
#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
PORT=8082

42
ecole/.gitignore vendored
View File

@ -1,33 +1,13 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Classique
package-lock.json
# Logs
logs/
# Ignore files type
*.log
*.env
*.exe
*.wbk
*.cmd
*~*
# Environment variables
.env
.env.local
.env.*.local
# OS files
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Build outputs
dist/
build/
# Ignore directory
**/vbs/
**/node_modules/
**/logs/
**/.vscode/

2
ecole/README.md Normal file
View File

@ -0,0 +1,2 @@
Description
EasyTransport est une application en cours de transition technologique, destinée à la plateforme AxA IARD Transport. Le backend est écrit en Node.js avec une base de données embarquée PocketBase, et le frontend utilise EJS et Materialize CSS. L'application propose divers modules tels que la tarification, la génération de contrats à partir de formulaires, l'authentification via JWT, et la génération d'attestations.

3818
ecole/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,13 @@
"scripts": {
"start": "nodemon ./src/server.js",
"build": "pkg ./src/server.js -o EasyTransport",
"test": "jest"
"test": "jest",
"db" : "cd ./src/db && start cmd /c Lancement_Pocketbase.cmd"
},
"pkg": {
"assets": ["public/**"]
"assets": [
"public/**"
]
},
"keywords": [],
"author": "cyril.ducaffy@axa.fr",

View File

@ -5,7 +5,12 @@ body {
color-scheme: light;
}
h1, h2, h3, h4, h5, h6 {
h1,
h2,
h3,
h4,
h5,
h6 {
color: darkblue !important;
font-weight: bold;
}
@ -116,6 +121,11 @@ hr.form {
padding: 0 5% !important;
}
.center {
display: flex;
justify-content: center
}
.circle {
cursor: pointer;
width: 35px;
@ -232,7 +242,10 @@ hr.form {
content: none;
}
#selectHistory, #selectHistory ul, #selectHistory li, #selectHistory span {
#selectHistory,
#selectHistory ul,
#selectHistory li,
#selectHistory span {
font-size: 13px !important;
}
@ -288,6 +301,14 @@ hr.form {
padding-right: 10px;
}
.moyenTransportTarif {
margin-bottom: 30px;
}
.moyenTransportTarif td i {
padding-right: 10px;
}
.select-chip {
display: none;
color: darkblue
@ -322,6 +343,13 @@ 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;
@ -341,6 +369,7 @@ hr.form {
flex-wrap: wrap;
justify-content: space-evenly;
margin-bottom: 20px;
align-items: flex-start;
}
@ -381,7 +410,7 @@ hr.form {
outline-offset: 3px;
}
.garAdd .card-content{
.garAdd .card-content {
display: flex;
align-items: flex-start;
justify-content: flex-start;
@ -389,29 +418,28 @@ hr.form {
}
.garAdd .card-title {
padding: 10px;
padding: 10px;
}
form h5 {
margin : 0px 0px 30px 0px
margin: 0px 0px 30px 0px
}
a.grille-garanties {
width : 40px ;
height : 40px ;
width: 40px;
height: 40px;
padding: 2px 0 0 0;
border-radius: 100%;
background-color: #F44336;
}
a.grille-garanties:hover{
a.grille-garanties:hover {
background-color: #be3026;
}
.modalAlert .modal-content .modalRed {
list-style-type: square;
color : red;
font-weight: bold;
color: red
}
.modalAlert .modal-content h4 {
@ -419,7 +447,7 @@ a.grille-garanties:hover{
}
.modalAlert .modal-footer a {
color : white
color: white
}
#rcProjetBlockingSummary,
@ -539,10 +567,110 @@ a.grille-garanties:hover{
padding: 0 10px;
}
#rowExtensionsGarantie .dropdown-content.select-dropdown.multiple-select-dropdown{
#rowExtensionsGarantie .dropdown-content.select-dropdown.multiple-select-dropdown {
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;

View File

@ -42,25 +42,99 @@ table.dataTable thead th>div {
left: 0 !important;
}
#historiqueParcours_filter label {
display: flex;
align-items: center;
#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: transparent;
border: none;
border-bottom: 1px solid #26a69a;
border-radius: 0;
background-color: #fff;
border: 1px solid #e0e0e0;
border-radius: 42px;
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;
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 {
@ -83,7 +157,7 @@ table.dataTable thead th>div {
width: 60px;
}
/* Style Input search by row */
/* Style Input recherche par ligne */
#historiqueParcours>thead>tr:nth-child(2)>th>input {
font-size: 13px !important;
padding: 6px !important;
@ -105,7 +179,7 @@ table.dataTable thead .sorting_desc:before {
content: "";
}
/* boutons de navigationw */
/* boutons de navigation */
.dataTables_wrapper .dataTables_paginate .paginate_button {
background-color: white !important;
border: darkblue solid 1.5px !important;
@ -145,10 +219,31 @@ td.nc-value {
}
#divToggleSearch {
width: 300px;
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 {
@ -184,7 +279,7 @@ td.nc-value {
margin: 0;
}
/* checkbox Filter Region Admin */
/* checkbox Filter Region Admin a supprimer probablement*/
#checkRegionAdmin {
border: 1px solid #ccc;
padding: 10px;
@ -205,4 +300,69 @@ td.nc-value {
#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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -181,4 +181,263 @@ async function loadContrat(idContrat) {
} 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, '&lt;').replace(/>/g, '&gt;');
// 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|&nbsp;/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 + ' €';
}

File diff suppressed because it is too large Load Diff

View File

@ -374,5 +374,26 @@ const validationRules = {
},
commentaire: {
required: true
},
//TARIF FAC
// franchiseTousCas: {
// range: {min: 0, max: 5000},
// errorMsg: "La franchise Tous Cas est inférieure à 1 ou supérieure à 5 000 €.",
// }
montant: {
range: {min: 0, max: 1000000},
errorMsg: "Le montant à Garantir est inférieur à 1 ou supérieur à 1 000 000 €.",
},
montantAchats: {
range: {min: 0, max: 1000000},
errorMsg: "Le montant à Garantir est inférieur à 1 ou supérieur à 1 000 000 €.",
},
montantVentes: {
range: {min: 0, max: 1000000},
errorMsg: "Le montant à Garantir est inférieur à 1 ou supérieur à 1 000 000 €.",
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -0,0 +1,464 @@
/**
*
* RC SYNCHRONIZATION UTILITIES
*
*
* Ce module contient toutes les fonctions utilitaires pour la synchronisation
* bidirectionnelle entre les formulaires Tarif RC et Projet RC.
*
* @author AXA Transport Team
* @version 2.0.0
* @since 2026-02-17
*/
(function(window) {
'use strict';
// ═══════════════════════════════════════════════════════════════════════
// CONSTANTES
// ═══════════════════════════════════════════════════════════════════════
/**
* Liste exhaustive des champs qui impactent le calcul du tarif.
* Si l'un de ces champs est modifié dans le projet, un modal
* demandera à l'utilisateur de retourner au tarif.
*
* @constant {Array<string>}
*/
const TARIF_IMPACTING_FIELDS = [
// Chiffre d'affaires et type de contrat
'ca', 'chiffreAffaires', 'CA',
'typeCotisation', 'cotisation',
'nombreVehicules', 'nbVehicules',
// Activités RCC
'checkVoiturier', 'capitalVoiturier', 'actVoiturier',
'checkCommissionnaire', 'capitalCommissionnaire', 'actMultimodal',
'checkDemenageur', 'capitalDemenageur',
'checkLogistique', 'capitalLogistique',
'checkAutocariste', 'capitalAutocariste',
'checkAutres', 'capitalAutres',
// RCE
'checkRCE', 'autresRC',
// Activités complémentaires
'actComplVoiturier', 'actComplCommissionnaire', 'actComplDemenageur', 'actComplLogistique',
'activitesVoiturier', 'activitesCommissionnaire', 'activitesDemenageur', 'activitesLogistique',
// Marchandises
'marchandisesVoiturier', 'marchandisesCommissionnaire', 'marchandisesDemenageur',
'marchandisesLogistique', 'marchandisesAutocariste', 'marchandisesAutres',
'marOrdinaire', 'marRoulant', 'marEngins', 'marRoulantDem', 'marMobilerUsag',
'marPerissable', 'marAnimaux', 'marCiterne', 'marBeton', 'marExceptionnels', 'marVrac',
// Zones géographiques
'zone1', 'zone2', 'zone3', 'zone4', 'zone5', 'zone6',
// Extensions de garantie RCC
'extRCCModifCalArrim', 'extRCCFerroutage', 'extRCCFraisRecons',
'extRCCConfie', 'typeExtConfies', 'extRCCTPPC', 'extRCCRegie', 'extRCCSansMontageDemontage',
'checkDomImmat', 'capitalDomImmat', 'checkContConf', 'capitalContConf',
'checkDiffInv', 'capitalDiffInv', 'checkTPPC', 'capitalTPPC', 'vehiculesTPPC',
// Extensions de garantie RCE
'extRCEBraDebra', 'extRCEMontageDemontage',
// Garanties additionnelles
'checkStationLavage', 'checkGarageInterne', 'checkCSE', 'checkPJ', 'pj',
// Sinistralité
'sinistre', 'nbSinistres3ans', 'montantSinistres3ans'
];
// ═══════════════════════════════════════════════════════════════════════
// HELPERS - MANIPULATION DE VALEURS
// ═══════════════════════════════════════════════════════════════════════
/**
* Convertit une valeur en nombre en gérant les formats français et internationaux.
* Gère les espaces, virgules, points, et valeurs nulles/undefined.
*
* @param {string|number|null|undefined} x - Valeur à convertir
* @returns {number} Nombre converti ou 0 si impossible
*
* @example
* toNumber("1 234,56") // 1234.56
* toNumber("1.234,56") // 1234.56
* toNumber("1,234.56") // 1234.56
* toNumber(null) // 0
*/
function toNumber(x) {
if (x == null) return 0;
let value = String(x).trim();
if (!value) return 0;
value = value
.replace(/\s/g, '')
.replace(/[^\d.,-]/g, '');
if (!value) return 0;
const isNegative = value.startsWith('-');
value = value.replace(/-/g, '');
if (isNegative && value) {
value = '-' + value;
}
const hasComma = value.includes(',');
const hasDot = value.includes('.');
if (hasComma) {
value = value.replace(/\./g, '').replace(/,/g, '.');
} else if (hasDot) {
const dotMatches = value.match(/\./g);
const dotCount = dotMatches ? dotMatches.length : 0;
if (dotCount > 1) {
const parts = value.split('.');
const lastSegment = parts[parts.length - 1];
if (lastSegment.length === 3) {
value = parts.join('');
} else {
value = parts.slice(0, -1).join('') + '.' + lastSegment;
}
}
}
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : 0;
}
/**
* Récupère la valeur d'un élément par son ID de manière flexible.
* Gère les différents types d'éléments (input, select, textarea, etc.)
* et les cas l'ID contient des caractères spéciaux.
*
* @param {string} id - ID de l'élément
* @returns {HTMLElement|null} Élément trouvé ou null
*
* @example
* const element = getElementByIdFlexible("my-element");
*/
function getElementByIdFlexible(id) {
if (!id) return null;
const direct = document.getElementById(id);
if (direct) return direct;
try {
return document.querySelector(`[id="${id.replace(/"/g, '\\"')}"]`);
} catch (err) {
return null;
}
}
/**
* Récupère la valeur d'un champ de formulaire de manière sécurisée.
* Gère les inputs, selects, textareas, checkboxes, et contenus textuels.
*
* @param {string} elementId - ID de l'élément
* @returns {string|number|boolean|null} Valeur du champ
*
* @example
* getValue("ca") // "100000"
* getValue("checkPJ") // true
*/
function getValue(elementId) {
const element = getElementByIdFlexible(elementId);
if (!element) return null;
if (element.type === 'checkbox') {
return element.checked;
} else if (element.type === 'radio') {
const checked = document.querySelector(`input[name="${element.name}"]:checked`);
return checked ? checked.value : null;
} else if (element.tagName === 'SELECT') {
return element.value;
} else if (element.value !== undefined) {
return element.value;
} else {
return element.textContent || element.innerText || null;
}
}
/**
* Définit la valeur d'un champ de formulaire.
* Gère automatiquement le type de champ et met à jour l'interface.
*
* @param {string} elementId - ID de l'élément
* @param {any} value - Valeur à définir
*
* @example
* setValue("ca", 100000);
* setValue("checkPJ", true);
*/
function setValue(elementId, value) {
const element = getElementByIdFlexible(elementId);
if (!element) {
console.warn(`Élément non trouvé: ${elementId}`);
return;
}
if (element.type === 'checkbox') {
element.checked = Boolean(value);
} else if (element.type === 'radio') {
const radio = document.querySelector(`input[name="${element.name}"][value="${value}"]`);
if (radio) radio.checked = true;
} else if (element.tagName === 'SELECT') {
element.value = value;
// Réinitialiser Materialize select si présent
if (window.M && window.M.FormSelect) {
const instance = window.M.FormSelect.getInstance(element);
if (instance) instance.destroy();
window.M.FormSelect.init(element);
}
} else if (element.value !== undefined) {
element.value = value;
} else {
element.textContent = value;
}
}
// ═══════════════════════════════════════════════════════════════════════
// COMPARAISON DE DONNÉES
// ═══════════════════════════════════════════════════════════════════════
/**
* Compare deux tableaux pour vérifier leur égalité.
* Effectue une comparaison profonde élément par élément.
*
* @param {Array} arr1 - Premier tableau
* @param {Array} arr2 - Deuxième tableau
* @returns {boolean} true si les tableaux sont égaux
*
* @example
* arraysEqual([1,2,3], [1,2,3]) // true
* arraysEqual([1,2], [1,2,3]) // false
*/
function arraysEqual(arr1, arr2) {
if (!Array.isArray(arr1) || !Array.isArray(arr2)) return false;
if (arr1.length !== arr2.length) return false;
const sorted1 = [...arr1].sort();
const sorted2 = [...arr2].sort();
return sorted1.every((val, idx) => val === sorted2[idx]);
}
/**
* Compare deux valeurs en tenant compte de leur type.
* Gère les tableaux, objets, null, undefined, et valeurs primitives.
*
* @param {any} value1 - Première valeur
* @param {any} value2 - Deuxième valeur
* @returns {boolean} true si les valeurs sont égales
*
* @example
* valuesEqual([1,2], [2,1]) // true (ordre indépendant)
* valuesEqual(null, undefined) // true
* valuesEqual(100, "100") // true (conversion automatique)
*/
function valuesEqual(value1, value2) {
// Normaliser null et undefined
if (value1 == null && value2 == null) return true;
if (value1 == null || value2 == null) return false;
// Comparer les tableaux
if (Array.isArray(value1) && Array.isArray(value2)) {
return arraysEqual(value1, value2);
}
// Comparer les objets
if (typeof value1 === 'object' && typeof value2 === 'object') {
return JSON.stringify(value1) === JSON.stringify(value2);
}
// Comparer les nombres (avec conversion)
if (!isNaN(value1) && !isNaN(value2)) {
return toNumber(value1) === toNumber(value2);
}
// Comparaison standard
return value1 === value2;
}
// ═══════════════════════════════════════════════════════════════════════
// DÉTECTION DE CHANGEMENTS IMPACTANTS
// ═══════════════════════════════════════════════════════════════════════
/**
* Vérifie si un champ donné impacte le calcul du tarif.
* Se base sur la liste TARIF_IMPACTING_FIELDS.
*
* @param {string} fieldName - Nom du champ
* @returns {boolean} true si le champ impacte le tarif
*
* @example
* isFieldImpactingTarif("ca") // true
* isFieldImpactingTarif("dateEffet") // false
*/
function isFieldImpactingTarif(fieldName) {
return TARIF_IMPACTING_FIELDS.some(field =>
fieldName.includes(field) || field.includes(fieldName)
);
}
/**
* Vérifie si un changement de valeur impacte le tarif.
* Compare la nouvelle valeur avec les données originales du tarif.
*
* @param {string} fieldName - Nom du champ modifié
* @param {any} newValue - Nouvelle valeur
* @param {Object} tarifOriginalData - Données originales du tarif
* @returns {boolean} true si le changement impacte le tarif
*
* @example
* const impacted = isChangeImpactingTarif("ca", 200000, tarifData);
* if (impacted) showReturnToTarifModal();
*/
function isChangeImpactingTarif(fieldName, newValue, tarifOriginalData) {
// Vérifier si le champ est dans la liste des champs impactants
if (!isFieldImpactingTarif(fieldName)) {
return false;
}
// Si pas de données originales, pas d'impact possible
if (!tarifOriginalData) {
return false;
}
// Récupérer la valeur originale
const originalValue = tarifOriginalData[fieldName];
// Comparer les valeurs
return !valuesEqual(newValue, originalValue);
}
// ═══════════════════════════════════════════════════════════════════════
// MODAL DE RETOUR AU TARIF
// ═══════════════════════════════════════════════════════════════════════
/**
* Affiche le modal demandant à l'utilisateur de retourner au tarif.
* Ce modal s'affiche quand une modification dans le projet impacte
* le calcul du tarif.
*
* @param {string} [fieldName] - Nom du champ modifié (optionnel, pour info)
*
* @example
* showReturnToTarifModal("ca");
*/
function showReturnToTarifModal(fieldName) {
const modalId = 'modalRetourTarif';
let modal = document.getElementById(modalId);
// Créer le modal s'il n'existe pas
if (!modal) {
modal = createReturnToTarifModal();
document.body.appendChild(modal);
}
// Mettre à jour le message si un champ est spécifié
if (fieldName) {
const messageEl = modal.querySelector('#modalRetourTarifMessage');
if (messageEl) {
messageEl.innerHTML = `
Vous avez modifié <strong>"${fieldName}"</strong> qui impacte le calcul du tarif.
<br><br>
Vous devez retourner sur le formulaire Tarif pour recalculer et valider le nouveau tarif.
`;
}
}
// Ouvrir le modal
if (window.M && window.M.Modal) {
const instance = window.M.Modal.getInstance(modal) || window.M.Modal.init(modal);
instance.open();
}
}
/**
* Crée l'élément DOM du modal de retour au tarif.
*
* @returns {HTMLElement} Élément modal créé
* @private
*/
function createReturnToTarifModal() {
const modal = document.createElement('div');
modal.id = 'modalRetourTarif';
modal.className = 'modal';
modal.innerHTML = `
<div class="modal-content">
<h5> Modification impactant le tarif</h5>
<p id="modalRetourTarifMessage">
Vous avez modifié une donnée qui impacte le calcul du tarif.
<br><br>
<strong>Vous devez retourner sur le formulaire Tarif pour recalculer et valider le nouveau tarif.</strong>
</p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-close waves-effect waves-red btn-flat">Annuler</a>
<a href="#!" class="waves-effect waves-green btn" onclick="window.RCSync.navigateToTarif()">
Aller au Tarif
</a>
</div>
`;
return modal;
}
/**
* Navigate vers l'onglet Tarif depuis le Projet.
*
* @example
* navigateToTarif();
*/
function navigateToTarif() {
// Fermer le modal
const modal = document.getElementById('modalRetourTarif');
if (modal && window.M) {
const instance = window.M.Modal.getInstance(modal);
if (instance) instance.close();
}
// Naviguer vers le tarif
const numParcours = new URLSearchParams(window.location.search).get('numParcours');
if (numParcours) {
window.location.href = `/navParcours?numParcours=${numParcours}&submenu=tarif`;
}
}
// ═══════════════════════════════════════════════════════════════════════
// EXPORT PUBLIC
// ═══════════════════════════════════════════════════════════════════════
/**
* API publique du module RC Sync.
* Toutes les fonctions exportées ici sont accessibles via window.RCSync.
*/
window.RCSync = {
// Helpers
toNumber,
getValue,
setValue,
getElementByIdFlexible,
// Comparaison
arraysEqual,
valuesEqual,
// Détection changements
isFieldImpactingTarif,
isChangeImpactingTarif,
// Modal
showReturnToTarifModal,
navigateToTarif,
// Constantes
TARIF_IMPACTING_FIELDS
};
console.log('✅ RC Sync Utils loaded');
})(window);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,908 @@
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
};

View File

@ -7,66 +7,66 @@ const objModMar = {
tarif: "Bennes",
modulo: 0.8
},
"marCiternes": {
tarif : "Citernes",
projet : "Transports en Citerne",
modulo : 1
"marCiternes": {
tarif: "Citernes",
projet: "Transports en Citerne",
modulo: 1
},
"marDenreesHorsTemp": {
tarif : "Denrées périssables hors température dirigée",
modulo : 1
tarif: "Denrées périssables hors température dirigée",
modulo: 1
},
"marDenreesSousTemp" : {
tarif : "Denrées périssables sous température dirigée",
projet : "Transports au moyen d'un véhicule à température dirigée",
modulo : 1.1
"marDenreesSousTemp": {
tarif: "Denrées périssables sous température dirigée",
projet: "Transports au moyen d'un véhicule à température dirigée",
modulo: 1.1
},
"marEngins" : {
tarif : "Engins (chantier, construction…)",
modulo : 1
"marEngins": {
tarif: "Engins (chantier, construction…)",
modulo: 1
},
"marAuto" : {
tarif : "Véhicules automobiles",
modulo : 1.1
"marAuto": {
tarif: "Véhicules automobiles",
modulo: 1.1
},
"marRisques" : {
tarif : "Risques exclus et réservés",
modulo : 1.5
"marRisques": {
tarif: "Risques exclus et réservés",
modulo: 1.5
},
"marAnimaux" : {
tarif : "Animaux vivants",
projet : "Transport d'animaux vivants",
modulo : 2
"marAnimaux": {
tarif: "Animaux vivants",
projet: "Transport d'animaux vivants",
modulo: 2
},
"marFranchise" : {
projet : "Franchise véhicule transporté",
"marFranchise": {
projet: "Franchise véhicule transporté",
}
}
const objMarEnExpo = {
"marOrdinaires": {
nom : "Marchandises Ordinaires",
modulo : 0.2
nom: "Marchandises Ordinaires",
modulo: 0.2
},
"marRisques" : {
nom : "Marchandises à Risques",
modulo : 0.25
"marRisques": {
nom: "Marchandises à Risques",
modulo: 0.25
},
"marPerissables" : {
nom : "Marchandises Périssables",
modulo : 0.35
"marPerissables": {
nom: "Marchandises Périssables",
modulo: 0.35
},
"lourds" : {
nom : "Matériels et Equipements lourds",
modulo : 0.35
"lourds": {
nom: "Matériels et Equipements lourds",
modulo: 0.35
},
"marExclues" : {
nom : "Marchandises exclues sauf accord aux CP",
modulo : 0.35
"marExclues": {
nom: "Marchandises exclues sauf accord aux CP",
modulo: 0.35
},
"marRoulants" : {
nom : "Véhicules roulants",
modulo : 0.35
"marRoulants": {
nom: "Véhicules roulants",
modulo: 0.35
}
}
@ -91,18 +91,18 @@ const objModSinistre = {
}
const listTypeVehicule = {
"leger" : "Véhicule-léger",
"benne" : "Benne",
"fourgon" : "Fourgon",
"utilitaire" : "Véhicule utilitaire",
"remorque" : "Remorque",
"leger": "Véhicule-léger",
"benne": "Benne",
"fourgon": "Fourgon",
"utilitaire": "Véhicule utilitaire",
"remorque": "Remorque",
"porteEngins": "Porte-Engins",
"camion": "Camion",
"semiRemorque": "Semi-Remorque",
}
const objPrimeMini ={
const objPrimeMini = {
"IAC + Vol": 125,
"IAC + HIAC": 110
}
@ -865,4 +865,3 @@ module.exports = {
objModFlotte,
objMarEnExpo: objMarEnExpo
};

View File

@ -1,12 +1,28 @@
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 });
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) => {
@ -14,9 +30,62 @@ router.get("/read/id/:id", async (req, res) => {
try {
const fac = await facService.getFACbyId(id);
res.json({ valid: Boolean(fac), fac });
res.json({valid: Boolean(fac), fac});
} catch (error) {
res.status(500).json({ valid: false, error: "Internal Server 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"});
}
});

View File

@ -9,10 +9,16 @@ 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"),
@ -20,25 +26,27 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
);
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true });
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 || {};
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 (fac[transport] !== "") {
if (projet[transport] !== "") {
selectedTransportList.push(transport)
}
})
} catch (error) { }
} catch (error) {}
var transportListVirguleEt
@ -63,10 +71,10 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
const listAssAdd = [];
try {
fac.assureAdditionnel.forEach((objet) => {
projet.assureAdditionnel.forEach((objet) => {
listAssAdd.push(objet.nom + " - Adresse : " + objet.adresse + " - Siret : " + objet.siret);
});
} catch (error) { }
} catch (error) {}
const risqueTransport = {
"achat": "des contrats d'achat",
@ -77,32 +85,40 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
}
Object.keys(risqueTransport).forEach((risque) => {
if (!fac.risqueTransport.includes(risque)) {
if (!projet.risqueTransport.includes(risque)) {
delete risqueTransport[risque]
}
})
const listRisqueTransport = Object.keys(risqueTransport).map((key) => risqueTransport[key])
const mondeEntier = (fac.zone1 && fac.zone2 && fac.zone3 && fac.zone4 && fac.zone5 && fac.zone6)
const hasZone456 = (fac.zone4 || fac.zone5 || fac.zone6)
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 hasGarOptAuto = fac.garOpt.includes('auto')
const hasGarOptEmballage = fac.garOpt.includes('emballage')
const hasGarOptEtiquette = fac.garOpt.includes('etiquette')
const hasGarOptTemperature = fac.garOpt.includes('temperature')
const hasGarOptMarque = fac.garOpt.includes('marque')
const hasTPPC = fac.typeTPPC !== ""
const hasMarchandiseExposition = !(fac.marExpo == "" || fac.marExpo == 0)
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 = fac.garOpt.length > 0
const hasGarOpt = projet.garOpt.length > 0
const condition2 = hasGarOpt || hasTPPC || hasMarchandiseExposition
const anneeProchaine = moment().add(1, 'years').format('YYYY')
const dateFin = (fac.dateFin == "00/00" || fac.dateFin == "00/00/0000") ? "A PRECISER" : fac.dateFin
const dateEffet = (fac.dateEffet == "00/00" || fac.dateEffet == "00/00/0000") ? "A PRECISER" : fac.dateEffet
const dateEcheance = (fac.dateEcheance == "00/00" || fac.dateEcheance == "00/00/0000") ? "A PRECISER" : fac.dateEcheance + '/' + anneeProchaine
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,
@ -124,12 +140,12 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
hasCP: (contrat.type == "AN" || contrat.type == "TEMPORAIRE"),
hasRemplacement: (contrat.type == "REMPLACEMENT"),
hasRGAuto: (fac.risqueGuerre == "auto"),
hasRGDemande: (fac.risqueGuerre == 'demande'),
hasRG: (fac.risqueGuerre == 'demande' || fac.risqueGuerre == 'auto'),
hasEtendue: (fac.typeGarantieRG == "etendue"),
hasWaterborne: (fac.typeGarantieRG == "waterborne"),
hasProgrammeInternational: fac.programmeInternational,
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,
@ -142,8 +158,8 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
listRisqueTransport: listRisqueTransport,
depart: fac.lieuDepart,
arrivee: fac.lieuArrivee,
depart: projet.lieuDepart,
arrivee: projet.lieuArrivee,
dateJour: moment().format("DD MMMM YYYY"),
dateEffet: dateEffet,
dateFin: dateFin,
@ -151,19 +167,19 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
hasMarchandiseExposition: hasMarchandiseExposition,
hasMondeEntier: mondeEntier,
hasZone1: fac.zone1,
hasZone2: fac.zone2,
hasZone3: fac.zone3,
hasZone4: fac.zone4,
hasZone5: fac.zone5,
hasZone6: fac.zone6,
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: fac.typeTPPC.includes('tousRisques'),
hasTPPCFlotteND: fac.typeTPPC.includes('flotteND'),
hasTPPCTousRisques: projet.typeTPPC.includes('tousRisques'),
hasTPPCFlotteND: projet.typeTPPC.includes('flotteND'),
capitalMax: fac.capitalMax,
franchiseTransport: fac.franchiseTransport,
capitalMax: projet.capitalMax,
franchiseTransport: projet.franchiseTransport,
condition4: condition4,
hasGarOpt: hasGarOpt,
condition2: condition2,
@ -174,11 +190,11 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
hasGarOptTemperature: hasGarOptTemperature,
capitalTPPC: fac.capitalTPPC,
franchiseExpo: fac.franchiseExpo,
hasCG: fac.valeurAssuree.includes('cg'),
hasDerogation: fac.valeurAssuree.includes('derogation'),
hasVaBasePrix: fac.valeurAssureeBase.includes('prix'),
hasVaBaseAchat: fac.valeurAssureeBase.includes('achat'),
hasVaBaseVente: fac.valeurAssureeBase.includes('vente'),
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'),
@ -190,30 +206,30 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
hasPostalEventMaj: fac.postal.includes('eventMaj'),
hasFluvialTousRisques: fac.fluvial.includes('tousRisques'),
hasFluvialEventMaj: fac.fluvial.includes('eventMaj'),
hasFranchiseTransport: fac.franchiseTransport !== "" && fac.franchiseTransport !== 0,
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(fac.cotComptant),
cotComptant: globalService.customFormatNumber(projet.cotComptant),
tauxCotRO: globalService.customFormatNumber(fac.tauxCotRO, true),
tauxCotRG: globalService.customFormatNumber(fac.tauxCotRG, true),
cotAnnuelle: globalService.customFormatNumber(fac.cotAnnuelleHT),
cotIrred: globalService.customFormatNumber(fac.cotIrred),
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: (fac.tempo == "mensuel"),
hasChiffreAffaires: (fac.typeContrat == "chiffreAffaires"),
hasAvisAliments: (fac.typeContrat == "avisAliments"),
hasPartResultat: fac.participationResultat,
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: fac.tempo,
tempo: projet.tempo,
}
try {
@ -225,13 +241,13 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
stack: error.stack,
properties: error.properties,
};
logger.log('error', JSON.stringify({ error: e }));
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 buf = doc.getZip().generate({type: "nodebuffer"});
const formattedDate = moment().format('DD-MM-YYYY-HH-mm-ss')
// Génération du nom de fichier
@ -256,4 +272,250 @@ router.post("/fac/projet/:numParcours", async (req, res) => {
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;

View File

@ -1,25 +1,124 @@
// 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);
});
renderPage("historiqueParcours.ejs", res);});
router.get("/read", async (req, res) => {
/**
* /regionUser : requête sur la region de l'user actuel
*/
router.get("/:regionUser", async (req, res) => {
try {
const allParcours = await parcoursService.getAllParcours();
if (allParcours) {
res.json({ valid: true, allParcours });
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.",
@ -27,25 +126,522 @@ router.get("/read", async (req, res) => {
}
});
//controller to get parcours by region
router.get("/:regionUser", async (req, res) => {
/**
* /datatable : DataTables server-side (gestion de pagination)
*/
router.post("/datatable", async (req, res) => {
try {
const { regionUser } = req.params;
const data = await parcoursService.getParcoursByRegion(regionUser);
const {
draw = 1,
start = 0,
length = 10,
regions = [],
search = { value: "" },
columns = [],
order = []
} = req.body || {};
if (data) {
res.json({ valid: true, data });
} else {
res.json({ valid: data });
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", "", ""]);
}
}
} catch (error) {
logger.log("error", error);
res.status(500).json({
valid: false,
error: "Erreur lors de la récupération des parcours.",
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: [] });
}
}
});
module.exports = router;
/**
* 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;

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -14,7 +14,55 @@ async function getClient(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,
};

View File

@ -2,8 +2,16 @@ const { db } = require('../db/db-connect');
const logger = require('../utils/logger');
const globalService = require("../services/globalService");
async function createFacProjet(data) {
return await db.records.create('facprojet', data);
}
async function createFacTarif(data) {
return await db.records.create('factarif', data);
}
async function createFAC(data) {
return await db.records.create('fac', data);
return await db.records.create('fac',data)
}
async function getFACbyId(id) {
@ -16,5 +24,7 @@ async function getFACbyId(id) {
module.exports = {
createFAC,
createFacProjet,
createFacTarif,
getFACbyId
};

View File

@ -30,7 +30,15 @@ async function fetchInfoByCriteria(collection, criteria) {
return resultList.items[0];
}
} catch (error) {
logger.log("error", 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;
@ -68,10 +76,71 @@ 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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&apos;");
}
/**
* 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,
};

View File

@ -1,109 +1,266 @@
// 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`};
const criteria = {
filter: `numParcours='${numParcours}'`,
expand: [
"dernierUtilisateur.region",
"contrat",
"contrat.client",
"contrat.intermediaire"
].join(",")
};
return globalService.fetchInfoByCriteria("parcours", criteria);
}
// get All parcours saved in DB
async function getAllParcours() {
/**
* 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 {
const criteria = {expand: "dernierUtilisateur, contrat, region"};
const resultList = await db.records.getList("parcours", 1, 200, criteria);
let regFilter = "";
if (Array.isArray(regions) && regions.length > 0) {
const ors = regions.map(r => `dernierUtilisateur.region.nom = "${r}"`);
regFilter = `(${ors.join(" || ")})`;
}
return resultList;
} catch (error) {
logger.log('error', error);
return null;
}
}
// get all parcours filtred on region
async function getParcoursByRegion(regionUser) {
try {
// Récupérer les enregistrements de la collection "parcours"
const filter = `dernierUtilisateur.region.nom = "${regionUser}"`;
const parcoursRecords = await db.records.getFullList("parcours", 200, {
sort: "-created",
filter: filter,
expand: "contrat, dernierUtilisateur.region, contrat.intermediaire",
});
// Récupérer les relations client pour chaque contrat
for (const record of parcoursRecords) {
const contrat = record["@expand"].contrat;
if (contrat && contrat.client) {
const clientRecord = await db.records.getOne("client", contrat.client);
record["@expand"].contrat.client = clientRecord;
}
}
return parcoursRecords;
} catch (error) {
logger.log('error', error);
throw error;
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;
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;
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 {
// fetch a paginated records list en utilisant le filtre pour le parcours
const resultList = await db.records.getFullList("parcours", 99999999, {sort: "-numParcours",});
const list = await db.records.getList("parcours", 1, 1, { sort: "-numParcours" });
const last = list?.items?.[0];
if (!last?.numParcours) return null;
if (resultList.length > 0) {
const lastNumParcours = resultList[0].numParcours;
const numericValue = parseInt(String(last.numParcours).substring(1), 10);
if (Number.isNaN(numericValue)) return null;
// Extrait les chiffres du numéro de parcours
const numericPart = lastNumParcours.substring(1); // Supprime le "P" initial
const numericValue = parseInt(numericPart, 10);
const next = numericValue + 1;
return "P" + next.toString().padStart(9, "0");
}
catch (error) {
logger.log("error", error);
return null;
}
}
if (!isNaN(numericValue)) {
const newNumericValue = numericValue + 1;
const newNumParcours = "P" + newNumericValue.toString().padStart(9, "0");
// --- Section détails profonds (contrat + fiche produit) --- //
return newNumParcours;
}
} else {
return null;
}
} catch (error) {
logger.log("error", error);
return null;
/**
* 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;
}
}
@ -112,6 +269,10 @@ module.exports = {
getParcoursByNumParcours,
createNewEmptyParcours,
updateFieldValueParcours,
getAllParcours,
getParcoursByRegion,
};
getParcoursByRegionsPage,
getParcoursFullList,
getParcoursForDetails,
getProduitRecordForContrat,
getDeepDetailsByNumParcours,
mapProduitToCollection,
};

View File

@ -1,5 +1,6 @@
const { db } = require('../db/db-connect');
const logger = require('../utils/logger');
const globalService = require("../services/globalService");
// ===== Collection RC principale =====
async function createRc(data) {

View File

@ -31,38 +31,6 @@
<div id="divToggleSearch">
<button class="btn" id="toggleSearch" type="button">Activer la recherche par colonne</button>
</div>
<div id="checkRegionAdmin">
<div class="checkbox-wrapper-1">
<label>
<input type="checkbox" id="zone1" name="zone1" class="filled-in" />
<span class="checkboxRegion" id="cbx-42">ILE DE FRANCE</span>
</label>
</div>
<div class="checkbox-wrapper-2">
<label>
<input type="checkbox" id="zone2" name="zone2" class="filled-in" />
<span class="checkboxRegion" id="cbx-43">SUD OUEST</span>
</label>
</div>
<div class="checkbox-wrapper-3">
<label>
<input type="checkbox" id="zone3" name="zone3" class="filled-in" />
<span class="checkboxRegion" id="cbx-44">SUD EST</span>
</label>
</div>
<div class="checkbox-wrapper-4">
<label>
<input type="checkbox" id="zone4" name="zone4" class="filled-in" />
<span class="checkboxRegion" id="cbx-45">OUEST</span>
</label>
</div>
<div class="checkbox-wrapper-5">
<label>
<input type="checkbox" id="zone5" name="zone5" class="filled-in" />
<span class="checkboxRegion" id="cbx-46">NORD EST</span>
</label>
</div>
</div>
</div>
<div>

View File

@ -163,42 +163,42 @@
<div class="row" style="margin-top: 20px;">
<div style="display: flex;align-items: center;justify-content: space-between;">
<label>
<input type="checkbox" id="zone1" name="zone1" class="filled-in" />
<input type="checkbox" id="zone1" name="zone" class="filled-in" />
<span id="zone1-text">Zone 1 - France Métropolitaine et pays limitrophes</span>
</label>
<button class="circle" type="button" id="btnZone1">?</button>
</div>
<div style="display: flex;align-items: center;justify-content: space-between;">
<label>
<input type="checkbox" id="zone2" name="zone2" class="filled-in" />
<input type="checkbox" id="zone2" name="zone" class="filled-in" />
<span id="zone2-text">Zone 2 - Union Européenne</span>
</label>
<button class="circle" type="button" id="btnZone2">?</button>
</div>
<div style="display: flex;align-items: center;justify-content: space-between;">
<label>
<input type="checkbox" id="zone3" name="zone3" class="filled-in" />
<input type="checkbox" id="zone3" name="zone" class="filled-in" />
<span id="zone3-text">Zone 3 - Autres pays européens sauf Russie et Ukraine</span>
</label>
<button class="circle" type="button" id="btnZone3">?</button>
</div>
<div style="display: flex;align-items: center;justify-content: space-between;">
<label>
<input type="checkbox" id="zone4" name="zone4" class="filled-in" />
<input type="checkbox" id="zone4" name="zone" class="filled-in" />
<span id="zone4-text">Zone 4 - Pays du Maghreb et Amérique du Nord (USA/Canada/Mexique)</span>
</label>
<button class="circle" type="button" id="btnZone4">?</button>
</div>
<div style="display: flex;align-items: center;justify-content: space-between;">
<label>
<input type="checkbox" id="zone5" name="zone5" class="filled-in" />
<input type="checkbox" id="zone5" name="zone" class="filled-in" />
<span id="zone5-text">Zone 5 - Amérique Centrale et Sud / Caraïbes, Asie et Océanie</span>
</label>
<button class="circle" type="button" id="btnZone5">?</button>
</div>
<div style="display: flex;align-items: center;justify-content: space-between;">
<label>
<input type="checkbox" id="zone6" name="zone6" class="filled-in" />
<input type="checkbox" id="zone6" name="zone" class="filled-in" />
<span id="zone6-text">Zone 6 - Afrique hors Maghreb / Proche Orient / Moyen Orient</span>
</label>
<button class="circle" type="button" id="btnZone6">?</button>
@ -445,7 +445,7 @@
<span>RG automatique</span>
</label>
<label id="row-rg-no" style="display : none">
<input name="risqueGuerre" id="rg-no" type="radio" value="" />
<input name="risqueGuerre" id="no-rg" type="radio" value="no-rg" />
<span>Pas de RG</span>
</label>
</div>
@ -535,7 +535,6 @@
<input name="fract" id="annuel" type="radio" value="annuel" />
<span>Annuel</span>
</label>
<input id="cotFraisHT" type="number" disabled style="display: none;">
</div>
<br>
@ -1018,7 +1017,24 @@
</div>
</div>
<!-- Modal pour pays zone 6 -->
<!-- Modal pour pays zone pays Exclus -->
<div id="modalZoneExclus" class="modal">
<div class="modal-content center-align">
<div class="row">
<h5>Liste des pays exclus</h5>
<hr>
</div>
<div class="row">
<img src="/images/zone7.png" alt="Liste zone 7" id="imgZone7" style="width:80%;">
</div>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat">Fermer</a>
</div>
</div>
<!-- Modal pour format saisie date -->
<div id="modalDate" class="modal">
<div class="modal-content center-align">
<div class="row">
@ -1045,43 +1061,6 @@
</div>
</div>
<!-- Modal pour pays zone pays Exclus -->
<div id="modalZoneExclus" class="modal">
<div class="modal-content center-align">
<div class="row">
<h5>Liste des pays exclus</h5>
<hr>
</div>
<div class="row">
<img src="/images/zone7.png" alt="Liste zone 7" id="imgZone7" style="width:80%;">
</div>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat">Fermer</a>
</div>
</div>
<!-- Modal Animaux Vivants -->
<div id="modalAnimauxVivants" class="modal">
<div class="modal-content center-align">
<div class="row">
<h5>Demande PASS animaux vivants</h5>
<hr>
</div>
<div class="row">
<h6>Vous devez faire une <strong>demande de dépassement de pouvoirs</strong> pour pouvoir garantir<strong> les animaux vivants</strong></h6>
</div>
<div class="row">
<h6 class="red-text text-darken-4">Veuillez cliquer sur <strong> « Demande PASS » </strong> pour faire la demande sur l'outil <strong>« PASS »</h6>
<br>
<button id="depassement" class="btn" onclick="window.open('https://ajir.axa-fr.intraxa/secure/RapidBoard.jspa?rapidView=24786&projectKey=ASIARD&quickFilter=47050', '_blank');">Demande PASS </button>
</div>
</div>
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat">Fermer</a>
</div>
</div>
<!-- Modal DIFFERENCE D'INVENTAIRE -->
<div id="modalDiffInventaire" class="modal">
<div class="modal-content center-align">
@ -1096,4 +1075,34 @@
<div class="modal-footer">
<a class="modal-close waves-effect waves-green btn-flat">Fermer</a>
</div>
</div>
<!-- MODAL modification & liaison tarif -->
<div id="modalModif" class="modal modalAlert">
<div class="modal-content">
<h4>Attention</h4>
<p>
Si vous modifiez cette information, la prime calculée à l'étape "Tarif" sera incorrecte,
Voulez-vous modifier cette information à l'étape "Tarif" ?
</p>
<p>Cela s'applique aux informations suivantes :</p>
<ul>
<li>Activité assurée</li>
<li>Type de Marchandise</li>
<li>Zones</li>
<li>Moyen de Transport</li>
<li>Risque de Guerre</li>
<li>Extensions de Garanties</li>
<li>Chiffre d'Affaires</li>
<li>Taux Risque de Guerre & Risque Ordinaires</li>
<li>Cotisations</li>
<li>Capital TPPC et Capital Exposition</li>
</ul>
</div>
<div class="modal-footer">
<a id="modif-OK" class="modal-close waves-effect btn-flat red darken-1">Modifier dans Tarif</a>
<a id="modif-NO" class="modal-close waves-effect btn-flat indigo darken-4">Annuler</a>
</div>
</div>

View File

@ -399,7 +399,7 @@
<td><input type="text" name="genre" id="genreVehicule" placeholder="VP" /></td>
<td><input type="text" name="type" id="typeVehicule" placeholder="Non défini" /></td>
<td><input type="text" name="immat" id="immatVehicule" placeholder="AA-999-AA" /></td>
<td><input type="text" name="capital" id="capitalVeh" placeholder="Ex: 10000" /></td>
<td><input type="text" name="capital" id="capitalVeh" placeholder="10 000" /></td>
<td><button class="btn" type="button" id="btnAddVehicule"><i
class="material-icons">add</i></button></td>
</tr>

1490
ecole/views/tarifformfac.ejs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -173,7 +173,7 @@
</td>
<td>
<input type="number" name="nbVehiculesTarif" id="nbVehiculesTarif"
placeholder="Ex: 2" min="0" value="0" />
placeholder="Non défini" min="0" value="0" />
</td>
<td>
<input type="number" name="primeVehTarif" id="primeVehTarif" min="0" placeholder="Ex: 300" />