import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { newCurrencyEvent, currencyEventTreatments, evaluateCurrencyEvent, recordCurrencyEvents, saveCurrencyEventBook, currencyEventBook, currencyEventCurrent, currencyEventReportRows, switchCurrencyEvent, } from '../../resources/js/lib/iva-currency-events.ts'; const frozen = JSON.parse( fs.readFileSync( 'tests/frontend/fixtures/iva-currency-events-preserved.json', 'utf8', ), ); const draft = { ...frozen, currency: 'MZN', context_opportunity: 'opportunity:357', project_title: 'SYNTHETIC QA — Moeda e eventos / Manica', }; function row(t) { return { ...newCurrencyEvent(t, 'MZN'), treatment: t, description: 'SYNTHETIC QA ' + t, period: '2026-12', origin: 'projected', operationRef: 'SYNTHETIC QA operação existente', documentRef: 'SYNTHETIC QA documento/pressuposto', documentDate: '2026-12-12', scope: 'SYNTHETIC QA conjunto/parte/operação/período', evidence: 'SYNTHETIC QA pressupostos expressos', eventDate: '2026-12-10', eventRef: 'SYNTHETIC QA evento específico/artigo7,excepções próprias conferidas', otherDate: t === 'consignment_deadline' ? '2026-06-13' : t === 'contract_effects' ? '2026-12-01' : '2026-12-12', reviewDate: '2026-12-31', factsConfirmed: 'yes', invoiceRequired: 'yes', invoiceState: 'issued', invoiceDate: '2026-12-12', invoiceRef: 'SYNTHETIC QA factura', legalDeadline: '2026-12-17', deadlineRef: 'SYNTHETIC QA prazo/disposição/calendário identificados,não regra vigente presumida', paymentState: t === 'advance_payment' ? 'received' : 'none', paymentDate: '2026-12-09', paymentRef: 'SYNTHETIC QA recebimento identificado', amount: '1000.00', paymentAmount: '200.00', sourceCurrency: 'USD', fxRate: '64.5', fxDate: '2026-12-10', fxSource: 'SYNTHETIC QA taxa/exemplar', fxRuleRef: 'SYNTHETIC QA pressuposto da taxa/data,não base fiscal', fxDirection: 'mzn_per_unit', }; } for (const rule of currencyEventTreatments) void test(rule.branch + ' ' + rule.id + ' own event and noVAT/base', () => { const r = row(rule.id), v = evaluateCurrencyEvent(r); assert.equal( v.status, rule.branch === 'P104' ? 'conditional' : 'determined', ); assert.equal(v.baseAmount, null); assert.equal(v.vat, null); assert.equal(v.rate, null); if (rule.branch === 'P104') assert.equal(v.scenarioAmount, '64500.00'); else { assert.equal( v.dueEvents.at(-1).date, ['import_clearance', 'import_auction'].includes(rule.id) ? '2026-12-10' : '2026-12-12', ); assert.equal( v.eventDate, rule.id === 'advance_payment' ? null : rule.id === 'consignment_deadline' ? '2026-12-10' : '2026-12-10', ); } for (const f of ['operationRef', 'documentRef', 'scope', 'evidence']) assert.equal( evaluateCurrencyEvent({ ...r, [f]: '' }).status, 'indeterminate', ); assert.equal( evaluateCurrencyEvent({ ...r, factsConfirmed: '' }).status, 'indeterminate', ); assert.equal( evaluateCurrencyEvent({ ...r, factsConfirmed: 'no' }).status, 'not_applicable', ); }); void test('P131/P132 invoice intime/late/absent,partial payment and no duplicate remainder', () => { const r = row('goods_available'); let v = evaluateCurrencyEvent({ ...r, paymentState: 'received' }); assert.deepEqual( v.dueEvents.map((x) => [x.date, x.amount]), [ ['2026-12-09', '200.00'], ['2026-12-12', '800.00'], ], ); v = evaluateCurrencyEvent({ ...r, invoiceDate: '2026-12-20' }); assert.equal(v.dueEvents[0].date, '2026-12-17'); v = evaluateCurrencyEvent({ ...r, invoiceState: 'not_issued' }); assert.equal(v.dueEvents[0].date, '2026-12-17'); v = evaluateCurrencyEvent({ ...r, invoiceState: 'not_issued', reviewDate: '2026-12-11', paymentState: 'received', }); assert.equal(v.dueEvents.length, 1); assert.equal(v.remainingPending, true); v = evaluateCurrencyEvent({ ...r, paymentState: 'received', paymentDate: '2026-12-19', invoiceDate: '2026-12-20', }); assert.deepEqual( v.dueEvents.map((x) => [x.date, x.amount]), [['2026-12-17', '1000.00']], ); v = evaluateCurrencyEvent({ ...r, paymentState: 'received', paymentAmount: '1000', }); assert.equal(v.dueEvents.length, 1); assert.equal(v.dueEvents[0].amount, '1000.00'); for (const patch of [ { paymentState: '' }, { legalDeadline: '' }, { deadlineRef: '' }, { invoiceState: '' }, { paymentState: 'received', paymentAmount: '1001' }, { paymentState: 'received', amount: '' }, ]) assert.equal( evaluateCurrencyEvent({ ...r, ...patch }).status, 'indeterminate', ); assert.equal( evaluateCurrencyEvent({ ...r, invoiceRequired: 'no', amount: '' }) .dueEvents[0].amount, null, ); assert.equal( evaluateCurrencyEvent({ ...r, eventDate: '2026-12-20', reviewDate: '2026-12-11', invoiceState: 'not_issued', legalDeadline: '2026-12-28', paymentState: 'received', }).dueEvents[0].date, '2026-12-09', ); }); void test('own events,consignmentterm,servicecharge,contractexceptions and dates', () => { assert.equal( evaluateCurrencyEvent({ ...row('consignment_deadline'), otherDate: '' }) .status, 'indeterminate', ); assert.equal( evaluateCurrencyEvent({ ...row('consignment_deadline'), invoiceRequired: 'no', }).dueEvents[0].date, '2026-12-10', ); assert.equal( evaluateCurrencyEvent({ ...row('service_early_charge'), otherDate: '2026-12-09', }).status, 'indeterminate', ); assert.equal( evaluateCurrencyEvent({ ...row('contract_effects'), otherDate: '2026-12-11', }).status, 'indeterminate', ); for (const field of ['reviewDate', 'eventDate', 'documentDate']) assert.equal( evaluateCurrencyEvent({ ...row('goods_available'), [field]: '2026-02-30', }).status, 'indeterminate', ); const r = row('own_goods'), sw = switchCurrencyEvent(r, 'foreign_scenario'); assert.equal(sw.eventDate, r.eventDate); assert.equal(sw.fxSource, r.fxSource); assert.equal(sw.factsConfirmed, ''); }); void test('P104 conditionalconversion direction/exactrounding/missingparams never fiscalbase', () => { const r = row('foreign_scenario'); assert.equal( evaluateCurrencyEvent({ ...r, fxDirection: 'units_per_mzn', fxRate: '2', amount: '100', }).scenarioAmount, '50.00', ); assert.equal( evaluateCurrencyEvent({ ...r, amount: '0.000001', fxRate: '1.5' }) .scenarioAmount, '0.000002', ); for (const patch of [ { fxRate: '0' }, { fxRate: '-1' }, { fxSource: '' }, { fxDate: '' }, { fxRuleRef: '' }, { fxDirection: '' }, { sourceCurrency: 'MZN' }, ]) assert.equal( evaluateCurrencyEvent({ ...r, ...patch }).status, 'indeterminate', ); assert.equal(evaluateCurrencyEvent(r).baseAmount, null); }); void test('snapshot/history/report preserve every delivered book and contextual scope', () => { const rows = currencyEventTreatments.map((t) => row(t.id)), a = recordCurrencyEvents(draft, { rows }), next = { ...draft, modelo_a_snapshot: saveCurrencyEventBook(draft, a) }, b = recordCurrencyEvents(next, a); next.modelo_a_snapshot = saveCurrencyEventBook(next, b); assert.equal(b.history.length, 1); assert.equal(currencyEventCurrent(next, b), true); assert.equal( currencyEventCurrent(next, { ...b, rows: rows.slice(1) }), false, ); assert.equal(currencyEventReportRows(next).length, 15); assert.deepEqual( currencyEventBook({ ...next, context_opportunity: 'opportunity:358' }), { rows: [] }, ); const store = JSON.parse(next.modelo_a_snapshot); delete store.currencyEvents; assert.deepEqual(store, JSON.parse(frozen.modelo_a_snapshot)); assert.throws(() => recordCurrencyEvents(next, { rows: [rows[0], rows[0]] }), ); assert.throws(() => currencyEventBook({ ...next, modelo_a_snapshot: JSON.stringify({ ...store, currencyEvents: [] }), }), ); if (process.env.IVA_CURRENCY_EVENTS_FIXTURE) fs.writeFileSync( process.env.IVA_CURRENCY_EVENTS_FIXTURE, JSON.stringify(next, null, 2) + '\n', ); });