206 lines
10 KiB
JavaScript
206 lines
10 KiB
JavaScript
// Mocks DB + pont AXA AVANT le require du service (jest hoiste les jest.mock).
|
|
const mockRecords = {
|
|
getList: jest.fn(),
|
|
getOne: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn()
|
|
};
|
|
const mockRunQt550 = jest.fn().mockResolvedValue({ ok: true });
|
|
|
|
jest.mock('../../db/db-connect', () => ({
|
|
db: { baseUrl: 'http://localhost:8090/', records: mockRecords }
|
|
}));
|
|
jest.mock('../axaBridgeService', () => ({
|
|
runQt550: (...args) => mockRunQt550(...args),
|
|
lookupContract: jest.fn(),
|
|
getHealth: jest.fn()
|
|
}));
|
|
|
|
const path = require('path');
|
|
const PizZip = require('pizzip');
|
|
const advaloService = require('../advaloService');
|
|
const { buildCommonDocContext, normalizeLookupInfo, normalizeMergedFallbackRow, renderAdvaloDocx, advaloCache } = advaloService.__private;
|
|
|
|
function docxText(buffer) {
|
|
const xml = new PizZip(buffer).file('word/document.xml').asText();
|
|
return (xml.match(/<w:t[^>]*>(.*?)<\/w:t>/g) || []).map((t) => t.replace(/<[^>]+>/g, '')).join(' ');
|
|
}
|
|
|
|
const actor = { userMatricule: 'A123BC', userAuthGroupe: 'MANAGER', userFirstName: 'Jean', userLastName: 'Dupont' };
|
|
|
|
describe('Advalo — parité v1 du calcul documentaire', () => {
|
|
test('buildCommonDocContext injecte les prix réellement saisis (pas les valeurs figées)', () => {
|
|
const row = { numContrat: '0000022126873304', capital: '100000', tarif: '336,00', dateDebut: '01/04/2026', dateFin: '02/04/2026', mode: 'Terrestre', marchandise: 'Moteurs' };
|
|
const contractInfo = { nomClient: 'DURAND & FILS', adresseAgent: '1 rue de Paris', postalAgent: '75001 PARIS', telAgent: '0102030405', faxAgent: '0102030406', numAgent: '0009', nomAgent: 'AGENCE' };
|
|
const pricing = { valeurAssuree: '100000', taux: '0.5', primeMinimum: '20', cotisationHT: '500', coutActe: '36', cotisationTTC: '536' };
|
|
|
|
const ctx = buildCommonDocContext(row, contractInfo, actor, pricing);
|
|
|
|
expect(ctx.tauxField).toBe('0,50');
|
|
expect(ctx.primeMinimum).toBe('20,00');
|
|
expect(ctx.cotisationField).toBe('500,00');
|
|
expect(ctx.coutActeField).toBe('36,00');
|
|
expect(ctx.cotisationTTC).toBe('536,00');
|
|
// Coordonnées agent propagées dans l'en-tête (parité v1 Avenant).
|
|
expect(ctx.adresseAgent).toBe('1 rue de Paris');
|
|
expect(ctx.telAgent).toBe('0102030405');
|
|
expect(ctx.faxAgent).toBe('0102030406');
|
|
// Valeurs brutes (pas de double-échappement XML) — docxtemplater échappera.
|
|
expect(ctx.nomClient).toBe('DURAND & FILS');
|
|
});
|
|
|
|
test('rendu réel Avenant_Ponctuel.docx (docxtemplater) contient les prix saisis + phrase TTC', () => {
|
|
const row = { numContrat: '0000022126873304', capital: '100000', tarif: '536,00', dateDebut: '01/04/2026', dateFin: '02/04/2026', mode: 'Terrestre, Aérien', marchandise: 'Moteurs', depart: 'Paris', arrivee: 'Lyon' };
|
|
const contractInfo = { nomClient: 'DURAND & FILS', adresseAgent: '1 rue de Paris' };
|
|
const pricing = { valeurAssuree: '100000', taux: '0.5', primeMinimum: '20', cotisationHT: '500', coutActe: '36', cotisationTTC: '536' };
|
|
const ctx = buildCommonDocContext(row, contractInfo, actor, pricing);
|
|
const tplPath = path.resolve(__dirname, '..', '..', 'templates', 'advalo', 'Avenant_Ponctuel.docx');
|
|
const text = docxText(renderAdvaloDocx(tplPath, ctx));
|
|
|
|
expect(text).toContain('500,00'); // cotisation HT
|
|
expect(text).toContain('536,00'); // TTC
|
|
expect(text).toContain('Il est perçu');
|
|
expect(text).toContain('Moteurs');
|
|
expect(text).toContain('DURAND & FILS'); // échappé une seule fois
|
|
});
|
|
|
|
test('buildCommonDocContext: défauts v1 (0,30 / 15 / 36) seulement à défaut de pricing, TTC = HT + acte', () => {
|
|
const row = { numContrat: '0000022126873304', capital: '3000', tarif: '15,00' };
|
|
const ctx = buildCommonDocContext(row, {}, actor, {});
|
|
|
|
expect(ctx.tauxField).toBe('0,30');
|
|
expect(ctx.primeMinimum).toBe('15,00');
|
|
expect(ctx.cotisationField).toBe('15,00'); // HT = tarif (grille déléguée)
|
|
expect(ctx.coutActeField).toBe('36,00');
|
|
expect(ctx.cotisationTTC).toBe('51,00'); // 15 + 36
|
|
});
|
|
});
|
|
|
|
describe('Advalo — lookup contrat (coordonnées intermédiaire)', () => {
|
|
test('normalizeLookupInfo conserve adresseAgent/postalAgent/telAgent/faxAgent', () => {
|
|
const raw = {
|
|
numContrat: '0000022126873304', numClient: '0858406820', nomClient: 'CLIENT',
|
|
nomAgent: 'AGENCE', numAgent: '0009876543',
|
|
adresseAgent: '1 rue de Paris', postalAgent: '75001 PARIS', telAgent: '0102030405', faxAgent: '0102030406'
|
|
};
|
|
const info = normalizeLookupInfo(raw, '0000022126873304');
|
|
expect(info.adresseAgent).toBe('1 rue de Paris');
|
|
expect(info.postalAgent).toBe('75001 PARIS');
|
|
expect(info.telAgent).toBe('0102030405');
|
|
expect(info.faxAgent).toBe('0102030406');
|
|
});
|
|
});
|
|
|
|
describe('Advalo — enrichissement Cumul/Reporting via référentiel contrat', () => {
|
|
test('normalizeMergedFallbackRow enrichit region/dpt/souscripteur depuis le ref (parité v1 getVarByNumContrat)', () => {
|
|
advaloCache.refContratById.set('0000022126873304', {
|
|
nomClient: 'KANGOUROUBOX', region: 'ILE DE FRANCE', dpt: '75', souscripteur: 'Z999ZZ'
|
|
});
|
|
const delegueeRow = normalizeMergedFallbackRow(
|
|
{ id: 'g1', numContrat: '0000022126873304', tarif: '15 €', statutFacturation: 'Non facturé' },
|
|
'deleguee'
|
|
);
|
|
expect(delegueeRow.region).toBe('ILE DE FRANCE');
|
|
expect(delegueeRow.dpt).toBe('75');
|
|
expect(delegueeRow.souscripteur).toBe('Z999ZZ');
|
|
expect(delegueeRow.nomClient).toBe('KANGOUROUBOX');
|
|
advaloCache.refContratById.delete('0000022126873304');
|
|
});
|
|
});
|
|
|
|
describe('Advalo — modification demande hors-grille (parité v1 "Modification")', () => {
|
|
beforeEach(() => {
|
|
Object.values(mockRecords).forEach((fn) => fn.mockReset());
|
|
advaloCache.loaded = false;
|
|
});
|
|
|
|
test('updateDemande met à jour les champs et journalise le pricing (event update)', async () => {
|
|
mockRecords.getOne.mockResolvedValue({
|
|
id: 'dem9', numContrat: '0000022126873304', numClient: '0858406820',
|
|
statutFacturation: 'Non facturé', isDeleted: false
|
|
});
|
|
mockRecords.update.mockImplementation(async (collection, id, patch) => ({ id, ...patch }));
|
|
mockRecords.create.mockResolvedValue({ id: 'aud9' });
|
|
|
|
const updated = await advaloService.updateDemande('d:dem9', {
|
|
marchandise: 'Pièces auto', mode: 'Terrestre, Maritime', depart: 'Lyon', arrivee: 'Gênes',
|
|
dateDebut: '05/04/2026', dateFin: '06/04/2026', capital: '50000',
|
|
taux: '0.4', primeMinimum: '15', coutActe: '36', cotisationHT: '200', cotisationTTC: '236'
|
|
}, actor);
|
|
|
|
expect(updated.marchandise).toBe('Pièces auto');
|
|
expect(updated.mode).toBe('Terrestre, Maritime');
|
|
expect(updated.tarif).toBe('236');
|
|
const audit = mockRecords.create.mock.calls.find((c) => c[0] === 'advalo_audit');
|
|
expect(audit[1].eventType).toBe('update');
|
|
expect(audit[1].data.pricing.cotisationHT).toBe('200');
|
|
});
|
|
|
|
test('updateDemande refuse une demande déjà facturée', async () => {
|
|
mockRecords.getOne.mockResolvedValue({ id: 'dem8', numContrat: '0000022126873304', statutFacturation: 'Facturé 01/04/2026', isDeleted: false });
|
|
await expect(advaloService.updateDemande('d:dem8', { marchandise: 'x', mode: 'Terrestre', depart: 'a', arrivee: 'b', dateDebut: '01/04/2026', dateFin: '02/04/2026', capital: '1', taux: '0.3', primeMinimum: '15', cotisationHT: '15', cotisationTTC: '51' }, actor))
|
|
.rejects.toThrow(/facturée ne peut pas être modifiée/);
|
|
});
|
|
});
|
|
|
|
describe('Advalo — facturation QT550 (parité v1: HT à Pos=329, coût d acte une seule fois)', () => {
|
|
const realPlatform = process.platform;
|
|
|
|
beforeEach(() => {
|
|
Object.values(mockRecords).forEach((fn) => fn.mockReset());
|
|
mockRunQt550.mockClear().mockResolvedValue({ ok: true });
|
|
// Cache vide forcé à chaque test pour isoler.
|
|
advaloCache.loaded = false;
|
|
advaloCache.loadingPromise = null;
|
|
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
|
|
});
|
|
|
|
afterEach(() => {
|
|
Object.defineProperty(process, 'platform', { value: realPlatform, configurable: true });
|
|
});
|
|
|
|
test('facturerBatch envoie la cotisation HT (audit) et le coût d acte 36 une seule fois — pas de double comptage', async () => {
|
|
const demande = {
|
|
id: 'dem1', numContrat: '0000022126873304', numClient: '0858406820', nomClient: 'CLIENT',
|
|
capital: '100000', tarif: '336,00', // TTC stocké (HT 300 + 36)
|
|
dateDebut: '01/04/2026', dateFin: '02/04/2026',
|
|
statutFacturation: 'Non facturé', isDeleted: false
|
|
};
|
|
const auditRec = {
|
|
eventType: 'create', created: '2026-04-01T10:00:00.000Z',
|
|
data: { demandeId: 'dem1', pricing: { cotisationHT: '300,00', coutActe: '36,00', cotisationTTC: '336,00' } }
|
|
};
|
|
|
|
mockRecords.getOne.mockImplementation(async (collection, id) => {
|
|
if (collection === 'advalo_demande' && id === 'dem1') return { ...demande };
|
|
throw new Error(`unexpected getOne ${collection} ${id}`);
|
|
});
|
|
mockRecords.getList.mockImplementation(async (collection) => {
|
|
if (collection === 'advalo_audit') return { items: [auditRec], totalItems: 1, totalPages: 1 };
|
|
if (collection === 'advalo_facturation_batch') return { items: [], totalItems: 0, totalPages: 1 };
|
|
return { items: [], totalItems: 0, totalPages: 1 };
|
|
});
|
|
mockRecords.create.mockImplementation(async (collection, payload) => ({ id: `${collection}-1`, ...payload }));
|
|
mockRecords.update.mockImplementation(async (collection, id, patch) => ({ id, ...patch }));
|
|
|
|
const result = await advaloService.facturerBatch(
|
|
{ demandeIds: ['d:dem1'], sourceMode: 'hors_grille', includeTransportDetails: true },
|
|
actor
|
|
);
|
|
|
|
expect(result.idempotent).toBe(false);
|
|
|
|
// QT550 reçoit le HT (300), PAS le TTC (336), et coutActe '36' une seule fois.
|
|
expect(mockRunQt550).toHaveBeenCalledTimes(1);
|
|
const qtArgs = mockRunQt550.mock.calls[0][0];
|
|
expect(qtArgs.totalCotisation).toBe(300);
|
|
expect(qtArgs.coutActe).toBe('36');
|
|
|
|
// Le lot persiste le total HT + l acte séparé.
|
|
const batchCreate = mockRecords.create.mock.calls.find((c) => c[0] === 'advalo_facturation_batch');
|
|
expect(batchCreate).toBeDefined();
|
|
expect(batchCreate[1].totalCotisation).toBe(300);
|
|
expect(batchCreate[1].totalActe).toBe(advaloService.__private.COUT_ACTE);
|
|
});
|
|
});
|