import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { normalBook, normalCurrent, calculateNormal, recordNormal, saveNormalBook, normalHtml, } from '../../resources/js/lib/irpc-normal.ts'; import { newNormalBaseInput, newNormalLoss, calculateNormalBase, } from '../../resources/js/lib/irpc-normal-base.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-profit-preserved.json', import.meta.url), 'utf8', ), ); const loss = (id, year, assessed, used, requested) => ({ ...newNormalLoss(id), year, subjectNuit: '123456789', assessed, previouslyUsed: used, requested, reference: 'QA saldo fiscal corrigido,sujeito/exercício ' + year, kind: 'ordinary', annualPeriods: 'yes', generalActivity: 'yes', balanceConfirmed: 'yes', }); const input = () => ({ ...newNormalBaseInput(), subjectNuit: '123456789', identityReference: 'QA sujeito do lucro e dos saldos', method: 'direct', methodReference: 'QA declaração e contabilidade organizada', lossesState: 'known', lossesReference: 'QA inventário completo dos saldos fiscais do sujeito', unchangedObject: 'yes', objectReference: 'QA actividade e objecto mantidos ao termo2026', losses: [ loss('old', '2021', '1000', '200', '800'), loss('new', '2024', '3000', '500', '1500'), ], benefits: { state: 'known', amount: '0', reference: 'QA deduções/benefícios à base,valor conhecido zero', }, }); void test('6700 is reused as profit;2300 documented loss use yields4400 taxable base,never tax payable', () => { const d = seed(), b = normalBook(d); b.base = input(); const m = calculateNormal(d, b); assert.equal(m.taxableProfit.numerator, '6700'); assert.equal(m.base.requestedLosses.numerator, '2300'); assert.equal(m.base.deductedLosses.numerator, '2300'); assert.equal(m.taxableBase.numerator, '4400'); assert.equal(m.tax, null); assert.deepEqual( m.base.losses.map((l) => l.available.numerator), ['800', '2500'], ); assert.deepEqual( m.base.losses.map((l) => l.remaining.numerator), ['0', '1000'], ); assert.deepEqual( m.base.losses.map((l) => l.lastOrdinaryYear), [2026, 2029], ); }); void test('supported absence of losses and benefits retains6700 base;missing benefit information does not mean zero', () => { const i = { ...input(), lossesState: 'absent', losses: [], unchangedObject: '', objectReference: '', benefits: { state: 'absent', amount: '', reference: 'QA sem benefício aplicável', }, }; let m = calculateNormalBase(i, fraction(6700n), '2026'); assert.equal(m.taxableBase.numerator, '6700'); i.benefits = { state: '', amount: '', reference: '' }; m = calculateNormalBase(i, fraction(6700n), '2026'); assert.equal(m.taxableBase, null); assert.equal(m.taxableProfit.numerator, '6700'); }); void test('expired ordinary2020 loss cannot be deducted;2021 remains within last ordinary year2026', () => { const i = input(); i.losses = [loss('expired', '2020', '500', '0', '0')]; i.unchangedObject = ''; i.objectReference = ''; let m = calculateNormalBase(i, fraction(6700n), '2026'); assert.equal(m.losses[0].status, 'excluded'); assert.equal(m.losses[0].lastOrdinaryYear, 2025); assert.equal(m.taxableBase.numerator, '6700'); i.losses[0].requested = '1'; assert.equal( calculateNormalBase(i, fraction(6700n), '2026').taxableBase, null, ); }); void test('losses cannot exceed available balance or profit;no implicit allocation order or percentage cap', () => { const i = input(); i.losses[0].requested = '801'; assert.equal( calculateNormalBase(i, fraction(6700n), '2026').taxableBase, null, ); i.losses = [ loss('a', '2021', '5000', '0', '5000'), loss('b', '2024', '5000', '0', '4000'), ]; let m = calculateNormalBase(i, fraction(6700n), '2026'); assert.equal(m.taxableBase, null); assert.match(m.pending.join(' '), /reparta explicitamente/); i.losses[0].requested = '2700'; m = calculateNormalBase(i, fraction(6700n), '2026'); assert.equal(m.taxableBase.numerator, '0'); assert.equal(m.deductedLosses.numerator, '6700'); }); void test('same taxpayer,annual periods,ordinary origin,reconciled balance and activity facts are specific dependencies', () => { for (const patch of [ { subjectNuit: '987654321' }, { annualPeriods: 'no' }, { generalActivity: 'no' }, { balanceConfirmed: '' }, { kind: 'transfer' }, { year: '2026' }, { previouslyUsed: '1100' }, { requested: '' }, ]) { const i = input(); Object.assign(i.losses[0], patch); assert.equal( calculateNormalBase(i, fraction(6700n), '2026').taxableBase, null, ); } }); void test('changed object explicitly excludes deduction;duplicate annual balances and indirect methods remain pending', () => { const i = input(); i.unchangedObject = 'no'; i.losses.forEach((l) => (l.requested = '0')); let m = calculateNormalBase(i, fraction(6700n), '2026'); assert.equal(m.taxableBase.numerator, '6700'); assert.ok(m.losses.every((l) => l.status === 'excluded')); i.unchangedObject = 'yes'; i.losses[1].year = '2021'; assert.equal( calculateNormalBase(i, fraction(6700n), '2026').taxableBase, null, ); i.losses[1].year = '2024'; i.method = 'indirect'; assert.equal( calculateNormalBase(i, fraction(6700n), '2026').taxableBase, null, ); }); void test('positive known benefits are preserved without free subtraction;absence contradictions remain explicit', () => { const i = input(); i.benefits = { state: 'known', amount: '200', reference: 'QA benefício positivo,regra por corresponder', }; let m = calculateNormalBase(i, fraction(6700n), '2026'); assert.equal(m.input.benefits.amount, '200'); assert.equal(m.taxableBase, null); i.benefits = { state: 'absent', amount: '200', reference: 'QA ausência contraditória', }; assert.equal( calculateNormalBase(i, fraction(6700n), '2026').taxableBase, null, ); i.benefits = { state: 'known', amount: '0', reference: 'QA zero conhecido', }; i.lossesState = 'absent'; assert.equal( calculateNormalBase(i, fraction(6700n), '2026').taxableBase, null, ); }); void test('unknown or negative current profit is preserved,not transformed to zero or current-year loss carryforward', () => { for (const p of [null, fraction(-100n)]) { const m = calculateNormalBase(input(), p, '2026'); assert.equal(m.taxableBase, null); assert.deepEqual(m.taxableProfit, p); } }); void test('legacy current result and all books survive;new3 history,save/recovery/dossier and repeated simulation do not consume official balances', () => { const d = seed(), legacy = normalBook(d); assert.equal(normalCurrent(d, legacy), true); assert.equal(legacy.result.model.version, 'IRPC-normal-adjustments/2'); const b = { ...legacy, base: input() }, before = JSON.parse(d.modelo_a_snapshot), saved = recordNormal(d, b, '2026-10-25T23:00:00Z'); assert.deepEqual(saved.history.at(-1), legacy.result); assert.equal(saved.result.model.version, 'IRPC-normal-adjustments/3'); d.modelo_a_snapshot = saveNormalBook(d, saved); assert.deepEqual( JSON.parse(d.modelo_a_snapshot).irpcWithholdings, before.irpcWithholdings, ); const back = JSON.parse(JSON.stringify(d)); assert.equal(normalCurrent(back, normalBook(back)), true); assert.match(normalHtml(back), /matéria colectável simulada:4400/); assert.match(normalHtml(back), /imposto a pagar:Indeterminado/); assert.match( JSON.stringify( createProjectDossierReport(back, { selectedSections: new Set(['finance']), }), ), /4400/, ); const repeated = recordNormal(back, normalBook(back)); assert.equal(repeated.result.model.taxableBase.numerator, '4400'); assert.equal(repeated.base.losses[1].previouslyUsed, '500'); assert.equal(repeated.base.losses[1].requested, '1500'); const changed = normalBook(back); changed.base.losses[0].requested = '700'; back.modelo_a_snapshot = saveNormalBook(back, changed); assert.equal(normalCurrent(back, normalBook(back)), false); assert.throws(() => normalHtml(back)); fs.mkdirSync('outputs/irpc-l6-base', { recursive: true }); fs.writeFileSync('outputs/irpc-l6-base/calculated.json', JSON.stringify(d)); fs.writeFileSync('outputs/irpc-l6-base/preparation.html', normalHtml(d)); const missing = normalBook(d); missing.base.benefits = { state: '', amount: '', reference: '' }; const md = structuredClone(d); md.modelo_a_snapshot = saveNormalBook(md, recordNormal(md, missing)); fs.writeFileSync('outputs/irpc-l6-base/missing.json', JSON.stringify(md)); }); void test('malformed future input versions and duplicate IDs fail without resetting originals', () => { const d = seed(), b = normalBook(d); b.base = input(); b.base.version = 2; assert.throws(() => saveNormalBook(d, b)); b.base.version = 1; b.base.losses[1].id = b.base.losses[0].id; assert.throws(() => saveNormalBook(d, b)); });