209 lines
7.6 KiB
JavaScript
209 lines
7.6 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
normalBook,
|
|
normalCurrent,
|
|
calculateNormal,
|
|
recordNormal,
|
|
saveNormalBook,
|
|
normalHtml,
|
|
normalReportRows,
|
|
} from '../../resources/js/lib/irpc-normal.ts';
|
|
import {
|
|
newNormalAssessmentInput,
|
|
calculateNormalAssessment,
|
|
} from '../../resources/js/lib/irpc-normal-assessment.ts';
|
|
import { fraction } from '../../resources/js/lib/fiscal-rational.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
const seed = () =>
|
|
JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/irpc-base-preserved.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
const input = () => ({
|
|
...newNormalAssessmentInput(),
|
|
treatment: 'general_61_1',
|
|
reference:
|
|
'QA sujeito residente,actividade ordinária,sem taxa reduzida/benefício ou tributação autónoma neste recorte',
|
|
});
|
|
const context = () => ({
|
|
year: '2026',
|
|
scope: 'resident_general',
|
|
currency: 'MZN',
|
|
});
|
|
void test('current base4400 times statutory general32percent yields1408 colecta with explicit2026 period and L8 unknown balance', () => {
|
|
const d = seed(),
|
|
b = normalBook(d);
|
|
b.assessment = input();
|
|
const m = calculateNormal(d, b);
|
|
assert.equal(m.taxableProfit.numerator, '6700');
|
|
assert.equal(m.taxableBase.numerator, '4400');
|
|
assert.equal(m.colecta.numerator, '1408');
|
|
assert.equal(m.tax, null);
|
|
assert.equal(m.assessment.rate.numerator, '32');
|
|
assert.deepEqual(m.assessment.period, {
|
|
year: '2026',
|
|
from: '2026-01-01',
|
|
to: '2026-12-31',
|
|
});
|
|
assert.match(m.assessment.classification, /artigo61\(1\)/);
|
|
assert.equal(m.assessment.finalBalance.payable, null);
|
|
assert.equal(m.assessment.finalBalance.recoverable, null);
|
|
assert.equal(m.assessment.finalBalance.scope, 'L8');
|
|
});
|
|
void test('missing classification and special cases do not silently inherit32percent;known upstream base remains', () => {
|
|
for (const patch of [
|
|
{ treatment: '' },
|
|
{ treatment: 'other' },
|
|
{ reference: '' },
|
|
]) {
|
|
const m = calculateNormalAssessment(
|
|
{ ...input(), ...patch },
|
|
fraction(4400n),
|
|
context(),
|
|
);
|
|
assert.equal(m.colecta, null);
|
|
assert.equal(m.taxableBase.numerator, '4400');
|
|
assert.ok(m.pending.length > 0);
|
|
if (patch.treatment === 'other') assert.equal(m.rate, null);
|
|
}
|
|
});
|
|
void test('rate is scoped by period,residence regime and currency,not carried across years or contexts', () => {
|
|
for (const patch of [
|
|
{ year: '2025' },
|
|
{ year: '2027' },
|
|
{ scope: 'other' },
|
|
{ currency: 'USD' },
|
|
]) {
|
|
const m = calculateNormalAssessment(input(), fraction(4400n), {
|
|
...context(),
|
|
...patch,
|
|
});
|
|
assert.equal(m.colecta, null);
|
|
if (patch.year) assert.equal(m.rate, null);
|
|
}
|
|
});
|
|
void test('unknown base is not replaced with6700profit;known zero stays zero and fractional cents remain exact', () => {
|
|
assert.equal(
|
|
calculateNormalAssessment(input(), null, context()).colecta,
|
|
null,
|
|
);
|
|
assert.equal(
|
|
calculateNormalAssessment(input(), fraction(0n), context()).colecta
|
|
.numerator,
|
|
'0',
|
|
);
|
|
const m = calculateNormalAssessment(input(), fraction(1n, 100n), context());
|
|
assert.deepEqual(m.colecta, { numerator: '2', denominator: '625' });
|
|
assert.equal(m.centExact, false);
|
|
assert.equal(
|
|
calculateNormalAssessment(input(), fraction(-1n), context()).colecta,
|
|
null,
|
|
);
|
|
});
|
|
void test('an incomplete base calculation only blocks colecta;profit and valid source amounts remain visible', () => {
|
|
const d = seed(),
|
|
b = normalBook(d);
|
|
b.assessment = input();
|
|
b.base.benefits = { state: '', amount: '', reference: '' };
|
|
const m = calculateNormal(d, b);
|
|
assert.equal(m.taxableProfit.numerator, '6700');
|
|
assert.equal(m.taxableBase, null);
|
|
assert.equal(m.colecta, null);
|
|
assert.equal(m.assessment.rate.numerator, '32');
|
|
});
|
|
void test('retentions,payments and annual credits are not subtracted or resolved by this L6 scope', () => {
|
|
const d = seed(),
|
|
b = normalBook(d);
|
|
b.assessment = input();
|
|
const m = calculateNormal(d, b);
|
|
d.annual_withholdings = '999999';
|
|
d.annual_payments = '999999';
|
|
d.annual_credits = '999999';
|
|
const s = JSON.parse(d.modelo_a_snapshot);
|
|
for (const w of Object.values(s.irpcWithholdings ?? {})) {
|
|
w.payments = [];
|
|
if (w.mrfta) w.mrfta.codes = [];
|
|
}
|
|
d.modelo_a_snapshot = JSON.stringify(s);
|
|
const changed = calculateNormal(d, b);
|
|
assert.deepEqual(changed.colecta, m.colecta);
|
|
assert.deepEqual(
|
|
changed.assessment.finalBalance,
|
|
m.assessment.finalBalance,
|
|
);
|
|
assert.equal(changed.tax, null);
|
|
});
|
|
void test('legacy3 remains current without recalculation;version4 roundtrip/history/report/dossier preserve prior books and stale guards', () => {
|
|
const d = seed(),
|
|
old = normalBook(d);
|
|
assert.equal(normalCurrent(d, old), true);
|
|
assert.equal(old.result.model.version, 'IRPC-normal-adjustments/3');
|
|
const originalStore = JSON.parse(d.modelo_a_snapshot);
|
|
const b = recordNormal(
|
|
d,
|
|
{ ...old, assessment: input() },
|
|
'2026-10-25T23:30:00Z',
|
|
);
|
|
assert.deepEqual(b.history.at(-1), old.result);
|
|
assert.equal(b.result.model.version, 'IRPC-normal-adjustments/4');
|
|
d.modelo_a_snapshot = saveNormalBook(d, b);
|
|
assert.deepEqual(
|
|
JSON.parse(d.modelo_a_snapshot).irpcWithholdings,
|
|
originalStore.irpcWithholdings,
|
|
);
|
|
const back = JSON.parse(JSON.stringify(d));
|
|
assert.equal(normalCurrent(back, normalBook(back)), true);
|
|
const html = normalHtml(back);
|
|
assert.match(html, /colecta simulada:1408/);
|
|
assert.match(html, /saldo final a pagar ou recuperar:Indeterminado \(L8\)/);
|
|
assert.match(html, /taxa:32%/);
|
|
assert.match(
|
|
JSON.stringify(
|
|
createProjectDossierReport(back, {
|
|
selectedSections: new Set(['finance']),
|
|
}),
|
|
),
|
|
/1408/,
|
|
);
|
|
assert.match(
|
|
JSON.stringify(normalReportRows(back)),
|
|
/Saldo final — âmbito L8/,
|
|
);
|
|
const changed = normalBook(back);
|
|
changed.assessment.reference = '';
|
|
back.modelo_a_snapshot = saveNormalBook(back, changed);
|
|
assert.equal(normalCurrent(back, normalBook(back)), false);
|
|
assert.throws(() => normalHtml(back));
|
|
const again = recordNormal(back, normalBook(back));
|
|
assert.equal(again.result.model.colecta, null);
|
|
assert.deepEqual(again.history.at(-1), b.result);
|
|
fs.mkdirSync('outputs/irpc-l6-colecta', { recursive: true });
|
|
fs.writeFileSync(
|
|
'outputs/irpc-l6-colecta/calculated.json',
|
|
JSON.stringify(d),
|
|
);
|
|
fs.writeFileSync('outputs/irpc-l6-colecta/preparation.html', normalHtml(d));
|
|
const md = structuredClone(d),
|
|
missing = normalBook(md);
|
|
missing.assessment.treatment = '';
|
|
md.modelo_a_snapshot = saveNormalBook(md, recordNormal(md, missing));
|
|
fs.writeFileSync(
|
|
'outputs/irpc-l6-colecta/missing.json',
|
|
JSON.stringify(md),
|
|
);
|
|
});
|
|
void test('unsupported input versions and malformed classification fail without replacing originals', () => {
|
|
const d = seed(),
|
|
b = normalBook(d);
|
|
b.assessment = input();
|
|
b.assessment.version = 2;
|
|
assert.throws(() => saveNormalBook(d, b));
|
|
b.assessment.version = 1;
|
|
b.assessment.treatment = 'general20';
|
|
assert.throws(() => saveNormalBook(d, b));
|
|
});
|