417 lines
15 KiB
JavaScript
417 lines
15 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,
|
|
acquisitionCurrent,
|
|
} from '../../resources/js/lib/iva-acquisitions.ts';
|
|
import {
|
|
closureBook,
|
|
closureSources,
|
|
newClosure,
|
|
newClosureCredit,
|
|
recordClosure,
|
|
saveClosureBook,
|
|
adoptPreviousCredit,
|
|
closureCurrent,
|
|
closureReportRows,
|
|
} from '../../resources/js/lib/iva-closure.ts';
|
|
const preserved = JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/iva-l3ab-preserved.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
export function closureFixture() {
|
|
const draft = {
|
|
...structuredClone(preserved),
|
|
context_opportunity: 'opportunity:373',
|
|
project_title: 'SYNTHETIC QA — L3-A/B fecho do IVA',
|
|
};
|
|
const s = JSON.parse(draft.modelo_a_snapshot),
|
|
base = s.books['opportunity:370'];
|
|
const sales = ['2026-01', '2026-02', '2026-03'].map((period, i) => ({
|
|
...structuredClone(s.salesOperations['opportunity:370'].rows[0]),
|
|
id: 'sale-' + i,
|
|
period,
|
|
amount: i === 1 ? '500' : '1000',
|
|
sourcePeriod: period,
|
|
}));
|
|
draft.modelo_a_snapshot = saveSalesBook(
|
|
draft,
|
|
recordSales(draft, {
|
|
currency: 'MZN',
|
|
rows: sales,
|
|
periods: sales.map((r) => ({ period: r.period, complete: true })),
|
|
}),
|
|
);
|
|
const purchase = {
|
|
...structuredClone(base.rows[0]),
|
|
id: 'purchase-Jan',
|
|
period: '2026-01',
|
|
documentDate: '2026-01-10',
|
|
documentRef: 'SYNTHETIC QA aquisiçãoJaneiro',
|
|
net: '1625',
|
|
vat: '260',
|
|
gross: '1885',
|
|
d: {
|
|
...structuredClone(base.rows[0].d),
|
|
numerator: '1',
|
|
denominator: '1',
|
|
},
|
|
};
|
|
const acquisitions = recordAcquisitions(draft, {
|
|
currency: 'MZN',
|
|
taxpayerRegistered: 'yes',
|
|
rows: [purchase],
|
|
periods: sales.map((r) => ({
|
|
period: r.period,
|
|
complete: true,
|
|
importsComplete: true,
|
|
absent: ['12', '13'],
|
|
})),
|
|
});
|
|
draft.modelo_a_snapshot = saveAcquisitionBook(draft, acquisitions);
|
|
const make = (period) => ({
|
|
...newClosure(
|
|
'close-' + period,
|
|
closureSources(draft).find((s) => s.period === period),
|
|
'SYNTHETIC QA entidade373',
|
|
),
|
|
firstDeclaration: 'yes',
|
|
creditsComplete: 'yes',
|
|
filing: 'timely',
|
|
submissionDate: `2026-${String(Number(period.slice(5)) + 1).padStart(2, '0')}-15`,
|
|
filingRef: 'SYNTHETIC QA entrega em prazo assumida',
|
|
interest: 'none',
|
|
interestRef: 'SYNTHETIC QA pressuposto sem juros',
|
|
destination: 'report',
|
|
});
|
|
const jan = make('2026-01');
|
|
jan.credits = [
|
|
{
|
|
...newClosureCredit('opening', 'opening'),
|
|
period: '2025-12',
|
|
reference: 'SYNTHETIC QA saldoabertura',
|
|
amount: '50',
|
|
},
|
|
];
|
|
let book = recordClosure(draft, { version: 1, rows: [jan] });
|
|
const feb = make('2026-02');
|
|
feb.credits = [
|
|
adoptPreviousCredit(
|
|
newClosureCredit('carry-Feb', 'previous'),
|
|
book.result.rows[0],
|
|
),
|
|
];
|
|
book = recordClosure(draft, { ...book, rows: [jan, feb] });
|
|
const mar = make('2026-03');
|
|
mar.credits = [
|
|
adoptPreviousCredit(
|
|
newClosureCredit('carry-Mar', 'previous'),
|
|
book.result.rows[1],
|
|
),
|
|
{
|
|
...newClosureCredit('communication', 'communication'),
|
|
period: '2026-02',
|
|
reference: 'SYNTHETIC QA comunicaçãoAT20',
|
|
date: '2026-03-10',
|
|
amount: '20',
|
|
},
|
|
];
|
|
mar.interest = 'documented';
|
|
mar.interestAmount = '2';
|
|
mar.interestRef = 'SYNTHETIC QA jurosidentificados';
|
|
mar.payments = [
|
|
{
|
|
id: 'payment1',
|
|
reference: 'SYNTHETIC QA reciboP1',
|
|
date: '2026-04-16',
|
|
amount: '30',
|
|
},
|
|
];
|
|
book = recordClosure(draft, { ...book, rows: [jan, feb, mar] });
|
|
return { draft, book, make, acquisitions };
|
|
}
|
|
const amount = (r, f) =>
|
|
r.fields[f] && `${r.fields[f].numerator}/${r.fields[f].denominator}`;
|
|
void test('three periods reconcile opening credit,carry,communicated credit,interest and payment without double deduction', () => {
|
|
const { draft, book } = closureFixture();
|
|
assert.deepEqual(
|
|
book.result.rows.map((r) => r.status),
|
|
['determined', 'determined', 'determined'],
|
|
);
|
|
assert.deepEqual(
|
|
book.result.rows.map((r) => amount(r, '26')),
|
|
['150/1', '70/1', '0/1'],
|
|
);
|
|
const mar = book.result.rows[2];
|
|
assert.equal(amount(mar, '19'), '70/1');
|
|
assert.equal(amount(mar, '20'), '20/1');
|
|
assert.equal(amount(mar, '22'), '70/1');
|
|
assert.equal(amount(mar, '24'), '72/1');
|
|
assert.deepEqual(mar.paid, { numerator: '30', denominator: '1' });
|
|
assert.deepEqual(mar.outstanding, { numerator: '42', denominator: '1' });
|
|
assert.equal(mar.fields['21'], null);
|
|
assert.equal(amount(mar, '27'), '0/1');
|
|
assert.ok(acquisitionCurrent(draft, acquisitionBook(draft)));
|
|
assert.equal(book.history.length, 2);
|
|
});
|
|
void test('origin,coverage,deadline and unsupported paths remain explicit;unknown never becomes zero', () => {
|
|
const { draft, book } = closureFixture();
|
|
for (const patch of [
|
|
{ creditsComplete: '' },
|
|
{ filing: '' },
|
|
{ filing: 'late' },
|
|
{ firstDeclaration: 'no' },
|
|
{ entityRef: '' },
|
|
{ submissionDate: '' },
|
|
{ destination: 'refund' },
|
|
]) {
|
|
const changed = structuredClone(book);
|
|
changed.rows[0] = { ...changed.rows[0], ...patch };
|
|
const r = recordClosure(draft, changed).result.rows;
|
|
assert.equal(r[0].status, 'indeterminate', JSON.stringify(patch));
|
|
assert.equal(r[1].status, 'indeterminate');
|
|
}
|
|
const interest = structuredClone(book);
|
|
interest.rows[2].interest = '';
|
|
const r = recordClosure(draft, interest).result.rows[2];
|
|
assert.equal(amount(r, '22'), '70/1');
|
|
assert.equal(r.fields['24'], null);
|
|
const future = structuredClone(book);
|
|
future.rows[2].credits[1].date = '2026-05-01';
|
|
assert.equal(
|
|
recordClosure(draft, future).result.rows[2].fields['20'],
|
|
null,
|
|
);
|
|
const effective = structuredClone(book);
|
|
effective.rows[0].origin = 'effective';
|
|
assert.match(
|
|
recordClosure(draft, effective).result.rows[0].reason,
|
|
/projectados/,
|
|
);
|
|
});
|
|
void test('duplicate documents,opening versus communication and payment references cannot create duplicate use', () => {
|
|
const { draft, book } = closureFixture();
|
|
const b = structuredClone(book);
|
|
b.rows[0].credits.push({ ...b.rows[0].credits[0], id: 'copy' });
|
|
assert.equal(recordClosure(draft, b).result.rows[0].fields['19'], null);
|
|
const cross = structuredClone(book);
|
|
cross.rows[0].credits.push({
|
|
...cross.rows[0].credits[0],
|
|
id: 'copy',
|
|
kind: 'communication',
|
|
date: '2026-01-05',
|
|
});
|
|
assert.equal(recordClosure(draft, cross).result.rows[0].fields['19'], null);
|
|
const payments = structuredClone(book);
|
|
payments.rows[2].payments.push({
|
|
...payments.rows[2].payments[0],
|
|
id: 'copy',
|
|
});
|
|
const p = recordClosure(draft, payments).result.rows[2];
|
|
assert.equal(p.paid, null);
|
|
assert.equal(amount(p, '26'), '0/1');
|
|
assert.equal(amount(p, '24'), '72/1');
|
|
const opening = structuredClone(book);
|
|
opening.rows[1].credits = [
|
|
{ ...opening.rows[0].credits[0], id: 'new-opening', period: '2026-01' },
|
|
];
|
|
assert.match(
|
|
recordClosure(draft, opening).result.rows[1].reason,
|
|
/reutilize26/,
|
|
);
|
|
});
|
|
void test('changing a predecessor preserves historical results and requires explicit carry adoption;payment edit does not change carry', () => {
|
|
const { draft, book } = closureFixture();
|
|
const first = structuredClone(book.result);
|
|
let changed = structuredClone(book);
|
|
changed.rows[0].credits[0].amount = '60';
|
|
assert.equal(closureCurrent(draft, changed), false);
|
|
changed = recordClosure(draft, changed);
|
|
assert.equal(amount(changed.result.rows[0], '26'), '160/1');
|
|
assert.equal(changed.result.rows[1].status, 'indeterminate');
|
|
assert.deepEqual(changed.history.at(-1), first);
|
|
changed.rows[1].credits[0] = adoptPreviousCredit(
|
|
changed.rows[1].credits[0],
|
|
changed.result.rows[0],
|
|
);
|
|
changed = recordClosure(draft, changed);
|
|
assert.equal(amount(changed.result.rows[1], '26'), '80/1');
|
|
assert.equal(changed.result.rows[2].status, 'indeterminate');
|
|
changed.rows[2].credits[0] = adoptPreviousCredit(
|
|
changed.rows[2].credits[0],
|
|
changed.result.rows[1],
|
|
);
|
|
changed = recordClosure(draft, changed);
|
|
assert.equal(amount(changed.result.rows[2], '22'), '60/1');
|
|
const pay = structuredClone(book);
|
|
pay.rows[0].payments = [
|
|
{
|
|
id: 'unexpected',
|
|
reference: 'SYNTHETIC QA pagamentoanterior',
|
|
date: '2026-02-16',
|
|
amount: '1',
|
|
},
|
|
];
|
|
const v = recordClosure(draft, pay);
|
|
assert.equal(v.result.rows[0].carryKey, first.rows[0].carryKey);
|
|
assert.equal(v.result.rows[1].status, 'determined');
|
|
});
|
|
void test('overpayment remains a payment fact and does not create credit20/25/26', () => {
|
|
const { draft, book } = closureFixture();
|
|
book.rows[2].payments[0].amount = '100';
|
|
const r = recordClosure(draft, book).result.rows[2];
|
|
assert.deepEqual(r.overpayment, { numerator: '28', denominator: '1' });
|
|
assert.equal(amount(r, '20'), '20/1');
|
|
assert.equal(amount(r, '25'), '0/1');
|
|
assert.equal(amount(r, '26'), '0/1');
|
|
});
|
|
void test('carry between periods preserves exact fractions instead of adding rounded presentation', () => {
|
|
const { draft, book } = closureFixture();
|
|
let a = acquisitionBook(draft);
|
|
a.rows[0].net = '3125';
|
|
a.rows[0].vat = '500';
|
|
a.rows[0].gross = '3625';
|
|
a.rows[0].d.denominator = '3';
|
|
a = recordAcquisitions(draft, a);
|
|
draft.modelo_a_snapshot = saveAcquisitionBook(draft, a);
|
|
book.rows[0].source = closureSources(draft).find(
|
|
(s) => s.revision === a.result.revision && s.period === '2026-01',
|
|
);
|
|
let b = recordClosure(draft, book);
|
|
assert.deepEqual(b.result.rows[0].fields['26'], {
|
|
numerator: '170',
|
|
denominator: '3',
|
|
});
|
|
b.rows[1].credits[0] = adoptPreviousCredit(
|
|
b.rows[1].credits[0],
|
|
b.result.rows[0],
|
|
);
|
|
b = recordClosure(draft, b);
|
|
assert.deepEqual(b.result.rows[1].fields['22'], {
|
|
numerator: '70',
|
|
denominator: '3',
|
|
});
|
|
});
|
|
void test('recovered opportunities keep scenarios separate and expose collisions of effective scope', () => {
|
|
const { draft, book } = closureFixture();
|
|
const other = structuredClone(book);
|
|
const store = JSON.parse(draft.modelo_a_snapshot);
|
|
store.ivaClosures = { 'opportunity:other': other };
|
|
draft.modelo_a_snapshot = JSON.stringify(store);
|
|
assert.equal(
|
|
recordClosure(draft, book).result.rows[0].status,
|
|
'determined',
|
|
'projected scenarios do not consume each other',
|
|
);
|
|
book.rows[0].origin = 'effective';
|
|
other.rows[0].origin = 'effective';
|
|
store.ivaClosures['opportunity:other'] = other;
|
|
draft.modelo_a_snapshot = JSON.stringify(store);
|
|
assert.match(
|
|
recordClosure(draft, book).result.rows[0].reason,
|
|
/noutra oportunidade/,
|
|
);
|
|
});
|
|
void test('frozen L2 sources are reused exactly and incomplete or fabricated sources cannot close balances', () => {
|
|
const { draft, book } = closureFixture();
|
|
const b = structuredClone(book);
|
|
b.rows[0].source.credit = { numerator: '999', denominator: '1' };
|
|
assert.equal(
|
|
recordClosure(draft, b).result.rows[0].status,
|
|
'indeterminate',
|
|
);
|
|
const removed = { ...draft };
|
|
const s = JSON.parse(draft.modelo_a_snapshot);
|
|
delete s.books['opportunity:373'].result;
|
|
removed.modelo_a_snapshot = JSON.stringify(s);
|
|
assert.equal(
|
|
recordClosure(removed, book).result.rows[0].fields['18'],
|
|
null,
|
|
);
|
|
assert.deepEqual(
|
|
acquisitionBook(draft).result.periods.map((p) => [
|
|
p.fields['17'],
|
|
p.fields['18'],
|
|
]),
|
|
[
|
|
['0.00', '100.00'],
|
|
['80.00', '0.00'],
|
|
['160.00', '0.00'],
|
|
],
|
|
);
|
|
const unchanged = recordAcquisitions(draft, acquisitionBook(draft));
|
|
const extraVersion = {
|
|
...draft,
|
|
modelo_a_snapshot: saveAcquisitionBook(draft, unchanged),
|
|
};
|
|
assert.ok(
|
|
closureCurrent(extraVersion, book),
|
|
'adding an unrelated L2 version does not rewrite or stale the selected frozen results',
|
|
);
|
|
});
|
|
void test('save recovery and report preserve every prior L1/L2 map,values and closure histories', () => {
|
|
let { draft, book } = closureFixture();
|
|
const before = JSON.parse(preserved.modelo_a_snapshot);
|
|
const acquired = structuredClone(acquisitionBook(draft));
|
|
draft.modelo_a_snapshot = saveClosureBook(draft, book);
|
|
const recovered = JSON.parse(JSON.stringify(draft));
|
|
assert.deepEqual(closureBook(recovered), book);
|
|
assert.ok(closureCurrent(recovered, book));
|
|
assert.ok(acquisitionCurrent(recovered, acquisitionBook(recovered)));
|
|
assert.deepEqual(acquisitionBook(recovered), acquired);
|
|
const after = JSON.parse(recovered.modelo_a_snapshot);
|
|
for (const [k, map] of Object.entries(before)) {
|
|
if (k === 'version') continue;
|
|
for (const [id, v] of Object.entries(map))
|
|
assert.deepEqual(after[k][id], v, k + id);
|
|
}
|
|
const report = JSON.stringify(closureReportRows(recovered));
|
|
for (const token of [
|
|
'2026-01',
|
|
'2026-02',
|
|
'2026-03',
|
|
'150/1',
|
|
'70/1',
|
|
'72/1',
|
|
'30/1',
|
|
'42/1',
|
|
'saldoabertura',
|
|
'comunicaçãoAT20',
|
|
'reciboP1',
|
|
'opportunity:373',
|
|
])
|
|
assert.ok(report.includes(token), token);
|
|
const malformed = JSON.parse(recovered.modelo_a_snapshot);
|
|
malformed.ivaClosures = null;
|
|
assert.throws(
|
|
() =>
|
|
closureBook({
|
|
...recovered,
|
|
modelo_a_snapshot: JSON.stringify(malformed),
|
|
}),
|
|
/não recuperável/,
|
|
);
|
|
book.rows[0].credits[0].amount = '999';
|
|
const edited = {
|
|
...recovered,
|
|
modelo_a_snapshot: saveClosureBook(recovered, book),
|
|
};
|
|
assert.equal(closureCurrent(edited, book), false);
|
|
assert.match(JSON.stringify(closureReportRows(edited)), /50/);
|
|
assert.equal(book.result.inputs[0].credits[0].amount, '50');
|
|
if (process.env.IVA_L3AB_CALCULATED)
|
|
fs.writeFileSync(
|
|
process.env.IVA_L3AB_CALCULATED,
|
|
JSON.stringify(recovered),
|
|
);
|
|
});
|