#!/usr/bin/env node
/**
* Reconstruit les templates Avenant Advalo pour la parité v1.
*
* Les .docx d'origine (lettre AXA stylée) n'ont que l'en-tête agent/client. La v1
* (golang/bordereau.go) construit par CODE : un tableau de prix (CAPITAUX | TAUX |
* COTISATION | COUT D'ACTE | A PERCEVOIR), la phrase "à percevoir", et — pour le
* périodique avec liste — un tableau récapitulatif des transports.
*
* Ce script:
* 1. sauvegarde la version pristine de chaque template dans _source/ (au 1er passage);
* 2. convertit les tokens bruts (nomAgent, …) en tags docxtemplater {token} (runs isolés);
* 3. injecte le tableau de prix + la phrase + (Avenant) la boucle {#avecListe}/{#listeTransports};
* 4. réécrit le .docx live. Idempotent : retransforme toujours depuis _source/.
*
* Usage: node scripts/advalo-build-templates.js
*/
const fs = require('fs');
const path = require('path');
const PizZip = require('pizzip');
const TEMPLATE_DIR = path.resolve(__dirname, '..', 'src', 'templates', 'advalo');
const SOURCE_DIR = path.join(TEMPLATE_DIR, '_source');
const COMMON_TOKENS = [
'nomAgent', 'adresseAgent', 'postalAgent', 'telAgent', 'faxAgent',
'nomClient', 'adresseClient', 'codePostal', 'numContrat', 'numClient',
'intervalle', 'numAgent'
];
const BLUE = '254E9B';
function escAttr(s) {
return String(s).replace(/&/g, '&').replace(//g, '>');
}
// Paragraphe simple. `text` peut contenir des tags docxtemplater {…}.
function para(text, { bold = false, color = null, size = null, align = null } = {}) {
const rpr = [];
if (bold) rpr.push('');
if (color) rpr.push(``);
if (size) rpr.push(``);
const rPr = rpr.length ? `${rpr.join('')}` : '';
const pPrParts = [];
if (align) pPrParts.push(``);
if (rpr.length) pPrParts.push(`${rpr.join('')}`);
const pPr = pPrParts.length ? `${pPrParts.join('')}` : '';
return `${pPr}${rPr}${text}`;
}
// Cellule de tableau. `text` peut contenir des tags docxtemplater.
function cell(text, width, { bold = false, color = null, fill = null } = {}) {
const rpr = [];
if (bold) rpr.push('');
if (color) rpr.push(``);
rpr.push('');
const rPr = `${rpr.join('')}`;
const shd = fill ? `` : '';
return `${shd}`
+ `${rPr}${text}`;
}
function tableOpen(widths) {
const borders = ['top', 'left', 'bottom', 'right', 'insideH', 'insideV']
.map((b) => ``)
.join('');
const grid = widths.map((w) => ``).join('');
return `${borders}${grid}`;
}
function row(cells) {
return `${cells.join('')}`;
}
// Tableau de prix à une ligne (parité v1: CAPITAUX | TAUX | COTISATION | COUT D'ACTE | A PERCEVOIR).
function pricingTable() {
const W = [1900, 1500, 1900, 1900, 1900];
const headers = ['CAPITAUX', 'TAUX', 'COTISATION', "COUT D'ACTE", 'A PERCEVOIR'];
const headerRow = row(headers.map((h, i) => cell(h, W[i], { bold: true, color: 'FFFFFF', fill: BLUE })));
const dataRow = row([
cell('{capitauxField} €', W[0]),
cell('{tauxField} %', W[1]),
cell('{cotisationField} €', W[2]),
cell('{coutActeField} €', W[3]),
cell('{cotisationTTC} €', W[4])
]);
return tableOpen(W) + headerRow + dataRow + '';
}
// Tableau récapitulatif des transports (boucle docxtemplater sur {listeTransports}).
function recapTable() {
const W = [1300, 1300, 1300, 1300, 1300, 1500, 1100];
const headers = ['N° Demande', 'Date demande', 'Mode', 'Capital', 'Départ', 'Arrivée', 'Transport', 'Tarif'];
const W8 = [1150, 1150, 1100, 1150, 1100, 1100, 1300, 1000];
const headerRow = row(headers.map((h, i) => cell(h, W8[i], { bold: true, color: 'FFFFFF', fill: BLUE })));
const loopRow = row([
cell('{#listeTransports}{numDemande}', W8[0]),
cell('{dateDemande}', W8[1]),
cell('{mode}', W8[2]),
cell('{capital} €', W8[3]),
cell('{depart}', W8[4]),
cell('{arrivee}', W8[5]),
cell('{dateTransport}', W8[6]),
cell('{tarif} €{/listeTransports}', W8[7])
]);
return tableOpen(W8) + headerRow + loopRow + '';
}
const EMPTY_P = '';
function buildAvenantBlock() {
// Tableau prix + phrase "à percevoir" + récap conditionnel (parité v1 remplissageAvenant).
return [
EMPTY_P,
pricingTable(),
EMPTY_P,
para('La cotisation à percevoir à la signature du présent avenant est de {cotisationTTC} €.', { bold: true }),
EMPTY_P,
para('{#avecListe}', {}),
para("RECAPITULATIF DES TRANSPORTS SAISIS DANS L'OUTIL AD VALOREM", { bold: true, color: BLUE }),
recapTable(),
para('{/avecListe}', {}),
EMPTY_P
].join('');
}
function buildPonctuelBlock() {
// Détails transport + tableau prix + phrase "Il est perçu" (parité v1 remplissageAvenantPonctuel).
return [
EMPTY_P,
para('Transport assuré du {dateDebut} au {dateFin}.'),
para('Nature de la marchandise : {typeMarchandise}.'),
para('Trajet : {depart} → {arrivee}.'),
para('Mode(s) de transport : {modes}.'),
EMPTY_P,
pricingTable(),
EMPTY_P,
para('Il est perçu la somme de {cotisationTTC} € TTC '
+ '(dont cotisation HT {cotisationField} € et coût d’acte {coutActeField} €).', { bold: true }),
EMPTY_P
].join('');
}
function convertTokens(xml, tokens) {
let out = xml;
tokens.forEach((tok) => {
out = out.replace(new RegExp(`(]*>)${tok}()`, 'g'), `$1{${tok}}$2`);
});
return out;
}
function insertBeforeSectPr(xml, block) {
const idx = xml.lastIndexOf('', `${block}`);
}
return xml.slice(0, idx) + block + xml.slice(idx);
}
function rebuild(name, blockBuilder, { stripCapitauxAnchor = false } = {}) {
const livePath = path.join(TEMPLATE_DIR, `${name}.docx`);
const sourcePath = path.join(SOURCE_DIR, `${name}.docx`);
fs.mkdirSync(SOURCE_DIR, { recursive: true });
if (!fs.existsSync(sourcePath)) {
fs.copyFileSync(livePath, sourcePath);
console.log(`[source] sauvegarde pristine -> ${path.relative(process.cwd(), sourcePath)}`);
}
const zip = new PizZip(fs.readFileSync(sourcePath));
let xml = zip.file('word/document.xml').asText();
xml = convertTokens(xml, COMMON_TOKENS);
if (stripCapitauxAnchor) {
// L'ancre inline `capitauxField` de la v1 est remplacée par le vrai tableau (ci-dessous).
xml = xml.replace(/(]*>)capitauxField(<\/w:t>)/g, '$1$2');
}
xml = insertBeforeSectPr(xml, blockBuilder());
zip.file('word/document.xml', xml);
fs.writeFileSync(livePath, zip.generate({ type: 'nodebuffer', compression: 'DEFLATE' }));
console.log(`[build] ${name}.docx reconstruit (${xml.length} octets de document.xml)`);
}
rebuild('Avenant', buildAvenantBlock, { stripCapitauxAnchor: true });
rebuild('Avenant_Ponctuel', buildPonctuelBlock);
console.log('Templates Avenant reconstruits.');