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

360 lines
14 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {
recordSales,
saveSalesBook,
} from '../../resources/js/lib/iva-operations.ts';
import {
recordAcquisitions,
saveAcquisitionBook,
acquisitionBook,
} from '../../resources/js/lib/iva-acquisitions.ts';
import {
closureBook,
closureCurrent,
closureSources,
closureReportRows,
recordClosure,
} from '../../resources/js/lib/iva-closure.ts';
import {
newSubstitution,
previousDeclarations,
substitutionBook,
recordSubstitution,
saveSubstitutionBook,
substitutionCurrent,
substitutionReportRows,
} from '../../resources/js/lib/iva-substitution.ts';
const frozen = JSON.parse(
fs.readFileSync(
new URL('./fixtures/iva-l3c-preserved.json', import.meta.url),
'utf8',
),
);
const old = JSON.parse(
fs.readFileSync(
new URL('./fixtures/iva-l3ab-preserved.json', import.meta.url),
'utf8',
),
);
function fixture() {
const d = structuredClone(frozen),
base = JSON.parse(old.modelo_a_snapshot),
snap = JSON.parse(d.modelo_a_snapshot);
for (const [k, v] of Object.entries(base))
if (k !== 'version') snap[k] = { ...v, ...snap[k] };
d.modelo_a_snapshot = JSON.stringify(snap);
const sales = snap.salesOperations['opportunity:373'];
sales.rows[2].amount = '1375';
d.modelo_a_snapshot = saveSalesBook(d, recordSales(d, sales));
d.modelo_a_snapshot = saveAcquisitionBook(
d,
recordAcquisitions(d, acquisitionBook(d), '2026-10-25T12:30:00.000Z'),
);
const prior = previousDeclarations(d).find((p) => p.period === '2026-03'),
source = closureSources(d)
.filter((s) => s.period === '2026-03')
.at(-1);
const row = {
...newSubstitution('sub-Mar'),
prior,
source,
priorReference: 'SYNTHETIC QA declaraçãoMarço',
priorDate: '2026-04-15',
reference: 'SYNTHETIC QA substituiçãoMarço',
date: '2026-05-10',
reason: 'SYNTHETIC QA operação de Março corrigida',
interest: 'documented',
interestAmount: '1',
interestRef: 'SYNTHETIC QA juros adicionais identificados',
payments: [
{
id: 'payment2',
reference: 'SYNTHETIC QA reciboP2',
date: '2026-05-11',
amount: '20',
},
],
};
return { d, b: { version: 1, rows: [row] } };
}
const value = (r, f) =>
r.fields[f] && `${r.fields[f].numerator}/${r.fields[f].denominator}`;
function alternative(d, debit, credit) {
const s = JSON.parse(d.modelo_a_snapshot),
calc = structuredClone(s.books['opportunity:373'].result);
calc.revision += 10;
calc.calculatedAt += '-unit';
const p = calc.periods.find((p) => p.period === '2026-03');
p.fields['17'] = String(debit);
p.fields['18'] = String(credit);
p.exactFields['17'] = { numerator: String(debit), denominator: '1' };
p.exactFields['18'] = { numerator: String(credit), denominator: '1' };
s.books['opportunity:373'].history.push(calc);
d.modelo_a_snapshot = JSON.stringify(s);
return closureSources(d).find(
(p) => p.revision === calc.revision && p.period === '2026-03',
);
}
void test('real corrected L2 version gives additional60 plus1;original72/30/42 and credits remain unchanged', () => {
const { d, b } = fixture(),
before = closureBook(d),
r = recordSubstitution(d, b).result.rows[0];
assert.equal(r.status, 'determined', r.reason);
assert.equal(value(r, '21'), '60/1');
assert.equal(value(r, '22'), '60/1');
assert.equal(value(r, '24'), '61/1');
assert.deepEqual(r.paid, { numerator: '20', denominator: '1' });
assert.deepEqual(r.outstanding, { numerator: '41', denominator: '1' });
assert.deepEqual(r.totalOutstanding, { numerator: '83', denominator: '1' });
assert.deepEqual(r.totalPaid, { numerator: '50', denominator: '1' });
for (const f of ['19', '20', '25', '26', '27'])
assert.equal(r.fields[f], null);
d.modelo_a_snapshot = saveSubstitutionBook(d, recordSubstitution(d, b));
assert.deepEqual(closureBook(d), before);
assert.ok(closureCurrent(d, before));
assert.match(
closureReportRows(d)
.map((r) => r.join(' '))
.join(' '),
/Calculado24:72\/1;pagamentos registados:30\/1;por liquidar:42\/1/,
);
});
void test('quadro9 four sign cases use exact difference;credit waits for communication and never offsets original debt', () => {
for (const [priorKind, actual17, actual18, expected] of [
['debit', 220, 0, '60/1'],
['credit', 20, 0, '120/1'],
['credit', 0, 80, '20/1'],
['debit', 0, 40, '-200/1'],
]) {
const { d, b } = fixture();
if (priorKind === 'credit') {
const jan = previousDeclarations(d).find(
(p) => p.period === '2026-01',
);
b.rows[0].prior = { ...jan };
b.rows[0].prior.period = '2026-03'; // use an actual frozen credit-origin version in March below
const snap = JSON.parse(d.modelo_a_snapshot),
ab = snap.ivaClosures['opportunity:373'],
calc = structuredClone(ab.result),
r = calc.rows[2];
r.fields['17'] = { numerator: '0', denominator: '1' };
r.fields['18'] = { numerator: '100', denominator: '1' };
r.id = 'unit-credit-Mar';
calc.inputs[2].id = r.id;
ab.history.push(calc);
d.modelo_a_snapshot = JSON.stringify(snap);
b.rows[0].prior = previousDeclarations(d).find(
(p) => p.id === 'unit-credit-Mar',
);
}
b.rows[0].source = alternative(d, actual17, actual18);
const r = recordSubstitution(d, b).result.rows[0];
assert.equal(r.status, 'determined', r.reason);
assert.equal(value(r, '21'), expected);
if (expected.startsWith('-')) {
assert.equal(value(r, '22'), '0/1');
assert.deepEqual(r.pendingCredit, {
numerator: '200',
denominator: '1',
});
assert.equal(value(r, '26'), null);
assert.equal(r.totalOutstanding.numerator, '42');
}
}
});
void test('missing facts,wrong period,currency,unknown source and interest never produce presumed zero', () => {
const { d, b } = fixture();
for (const patch of [
{ prior: null },
{ source: null },
{ priorReference: '' },
{ priorDate: '' },
{ date: '2026-04-01' },
{ reference: '' },
{ reason: '' },
{ interest: '' },
]) {
const bad = structuredClone(b);
Object.assign(bad.rows[0], patch);
assert.equal(
recordSubstitution(d, bad).result.rows[0].status,
'indeterminate',
JSON.stringify(patch),
);
}
const bad = structuredClone(b);
bad.rows[0].source = closureSources(d).find((s) => s.period === '2026-02');
assert.equal(recordSubstitution(d, bad).result.rows[0].fields['21'], null);
assert.equal(
recordSubstitution({ ...d, currency: 'USD' }, b).result.rows[0].status,
'indeterminate',
);
});
void test('old payment cannot be re-used and two substitutions cannot repeat one predecessor or payment', () => {
const { d, b } = fixture();
b.rows[0].payments[0].reference = 'SYNTHETIC QA reciboP1';
assert.match(
recordSubstitution(d, b).result.rows[0].reason,
/Pagamento já/,
);
const f = fixture();
f.b.rows.push({ ...structuredClone(f.b.rows[0]), id: 'copy' });
assert.ok(
recordSubstitution(f.d, f.b).result.rows.every(
(r) => r.status === 'indeterminate',
),
);
const g = fixture();
g.b.rows[0].payments.push({ ...g.b.rows[0].payments[0], id: 'copy' });
assert.equal(recordSubstitution(g.d, g.b).result.rows[0].paid, null);
});
void test('successive substitution links frozen predecessor,charges only new difference and preserves all payments', () => {
const { d, b } = fixture();
let book = recordSubstitution(d, b, '2026-10-25T12:31:00Z');
d.modelo_a_snapshot = saveSubstitutionBook(d, book);
const next = structuredClone(book.rows[0]);
next.id = 'sub-Mar-2';
next.prior = previousDeclarations(d).find((p) => p.kind === 'C');
next.source = alternative(d, 250, 0);
next.priorReference = next.reference;
next.priorDate = next.date;
next.reference = 'SYNTHETIC QA segunda substituição';
next.date = '2026-06-10';
next.interest = 'none';
next.interestRef = 'SYNTHETIC QA sem juros adicionais';
next.payments = [];
book = recordSubstitution(d, { ...book, rows: [...book.rows, next] });
const r = book.result.rows[1];
assert.equal(r.status, 'determined', r.reason);
assert.equal(value(r, '21'), '30/1');
assert.deepEqual(r.totalOutstanding, {
numerator: '113',
denominator: '1',
});
assert.deepEqual(r.totalPaid, { numerator: '50', denominator: '1' });
assert.equal(book.history.length, 1);
next.payments = [
{
id: 'reuse',
reference: 'SYNTHETIC QA reciboP2',
date: '2026-06-11',
amount: '20',
},
];
assert.equal(
recordSubstitution(d, { ...book, rows: [book.rows[0], next] }).result
.rows[1].paid,
null,
);
});
void test('save/recover retains every L1/L2/AB map;report uses frozen inputs and recalculation appends history', () => {
const { d, b } = fixture(),
before = JSON.parse(d.modelo_a_snapshot);
let book = recordSubstitution(d, b);
d.modelo_a_snapshot = saveSubstitutionBook(d, book);
assert.ok(substitutionCurrent(d, substitutionBook(d)));
const stored = JSON.parse(d.modelo_a_snapshot);
for (const [k, v] of Object.entries(before))
assert.deepEqual(stored[k], v, k);
book.rows[0].reference = 'new deliberate edit';
assert.equal(substitutionCurrent(d, book), false);
d.modelo_a_snapshot = saveSubstitutionBook(d, book);
const report = substitutionReportRows(d)
.map((r) => r.join(' '))
.join(' ');
assert.match(report, /SYNTHETIC QA substituiçãoMarço/);
assert.doesNotMatch(report, /new deliberate edit/);
const oldResult = structuredClone(book.result);
book = recordSubstitution(d, book);
assert.deepEqual(book.history.at(-1), oldResult);
d.modelo_a_snapshot = saveSubstitutionBook(d, book);
assert.ok(substitutionCurrent(d, book));
assert.match(
substitutionReportRows(d)
.map((r) => r.join(' '))
.join(' '),
/new deliberate edit/,
);
});
void test('fractional delta remains exact and overpayment does not generate communicated credit', () => {
const { d, b } = fixture();
const s = alternative(d, 220, 0),
snap = JSON.parse(d.modelo_a_snapshot),
calc = snap.books['opportunity:373'].history.at(-1);
calc.periods[2].exactFields['17'] = { numerator: '661', denominator: '3' };
d.modelo_a_snapshot = JSON.stringify(snap);
b.rows[0].source = closureSources(d).find(
(x) => x.revision === s.revision && x.period === '2026-03',
);
b.rows[0].payments[0].amount = '100';
const r = recordSubstitution(d, b).result.rows[0];
assert.equal(value(r, '21'), '181/3');
assert.deepEqual(r.overpayment, { numerator: '116', denominator: '3' });
assert.equal(r.pendingCredit.numerator, '0');
assert.equal(r.fields['20'], null);
});
void test('a payment already frozen in C cannot be added again in A/B;original valid A/B remains current', () => {
const { d, b } = fixture();
d.modelo_a_snapshot = saveSubstitutionBook(d, recordSubstitution(d, b));
const ab = closureBook(d);
assert.ok(closureCurrent(d, ab));
ab.rows[2].payments.push({ ...b.rows[0].payments[0] });
assert.equal(closureCurrent(d, ab), false);
const r = recordClosure(d, ab).result.rows[2];
assert.equal(r.paid, null);
assert.match(r.reason, /Pagamento/);
});
void test('a newly conflicting payment in recovered AB makes C stale and requires reconciliation', () => {
const { d, b } = fixture();
const book = recordSubstitution(d, b);
d.modelo_a_snapshot = saveSubstitutionBook(d, book);
assert.ok(substitutionCurrent(d, book));
const s = JSON.parse(d.modelo_a_snapshot);
s.ivaClosures['opportunity:373'].rows[2].payments.push({
...b.rows[0].payments[0],
});
d.modelo_a_snapshot = JSON.stringify(s);
assert.equal(substitutionCurrent(d, book), false);
assert.equal(recordSubstitution(d, book).result.rows[0].paid, null);
});
void test('a substitution cannot link back through a circular predecessor chain', () => {
const { d, b } = fixture();
let book = recordSubstitution(d, b, '2026-10-25T12:31:00Z');
d.modelo_a_snapshot = saveSubstitutionBook(d, book);
const child = {
...structuredClone(book.rows[0]),
id: 'child',
prior: previousDeclarations(d).find((p) => p.kind === 'C'),
priorReference: book.rows[0].reference,
priorDate: book.rows[0].date,
date: '2026-06-10',
reference: 'child-ref',
payments: [],
};
book = recordSubstitution(
d,
{ ...book, rows: [...book.rows, child] },
'2026-10-25T12:32:00Z',
);
d.modelo_a_snapshot = saveSubstitutionBook(d, book);
book.rows[0].prior = previousDeclarations(d).find(
(p) => p.kind === 'C' && p.id === 'child',
);
assert.ok(
recordSubstitution(d, book).result.rows.every((r) =>
/circular/.test(r.reason),
),
);
});
void test('export synthetic demonstrated correction for guarded private API and actual component proof', () => {
const { d, b } = fixture();
const result = recordSubstitution(d, b, '2026-10-25T12:31:00.000Z');
const again = recordSubstitution(d, result, '2026-10-25T12:32:00.000Z');
d.modelo_a_snapshot = saveSubstitutionBook(d, again);
assert.ok(substitutionCurrent(d, again));
if (process.env.IVA_L3C_CALCULATED)
fs.writeFileSync(process.env.IVA_L3C_CALCULATED, JSON.stringify(d));
});