Files
Investidor-V2/tests/frontend/iva-currency-events.test.mjs
T

287 lines
9.5 KiB
JavaScript

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',
);
});