import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { newIrpcRow, emptyIrpcOrdinaryFacts, recordIrpc, saveIrpcBook, irpcBook, irpcCurrent, irpcReportRows, irpcPreparationHtml, } from '../../resources/js/lib/irpc-withholdings.ts'; import { newIrpcReliefPreparation } from '../../resources/js/lib/irpc-relief-preparation.ts'; import { createProjectDossierReport, createProjectDossierPrintHtml, } from '../../resources/js/lib/project-dossier-report.ts'; const draft = () => JSON.parse( fs.readFileSync( new URL('./fixtures/irpc-grouped-preserved.json', import.meta.url), 'utf8', ), ); const row = (id = 'dispensa') => ({ ...newIrpcRow(id), counterparty: 'QA sociedade', documentRef: 'QA-' + id, base: '100000', kind: 'services', recipientIrpc: 'yes', payerIrpc: 'yes', residence: 'resident', mozambique: 'yes', qualificationRef: 'QA contrato e cadastro do titular', specialRelief: 'claimed', eventsComplete: 'yes', events: [{ kind: 'cost', date: '2026-10-02', reference: 'QA custo' }], ordinary: { ...emptyIrpcOrdinaryFacts(), nonDigital: 'yes', reference: 'QA contrato de serviços', }, reliefPreparation: { ...newIrpcReliefPreparation(), rule: 'cirpc68d', instrument: 'CIRPC Lei34/2007', article: '68(d)', validFrom: '2026-01-01', validTo: '2026-12-31', conditions: 'QA titular e rendimento sujeitos a IRPC', evidence: 'QA cadastro e contrato existentes', facts: { subjectToIrpc: 'yes' }, }, }); const treaty = (id = 'convention') => ({ ...row(id), kind: 'royalty', residence: 'nonresident', permanentEstablishment: 'no', reliefPreparation: { ...row().reliefPreparation, kind: 'treaty', rule: 'ptMz12', instrument: 'CDT PT-MZ RAR36/1992 e Protocolo2009', article: '12', country: 'PT', residenceDocument: 'QA certificado fiscal Portugal 2026', facts: { treatyResident: 'yes', beneficialOwner: 'yes', payerResidentMz: 'yes', armLength: 'yes', royaltyDefinition: 'yes', }, }, }); const calc = (r) => recordIrpc(draft(), { version: 1, rows: [r], payments: [] }).result.rows[0]; void test('68d dispenses resident and attributable EP services/equipment without exempting the income', () => { for (const kind of ['services', 'equipment']) for (const patch of [ {}, { residence: 'nonresident', permanentEstablishment: 'yes', attributable: 'yes', }, ]) { const v = calc({ ...row(), kind, ...patch }); assert.equal(v.status, 'calculated'); assert.equal(v.treatment, 'dispensed'); assert.equal(v.nature, 'on_account'); assert.equal(v.tax.numerator, '0'); assert.equal(v.outstanding.numerator, '0'); assert.match(v.reason, /não é isenção/); } }); void test('68f requires own-property management and absence of fiscal transparency', () => { const r = row(); r.kind = 'rent'; r.ordinary.rentOwnPropertyManagement = 'yes'; r.reliefPreparation.rule = 'cirpc68f'; r.reliefPreparation.article = '68(f)'; r.reliefPreparation.facts.notTransparent = 'yes'; assert.equal(calc(r).treatment, 'dispensed'); for (const value of ['', 'no']) { r.reliefPreparation.facts.notTransparent = value; assert.equal(calc(r).tax, null); } r.reliefPreparation.facts.notTransparent = 'yes'; r.ordinary.rentOwnPropertyManagement = 'no'; assert.equal(calc(r).tax, null); }); void test('definitive nonresident equipment is never dispensed under68d', () => { for (const patch of [ { permanentEstablishment: 'no' }, { permanentEstablishment: 'yes', attributable: 'no' }, { permanentEstablishment: '' }, ]) assert.equal( calc({ ...row(), kind: 'equipment', residence: 'nonresident', ...patch, }).tax, null, ); }); void test('Portugal royalties and qualified equipment use10% gross definitive limit', () => { for (const kind of ['royalty', 'equipment']) { const v = calc({ ...treaty(), kind }); assert.equal(v.status, 'calculated'); assert.equal(v.treatment, 'treaty'); assert.equal(v.rate, 10); assert.equal(v.tax.numerator, '10000'); assert.equal(v.nature, 'definitive'); } }); void test('missing,negative,expired or contradictory evidence remains indeterminate,never zero', () => { const samples = [ (r) => (r.reliefPreparation.evidence = ''), (r) => (r.reliefPreparation.residenceDocument = ''), (r) => (r.reliefPreparation.validTo = '2026-09-30'), (r) => (r.reliefPreparation.validFrom = '2026-02-30'), (r) => (r.reliefPreparation.article = '7'), (r) => (r.reliefPreparation.country = 'ZA'), (r) => (r.permanentEstablishment = 'yes'), (r) => (r.kind = 'digital_services'), ]; for (const fact of [ 'treatyResident', 'beneficialOwner', 'payerResidentMz', 'armLength', 'royaltyDefinition', ]) for (const value of ['', 'no']) samples.push((r) => (r.reliefPreparation.facts[fact] = value)); for (const mutate of samples) { const r = treaty(); mutate(r); const v = calc(r); assert.equal(v.status, 'indeterminate'); assert.equal(v.tax, null); assert.equal(v.rate, null); } }); void test('independent rows calculate beside insufficient information; save recover report export and history', () => { const d = draft(), prior = irpcBook(d), insufficient = treaty('missing'); insufficient.reliefPreparation.residenceDocument = ''; const rent = row('property-dispensation'); rent.kind = 'rent'; rent.ordinary.rentOwnPropertyManagement = 'yes'; rent.reliefPreparation.rule = 'cirpc68f'; rent.reliefPreparation.article = '68(f)'; rent.reliefPreparation.facts.notTransparent = 'yes'; const b = recordIrpc(d, { ...prior, rows: [...prior.rows, row(), rent, treaty(), insufficient], }); d.modelo_a_snapshot = saveIrpcBook(d, b); const recovered = JSON.parse(JSON.stringify(d)), book = irpcBook(recovered); assert.deepEqual(book, b); assert.equal(irpcCurrent(recovered, book), true); assert.deepEqual(book.history.at(-1), prior.result); assert.deepEqual( book.result.rows.slice(-3).map((r) => r.status), ['calculated', 'calculated', 'indeterminate'], ); const report = irpcReportRows(recovered).slice(-3); assert.match(report[0][2], /Dispensa de retenção/); assert.match(report[1][2], /Limite convencional/); assert.match(report[2][2], /Indeterminado/); assert.match(report[1][3], /QA certificado fiscal Portugal 2026/); assert.match(irpcPreparationHtml(recovered), /10000/); book.rows.at(-2).reliefPreparation.facts.beneficialOwner = ''; assert.equal(irpcCurrent(recovered, book), false); assert.equal(recordIrpc(recovered, book).result.rows.at(-2).tax, null); fs.mkdirSync('outputs/irpc-reliefs', { recursive: true }); fs.writeFileSync( 'outputs/irpc-reliefs/calculated.json', JSON.stringify(recovered), ); fs.writeFileSync( 'outputs/irpc-reliefs/preparation.html', irpcPreparationHtml(recovered), ); }); void test('dispensa with payment attached requires reconciliation and retains the original proof', () => { const r = row(); r.allocations = [{ paymentId: 'p', amount: '100' }]; const p = { id: 'p', kind: 'remittance', reference: 'QA prova', date: '2026-10-20', amount: '100', currency: 'MZN', }; const b = recordIrpc(draft(), { version: 1, rows: [r], payments: [p] }); assert.equal(b.result.rows[0].status, 'indeterminate'); assert.deepEqual(b.payments, [p]); }); void test('unsupported relief and malformed persisted conditions are not accepted as a benefit', () => { const r = row(); r.reliefPreparation.rule = ''; assert.equal(calc(r).tax, null); r.reliefPreparation.facts.subjectToIrpc = 'no'; assert.equal(calc(r).tax, null); r.reliefPreparation.facts.subjectToIrpc = 42; assert.throws(() => saveIrpcBook(draft(), { version: 1, rows: [r], payments: [] }), ); }); void test('financial dossier includes each IRPC result, evidence and insufficient information', async () => { const d = draft(), missing = treaty('missing'); missing.reliefPreparation.residenceDocument = ''; d.modelo_a_snapshot = saveIrpcBook( d, recordIrpc(d, { version: 1, rows: [row(), treaty(), missing], payments: [], }), ); const report = createProjectDossierReport(d, { selectedSections: new Set(['finance']), }), text = JSON.stringify(report); for (const label of [ 'Dispensa de retenção', 'Limite convencional', 'Indeterminado', 'QA certificado fiscal Portugal 2026', ]) assert.ok(text.includes(label), label); const html = await createProjectDossierPrintHtml(report); assert.match(html, /Dispensa de retenção/); assert.match(html, /prova de residência/); });