Files
Investidor-V2/tests/frontend/irpc-reliefs.test.mjs
T

269 lines
9.5 KiB
JavaScript

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/);
});