54 lines
7.3 KiB
JavaScript
54 lines
7.3 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];
|
|
|
|
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/);
|
|
}
|
|
});
|
|
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);
|
|
});
|
|
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);
|
|
});
|
|
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');}
|
|
});
|
|
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);}
|
|
});
|
|
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));
|
|
});
|
|
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]);
|
|
});
|
|
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:[]}));
|
|
});
|
|
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/);
|
|
});
|