597 lines
21 KiB
JavaScript
597 lines
21 KiB
JavaScript
// public/js/historiqueParcours.js
|
||
|
||
// --- debounce pour éviter de spam + erreur de la base ---
|
||
function debounce(fn, delay = 300) {
|
||
let t;
|
||
|
||
function wrapped(...args) {
|
||
clearTimeout(t);
|
||
t = setTimeout(() => fn(...args), delay);
|
||
}
|
||
|
||
wrapped.cancel = () => clearTimeout(t);
|
||
|
||
return wrapped;
|
||
}
|
||
|
||
document.addEventListener("DOMContentLoaded", async function () {
|
||
// Récupération du token
|
||
const token = localStorage.getItem("jwtToken");
|
||
|
||
if (!token) {
|
||
throw new Error("Aucun token trouvé dans le localStorage.");
|
||
}
|
||
|
||
const userData = parseJwt(token);
|
||
|
||
if (!userData) {
|
||
displayError("Erreur lors de l'extraction des données utilisateur à partir du token.");
|
||
|
||
return;
|
||
}
|
||
|
||
// Initialiser DataTables en mode server-side (obligé pour pagination)
|
||
const table = initServerSideDataTable;
|
||
|
||
// Exports CSV/XLSX
|
||
$("#exportCSV").on("click", function () {
|
||
const dt = $("#historiqueParcours").DataTable();
|
||
|
||
const payload = {
|
||
mode: "full", // export total
|
||
search: { value: "" },
|
||
columns: dt.settings()[0].aoColumns.map((c, i) => ({
|
||
data: i,
|
||
search: { value: "" }
|
||
})),
|
||
// on garde l'ordre actuel pour la récuperation
|
||
order: dt.order().map(([col, dir]) => ({ column: col, dir }))
|
||
};
|
||
|
||
fetch("/historiqueParcours/export/csv", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(payload),
|
||
})
|
||
.then(resp => {
|
||
if (!resp.ok) throw new Error("Export CSV (complet) impossible");
|
||
|
||
return resp.blob();
|
||
})
|
||
.then(blob => {
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = "historique_parcours_complet.csv";
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
a.remove();
|
||
})
|
||
.catch(() => displayError("Export CSV (complet) impossible"));
|
||
});
|
||
|
||
|
||
$("#exportCSVFilter").on("click", function () {
|
||
const dt = $("#historiqueParcours").DataTable();
|
||
const payload = {
|
||
mode: "filtered", // export avec les filtres/colonnes/tri actuels
|
||
search: { value: dt.search() || "" }, // recherche globale
|
||
columns: dt.settings()[0].aoColumns.map((c, i) => ({
|
||
data: i,
|
||
search: { value: dt.column(i).search() || "" } // filtres par colonne
|
||
})),
|
||
order: dt.order().map(([col, dir]) => ({ column: col, dir }))
|
||
};
|
||
|
||
fetch("/historiqueParcours/export/csv", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(payload),
|
||
})
|
||
.then(resp => {
|
||
if (!resp.ok) throw new Error("Export CSV (filtré) impossible");
|
||
return resp.blob();
|
||
})
|
||
.then(blob => {
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = "historique_parcours_filtre.csv";
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
a.remove();
|
||
})
|
||
.catch(() => displayError("Export CSV (filtré) impossible"));
|
||
});
|
||
|
||
|
||
$("#exportXlxs").on("click", function () {
|
||
const dt = $("#historiqueParcours").DataTable();
|
||
const payload = {
|
||
mode: "full",
|
||
search: { value: "" },
|
||
columns: dt.settings()[0].aoColumns.map((c, i) => ({ data: i, search: { value: "" } })),
|
||
order: dt.order().map(([col, dir]) => ({ column: col, dir }))
|
||
};
|
||
|
||
fetch("/historiqueParcours/export/xls", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(payload),
|
||
})
|
||
.then(resp => { if (!resp.ok) throw new Error(); return resp.blob(); })
|
||
.then(blob => {
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url; a.download = "historique_parcours_complet.xls";
|
||
document.body.appendChild(a); a.click();
|
||
URL.revokeObjectURL(url); a.remove();
|
||
})
|
||
.catch(() => displayError("Export XLS (complet) impossible"));
|
||
});
|
||
|
||
|
||
$("#exportXlxsFilter").on("click", function () {
|
||
const dt = $("#historiqueParcours").DataTable();
|
||
const payload = {
|
||
mode: "filtered", // export avec les filtres/colonnes/tri actuels
|
||
search: { value: dt.search() || "" }, // recherche globale
|
||
columns: dt.settings()[0].aoColumns.map((c, i) => ({
|
||
data: i,
|
||
search: { value: dt.column(i).search() || "" } // filtres par colonne
|
||
})),
|
||
order: dt.order().map(([col, dir]) => ({ column: col, dir }))
|
||
};
|
||
|
||
fetch("/historiqueParcours/export/xls", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(payload),
|
||
})
|
||
.then(resp => {
|
||
if (!resp.ok) throw new Error("Export XLS (filtré) impossible");
|
||
return resp.blob();
|
||
})
|
||
.then(blob => {
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = "historique_parcours_filtre.xls";
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
a.remove();
|
||
})
|
||
.catch(() => displayError("Export XLS (filtré) impossible"));
|
||
});
|
||
|
||
|
||
// Délégation pour la génération de projet
|
||
$("#historiqueParcours").on("click", "button#btnGenerate", function () {
|
||
const numParcours = $(this).data("num-parcours");
|
||
const produit = $(this).data("produit");
|
||
generateProject(numParcours, produit);
|
||
});
|
||
});
|
||
|
||
/* =========================
|
||
* Helpers spécifiques server-side
|
||
* ========================= */
|
||
|
||
// Initialisation DataTables en server-side (recherche globale + par colonnes + tri + pagination)
|
||
function initServerSideDataTable() {
|
||
let inflightController = null;
|
||
|
||
const table = $("#historiqueParcours").DataTable({
|
||
processing: true,
|
||
serverSide: true,
|
||
searching: true,
|
||
paging: true,
|
||
orderCellsTop: true,
|
||
fixedHeader: true,
|
||
responsive: { details: false },
|
||
pageLength: 10,
|
||
retrieve: true,
|
||
order: [[0, "desc"]],
|
||
searchDelay: 350,
|
||
language: {
|
||
search: "Rechercher",
|
||
lengthMenu: "Afficher _MENU_ entrées par page",
|
||
info: "Affichage de _START_ à _END_ sur _TOTAL_ entrées",
|
||
infoEmpty: "Affichage de 0 à 0 sur 0 entrée",
|
||
infoFiltered: "(filtré de _MAX_ entrées au total)",
|
||
paginate: { first: "Début", previous: "Précédent", next: "Suivant", last: "Fin" },
|
||
},
|
||
|
||
ajax: function (data, callback) {
|
||
const body = {
|
||
draw: data.draw || 1,
|
||
start: data.start || 0,
|
||
length: data.length || 10,
|
||
order: data.order || [],
|
||
columns: data.columns || [],
|
||
search: data.search || { value: "" },
|
||
};
|
||
|
||
|
||
if (inflightController) inflightController.abort(); // action en cours
|
||
inflightController = new AbortController();
|
||
|
||
fetch("/historiqueParcours/datatable", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(body),
|
||
signal: inflightController.signal,
|
||
})
|
||
.then(res => res.json())
|
||
.then(payload => {
|
||
callback({
|
||
draw: payload.draw,
|
||
recordsTotal: payload.recordsTotal,
|
||
recordsFiltered: payload.recordsTotal,
|
||
data: payload.data
|
||
});
|
||
})
|
||
.catch(err => {
|
||
if (err && err.name === "AbortError") return;
|
||
displayError("Failed to fetch data. Please try again later.");
|
||
callback({ draw: 0, recordsTotal: 0, recordsFiltered: 0, data: [] });
|
||
});
|
||
},
|
||
|
||
initComplete: function () {
|
||
const api = this.api();
|
||
|
||
// Recherche globale : debounce y compris ENTER (plus de bypass immédiat)
|
||
const $globalInput = $('div.dataTables_filter input[type="search"]');
|
||
$globalInput.off('.DT'); // nettoie handlers datatables
|
||
const debouncedGlobal = debounce((v) => {
|
||
api.search(v);
|
||
api.ajax.reload();
|
||
}, 350);
|
||
$globalInput.on('input keyup keydown', function () {
|
||
debouncedGlobal(this.value);
|
||
});
|
||
|
||
// Recherche par colonne avec DEBOUNCE (ENTER inclus)
|
||
const debouncedColSearch = debounce((i, val) => {
|
||
api.column(i).search(val);
|
||
api.ajax.reload();
|
||
}, 350);
|
||
|
||
$("#historiqueParcours thead tr:eq(1) th").each(function (i) {
|
||
$("input", this).on("input keyup keydown change", function () {
|
||
debouncedColSearch(i, this.value);
|
||
});
|
||
});
|
||
|
||
api.on("responsive-resize", function (e, datatable, columns) {
|
||
for (let i = 0; i < columns.length; i++) {
|
||
if (columns[i]) {
|
||
$(api.column(i).header()).show();
|
||
$(api.column(i).footer()).show();
|
||
$($("#historiqueParcours thead tr:eq(1) th")[i]).show();
|
||
}
|
||
else {
|
||
$(api.column(i).header()).hide();
|
||
$(api.column(i).footer()).hide();
|
||
$($("#historiqueParcours thead tr:eq(1) th")[i]).hide();
|
||
}
|
||
}
|
||
});
|
||
|
||
$("#divToggleSearch").on("click", function () {
|
||
const $row = $("#historiqueParcours thead tr:eq(1)");
|
||
$row.toggle();
|
||
const $btn = $("#toggleSearch");
|
||
if ($row.is(":visible")) {
|
||
$btn.text("ENLEVER LA RECHERCHE PAR COLONNE");
|
||
} else {
|
||
$btn.text("ACTIVER LA RECHERCHE PAR COLONNE");
|
||
}
|
||
});
|
||
|
||
// Cacher la 2e ligne au départ
|
||
$("#historiqueParcours thead tr:eq(1)").hide();
|
||
},
|
||
|
||
// --- Ajout bouton détails en 1re colonne
|
||
columnDefs: [
|
||
{
|
||
targets: 0,
|
||
render: function (data, type, row) {
|
||
const np = String(data ?? "");
|
||
const numPacours = np.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
|
||
|
||
return '<span style="display:inline-flex; align-items:center; gap:8px;">'
|
||
+ '<button class="btn-row-details" title="Détails" aria-label="Afficher les détails"'
|
||
+ ' style="display:inline-block; width:22px; height:22px; border:none; border-radius:4px;'
|
||
+ ' background:#2ecc71; color:#fff; font-weight:700; cursor:pointer; line-height:22px;'
|
||
+ ' text-align:center;">+</button>'
|
||
+ '<span class="np">' + numPacours + '</span>'
|
||
+ '</span>';
|
||
|
||
}
|
||
},
|
||
{ type: "date-uk", targets: 10 },
|
||
{ type: "date-eu", targets: 4 },
|
||
{ responsivePriority: 1, targets: -1 },
|
||
{ responsivePriority: 2, targets: -2 }
|
||
],
|
||
});
|
||
|
||
// --- Toggle des détails
|
||
$('#historiqueParcours tbody').on('click', '.btn-row-details', function (e) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
const api = $('#historiqueParcours').DataTable();
|
||
const $tr = $(this).closest('tr');
|
||
|
||
// si c'est une ligne enfant (responsive), remonter à la parent
|
||
const row = api.row($tr.hasClass('child') ? $tr.prev() : $tr);
|
||
|
||
if (row.child.isShown()) {
|
||
row.child.hide();
|
||
// change icône
|
||
this.textContent = "+";
|
||
this.style.background = "#2ecc71";
|
||
return;
|
||
}
|
||
|
||
// récupérer le numParcours depuis la 1re cellule
|
||
const raw = row.data()?.[0] ?? "";
|
||
const tmp = document.createElement('div');
|
||
tmp.innerHTML = String(raw);
|
||
const numParcours = tmp.textContent.replace("+", "").trim();
|
||
if (!numParcours) return;
|
||
|
||
row.child('<div style="padding:12px 16px; font-style:italic;">Chargement des détails…</div>').show();
|
||
this.textContent = "–";
|
||
this.style.background = "#27ae60";
|
||
|
||
fetch(`/historiqueParcours/details/${encodeURIComponent(numParcours)}`)
|
||
.then(r => r.json())
|
||
.then(payload => {
|
||
if (!payload?.valid) {
|
||
row.child('<div style="padding:12px 16px; color:#b00020;">Impossible de charger les détails.</div>');
|
||
this.textContent = "+";
|
||
this.style.background = "#2ecc71";
|
||
return;
|
||
}
|
||
row.child(formatDetailsPanel(payload));
|
||
})
|
||
.catch(() => {
|
||
row.child('<div style="padding:12px 16px; color:#b00020;">Erreur de chargement.</div>');
|
||
this.textContent = "+";
|
||
this.style.background = "#2ecc71";
|
||
});
|
||
});
|
||
|
||
return table;
|
||
}
|
||
|
||
/* =========================
|
||
* Fonctions annexes importantes
|
||
* ========================= */
|
||
|
||
async function fetchUserDetails(matriculeUser) {
|
||
try {
|
||
const response = await fetch(`/user/read/matricule/${matriculeUser}`);
|
||
const data = await response.json();
|
||
|
||
return data.valid ? data : null;
|
||
} catch (error) {
|
||
displayError(`Erreur lors de la récupération du contrat avec le matricule ${matriculeUser} :`, error);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function generateProject(numParcours, produit) {
|
||
try {
|
||
const response = await fetch(`/generate/${produit}/projet/${numParcours}`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
});
|
||
|
||
if (!response.ok) throw new Error("Erreur réseau ou serveur");
|
||
|
||
const disposition = response.headers.get("content-disposition");
|
||
const filename = disposition.split(";")[1].trim().split("=")[1];
|
||
|
||
const blob = await response.blob();
|
||
const url = window.URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = filename;
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
window.URL.revokeObjectURL(url);
|
||
a.remove();
|
||
} catch (error) {
|
||
console.error("Erreur lors de la génération du projet:", error);
|
||
}
|
||
}
|
||
|
||
// Décoder le JWT
|
||
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;
|
||
}
|
||
}
|
||
|
||
function displayError(message) {
|
||
const errorElement = document.getElementById("error");
|
||
errorElement.textContent = message;
|
||
errorElement.style.display = "block";
|
||
}
|
||
|
||
// ---------- Helpers rendu ----------
|
||
|
||
// kv : rend l'HTML pour l'affichage des données détails
|
||
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>`;
|
||
}
|
||
|
||
//formatDetailsPanel : rend l'HTML pour la récuperation des données par parcours
|
||
function formatDetailsPanel(payload) {
|
||
const prodKey = (payload.produit || "").toUpperCase();
|
||
const prod = payload.produitRecord || null;
|
||
|
||
let body = "";
|
||
if (prodKey === "TPPC") body = sectionTPPC(prod);
|
||
else if (prodKey === "RC") body = sectionRC(prod);
|
||
else if (prodKey === "FAC") body = sectionFAC(prod);
|
||
else body = `<em>Produit non renseigné.</em>`;
|
||
|
||
return `
|
||
<div class="parcours-details" style="padding:14px 16px; background:#fafafa; border-top:1px solid #e0e0e0;">
|
||
<div style="font-weight:600; margin:0 0 8px;">Détails ${prodKey || ""}</div>
|
||
${body}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
//fmtDate : formatage des Dates (sections détails)
|
||
function fmtDate(iso, withTime=true) {
|
||
if (!iso) return "NC";
|
||
const d = new Date(iso);
|
||
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}`;
|
||
}
|
||
|
||
//gridWrap2cols : rend les détails sur 2 colonnes
|
||
function gridWrap2cols(innerLeft, innerRight) {
|
||
return `<div style="display:grid;grid-template-columns:repeat(2,minmax(280px,1fr));gap:14px;">${innerLeft}${innerRight}</div>`;
|
||
}
|
||
|
||
// ---------- Sections produit ----------
|
||
|
||
//sectionTTPC : récupere les détails d'un parcours TPPC pour le mettre en forme (2 colonnes)
|
||
function sectionTPPC(produit) {
|
||
if (!produit) return `<em style="color:#a00;">Fiche TPPC introuvable.</em>`;
|
||
|
||
const tarif = produit?.["@expand"]?.tarif || null; // tppctarif
|
||
const projet = produit?.["@expand"]?.projet || null; // tppcprojet
|
||
|
||
const garant = Array.isArray(produit.garanties) ? produit.garanties.join(", ") : (produit.garanties || "NC");
|
||
|
||
// 6 infos, affichées en 2 colonnes (3 / colonne)
|
||
const gauche = [
|
||
kv("Activité assurée", produit.actAssuree),
|
||
kv("Nombre de véhicules", produit.nbVehic),
|
||
kv("Garanties", garant),
|
||
].join("");
|
||
|
||
const droite = [
|
||
kv("Prime HT", produit.primeHT),
|
||
kv("Tarif - Référence", tarif?.tarifRef),
|
||
kv("Projet - Dates", `Effet: ${projet?.dateEffet || "NC"} / Échéance: ${projet?.dateEcheance || "NC"} / Fin: ${projet?.dateFin || "NC"}`),
|
||
].join("");
|
||
|
||
return gridWrap2cols(gauche, droite);
|
||
}
|
||
|
||
|
||
//sectionRC : récupere les détails d'un parcours RC pour le mettre en forme (2 colonnes)
|
||
function sectionRC(produit) {
|
||
if (!produit) return `<em style="color:#a00;">Fiche RC introuvable.</em>`;
|
||
|
||
//nombre d'éléments incorrect, il faut revoir quand parcours RC fait
|
||
const gauche = [
|
||
kv("Dates", `Effet: ${produit.dateEffet || "NC"} / Échéance: ${produit.dateEcheance || "NC"} / Fin: ${produit.dateFin || "NC"}`),
|
||
kv("Tempo", produit.tempo),
|
||
kv("Type cotisation", produit.typeCot),
|
||
kv("Chiffre d'affaires", produit.ca),
|
||
|
||
kv("Taux RCC HT / TTC", `${produit.tauxRCCHT ?? "NC"} / ${produit.tauxRCCTTC ?? "NC"}`),
|
||
kv("Taux RCE HT / TTC", `${produit.tauxRCEHT ?? "NC"} / ${produit.tauxRCETTC ?? "NC"}`),
|
||
kv("Taux total HT / TTC", `${produit.tauxTotalHT ?? "NC"} / ${produit.tauxTotalTTC ?? "NC"}`),
|
||
|
||
kv("Cotisations RCC HT / TTC", `${produit.cotRCCHT ?? "NC"} / ${produit.cotRCCTTC ?? "NC"}`),
|
||
kv("Cotisations RCE HT / TTC", `${produit.cotRCEHT ?? "NC"} / ${produit.cotRCETTC ?? "NC"}`),
|
||
kv("PJ", produit.pj),
|
||
kv("Programme international", produit.programmeInternationale),
|
||
kv("Participation résultat", produit.participationResultat),
|
||
|
||
kv("Cot. totale HT / TTC", `${produit.cotTotalHT ?? "NC"} / ${produit.cotTotalTTC ?? "NC"}`),
|
||
kv("Cot. frais HT / TTC", `${produit.cotFraisHT ?? "NC"} / ${produit.cotFraisTTC ?? "NC"}`),
|
||
kv("Cot. irréductible", produit.cotIrreductible),
|
||
].join("");
|
||
|
||
const droite = [
|
||
kv("Act. Voiturier", produit.actVoiturier),
|
||
kv("Act. Loueur", produit.actLoueur),
|
||
kv("Act. Multimodal", produit.actMultimodal),
|
||
kv("Act. Douane", produit.actDouane),
|
||
kv("Act. Levageur", produit.actLevageur),
|
||
kv("Act. Transitaire", produit.actTransitaire),
|
||
kv("March. Roulant", produit.marRoulant),
|
||
kv("March. Engins", produit.marEngins),
|
||
kv("March. Perissable", produit.marPerissable),
|
||
kv("Zone 1..6", `1:${produit.zone1?"Oui":"Non"} 2:${produit.zone2?"Oui":"Non"} 3:${produit.zone3?"Oui":"Non"} 4:${produit.zone4?"Oui":"Non"} 5:${produit.zone5?"Oui":"Non"} 6:${produit.zone6?"Oui":"Non"}`),
|
||
].join("");
|
||
|
||
return gridWrap2cols(gauche, droite);
|
||
}
|
||
|
||
//sectionFAC : récupere les détails d'un parcours FAC pour le mettre en forme (1 colonnes)
|
||
function sectionFAC(produit) {
|
||
if (!produit) return `<em style="color:#a00;">Fiche FAC introuvable.</em>`;
|
||
|
||
//nombre d'éléments incorrect, il faut revoir quand parcours RC fait
|
||
const gauche = [
|
||
kv("Zones 1..6", `1:${produit.zone1?"Oui":"Non"} 2:${produit.zone2?"Oui":"Non"} 3:${produit.zone3?"Oui":"Non"} 4:${produit.zone4?"Oui":"Non"} 5:${produit.zone5?"Oui":"Non"} 6:${produit.zone6?"Oui":"Non"}`),
|
||
kv("Terrestre", produit.terrestre),
|
||
kv("Maritime", produit.maritime),
|
||
kv("Aérien", produit.aerien),
|
||
kv("Postal", produit.postal),
|
||
kv("Fluvial", produit.fluvial),
|
||
kv("Type TPPC", produit.typeTPPC),
|
||
|
||
kv("Capital max / expo / expédition / colis / TPPC",
|
||
`${produit.capitalMax ?? "NC"} / ${produit.capitalExpo ?? "NC"} / ${produit.capitalExped ?? "NC"} / ${produit.capitalColis ?? "NC"} / ${produit.capitalTPPC ?? "NC"}`),
|
||
|
||
kv("Franchise Transport / Expo / TPPC",
|
||
`${produit.franchiseTransport ?? "NC"} / ${produit.franchiseExpo ?? "NC"} / ${produit.franchiseTPPC ?? "NC"}`),
|
||
].join("");
|
||
|
||
const droite = [
|
||
kv("Taux RO / RG", `${produit.tauxCotRO ?? "NC"} / ${produit.tauxCotRG ?? "NC"}`),
|
||
kv("CA", produit.ca),
|
||
kv("Cot. annuelle HT / TTC", `${produit.cotAnnuelleHT ?? "NC"} / ${produit.cotAnnuelleTTC ?? "NC"}`),
|
||
kv("Cot. provisoire RO / RG", `${produit.cotProvRO ?? "NC"} / ${produit.cotProvRG ?? "NC"}`),
|
||
kv("Cot. irréductible", produit.cotIrred),
|
||
kv("Cot. RO / RG / Comptant", `${produit.cotRO ?? "NC"} / ${produit.cotRG ?? "NC"} / ${produit.cotComptant ?? "NC"}`),
|
||
|
||
kv("Dates", `Effet: ${produit.dateEffet || "NC"} / Échéance: ${produit.dateEcheance || "NC"} / Fin: ${produit.dateFin || "NC"}`),
|
||
kv("Act. assurée", produit.actAssure),
|
||
kv("Type marchandises", produit.typeMar),
|
||
].join("");
|
||
|
||
return gridWrap2cols(gauche, droite);
|
||
} |