64 lines
11 KiB
JavaScript
64 lines
11 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} from '../../resources/js/lib/irpc-withholdings.ts';
|
|
import {newMrftaInput,recordMrfta,mrftaCurrent,mrftaHtml,prepareMrfta} from '../../resources/js/lib/irpc-mrfta.ts';
|
|
import {newIrpcInterestRule,calculateIrpcInterest} from '../../resources/js/lib/irpc-interest.ts';
|
|
import {mrftaCases,mrftaCaseHtml} from '../../resources/js/lib/irpc-mrfta-cases.ts';
|
|
import {createProjectDossierReport} 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 rule=()=>({...newIrpcInterestRule(),annualRate:'10',rateSource:'QA parâmetro fictício; não é taxa fiscal2026',fiscalBasis:'QA fundamento sintético para ensaio aritmético',validFrom:'2026-01-01',validTo:'2026-12-31',dayBasis:'365',dayBasisRef:'QA fonte sintética da base365',rounding:'total_cent_half_up',roundingRef:'QA fonte sintética do arredondamento do total',retention:'withheld',occurrenceRef:'QA retenção e atraso documentados'});
|
|
const row=(id='one')=>({...newIrpcRow(id),counterparty:'QA contraparte',documentRef:'QA-'+id,base:'36500',kind:'royalty',recipientIrpc:'yes',payerIrpc:'yes',residence:'nonresident',permanentEstablishment:'no',mozambique:'yes',qualificationRef:'QA incidência e residência',specialRelief:'none',eventsComplete:'yes',events:[{kind:'cost',date:'2026-10-02',reference:'QA custo'}],settlement:'none',settlementRef:'QA sem entregas',ordinary:{...emptyIrpcOrdinaryFacts(),nonDigital:'yes',reference:'QA royalties não digitais'}});
|
|
const book=(payments=[])=>{const r=row();if(payments.length){r.settlement='complete';r.allocations=payments.map(p=>({paymentId:p.id,amount:p.amount}));}return recordIrpc(draft(),{version:1,rows:[r],payments});};
|
|
const payment=(amount,date='2026-11-25',id='p')=>({id,kind:'remittance',reference:'QA entrega-'+id,date,amount,currency:'MZN'});
|
|
const calc=(b=book(),r=rule(),due='2026-11-20',end='2026-11-30')=>calculateIrpcInterest(b,true,'2026-10',due,'QA prazo documentado',end,r);
|
|
const input=()=>({...newMrftaInput(),period:'2026-10',declaration:'initial',timing:'late',nuit:'999999999',name:'QA empresa',taxArea:'QA área fiscal',address:'QA domicílio',completeness:'yes',completenessRef:'QA reconciliação do período',otherRubrics:'none',otherRubricsRef:'QA outras rubricas não devidas',codes:[{rowId:'one',cer:'999',reference:'QA CER fictício, não oficial'}],interest:{...newMrftaInput().interest,mode:'',calculation:'automatic',period:'2026-10',date:'2026-11-30',dueDate:'2026-11-20',dueReference:'QA prazo documentado',reference:'QA ocorrência',automatic:rule()}});
|
|
|
|
test('daily interest uses documented parameters and explains principal,days and exact arithmetic',()=>{
|
|
const v=calc();assert.deepEqual(v.pending,[]);assert.equal(v.total.numerator,'20');assert.equal(v.rows[0].segments[0].from,'2026-11-21');assert.equal(v.rows[0].segments[0].to,'2026-11-30');assert.equal(v.rows[0].segments[0].days,10);assert.equal(v.rows[0].segments[0].principal.numerator,'7300');
|
|
});
|
|
test('partial remittance reduces principal after its payment day without duplicate daily interest',()=>{
|
|
const v=calc(book([payment('3650')]));assert.equal(v.total.numerator,'15');assert.deepEqual(v.rows[0].segments.map(s=>[s.days,s.principal.numerator]),[[5,'7300'],[5,'3650']]);
|
|
const sameDay=calc(book([payment('1825','2026-11-25','p1'),payment('1825','2026-11-25','p2')]));assert.equal(sameDay.total.numerator,'15');assert.equal(sameDay.rows[0].segments.length,2);
|
|
});
|
|
test('full payment stops charges and pre-deadline payment reduces the opening debt',()=>{
|
|
assert.equal(calc(book([payment('7300')])).total.numerator,'10');assert.equal(calc(book([payment('3650','2026-11-18')])).total.numerator,'10');
|
|
});
|
|
test('missing rate,reference,day basis or applicable version never becomes a default tax rate',()=>{
|
|
for(const patch of [{annualRate:''},{annualRate:'0'},{annualRate:'-1'},{rateSource:''},{fiscalBasis:''},{dayBasis:''},{dayBasisRef:''},{rounding:''},{roundingRef:''},{validTo:'2017-12-31'},{occurrenceRef:''}]){const v=calc(book(),{...rule(),...patch});assert.equal(v.total,null);assert.ok(v.pending.length);}
|
|
assert.equal(calculateIrpcInterest(book(),false,'2026-10','2026-11-20','QA prazo','2026-11-30',rule()).total,null);
|
|
});
|
|
test('on-account not-withheld cases remain separate;definitive not-withheld uses its own supported branch',()=>{
|
|
const r=row();r.residence='resident';const b=recordIrpc(draft(),{version:1,rows:[r],payments:[]});assert.equal(calc(b,{...rule(),retention:'not_withheld'}).total,null);assert.match(calc(b,{...rule(),retention:'not_withheld'}).pending.join(' '),/declaração anual/);assert.equal(calc(b).total.numerator,'20');assert.equal(calc(book(),{...rule(),retention:'not_withheld'}).total.numerator,'20');
|
|
});
|
|
test('documented actual-year basis splits year boundary including leap year without floating money',()=>{
|
|
const r={...rule(),validTo:'2028-12-31',dayBasis:'actual_year'},v=calc(book(),r,'2027-12-30','2028-01-02');assert.deepEqual(v.rows[0].segments.map(s=>[s.days,s.denominator]),[[1,365],[2,366]]);assert.deepEqual(v.total,{numerator:'599',denominator:'100'});
|
|
});
|
|
test('rounding is explicit,on total only,and matches the documented convention',()=>{
|
|
assert.deepEqual(calc(book(),{...rule(),annualRate:'10.0025'}).total,{numerator:'2001',denominator:'100'});assert.equal(calc(book(),{...rule(),annualRate:'10.0025',rounding:'total_cent_down'}).total.numerator,'20');
|
|
});
|
|
test('unreconciled payments and payments after liquidation keep the affected calculation indeterminate',()=>{
|
|
assert.equal(calc(book([payment('100','2026-12-01')])).total,null);const b=book();b.result.rows[0].settled=null;assert.equal(calc(b).total,null);
|
|
});
|
|
test('distinct rates and CER produce stable working cases,not invented declarations or merged rates',()=>{
|
|
const b={...book(),mrfta:input()};const second={...b.result.rows[0],id:'two',rate:10};b.result.rows.push(second);b.result.inputs.push(row('two'));b.mrfta.codes.push({rowId:'two',cer:'998',reference:'QA second fictionalCER'});const m=prepareMrfta(b,true),cases=mrftaCases(m);assert.equal(cases.length,2);assert.notEqual(cases[0].key,cases[1].key);assert.deepEqual(cases.map(c=>[c.rate,c.cer]),[[20,'999'],[10,'998']]);assert.equal(m.total,null);const html=mrftaCaseHtml(m,cases[0].key);assert.match(html,/Não é uma declaração autónoma/);assert.ok(html.includes('Pendências do período'));assert.deepEqual(mrftaCases({...m,lines:[...m.lines].reverse()}).map(c=>c.key).sort(),cases.map(c=>c.key).sort());
|
|
});
|
|
test('missing CER references remain visible within each case sheet without presumed correspondence',()=>{
|
|
const b={...book(),mrfta:input()};b.mrfta.codes[0].reference='';const m=prepareMrfta(b,true);assert.match(mrftaCases(m)[0].pending,/referência por corresponder/);b.mrfta.codes=[];assert.match(mrftaCases(prepareMrfta(b,true))[0].pending,/sem preenchimento presumido/);
|
|
});
|
|
test('automatic result persists,recovers and reaches dossier with traceable parameters and history',()=>{
|
|
const d=draft(),b=recordMrfta({...book([payment('3650')]),mrfta:input()},true);d.modelo_a_snapshot=saveIrpcBook(d,b);const recovered=JSON.parse(JSON.stringify(d)),saved=irpcBook(recovered);assert.equal(irpcCurrent(recovered,saved),true);assert.equal(mrftaCurrent(saved,true),true);assert.equal(saved.mrftaResult.model.interest.numerator,'15');assert.equal(saved.mrftaResult.model.total.numerator,'3665');assert.equal(saved.mrftaResult.model.automatic.rows[0].segments.length,2);
|
|
const report=JSON.stringify(irpcReportRows(recovered));assert.match(report,/Taxa anual:10%/);assert.match(report,/QA parâmetro fictício/);assert.match(report,/5dias\/365/);assert.match(report,/metade por excesso/);assert.ok(!report.includes('total_cent_half_up'));assert.match(JSON.stringify(createProjectDossierReport(recovered,{selectedSections:new Set(['finance'])})),/3665/);assert.match(mrftaHtml(saved,true),/Juros calculados por parâmetros documentados/);assert.match(mrftaHtml(saved,true),/simulação pelos pressupostos introduzidos, sem confirmação fiscal/);assert.match(mrftaHtml(saved,true),/Códigos provisórios ou de simulação não são correspondências oficiais/);assert.match(report,/sem validação por catálogo oficial/);assert.match(mrftaCases(saved.mrftaResult.model)[0].pending,/Código provisório ou de simulação não é correspondência oficial/);
|
|
saved.mrfta.interest.automatic.annualRate='';assert.equal(mrftaCurrent(saved,true),false);const changed=recordMrfta(saved,true);assert.equal(changed.mrftaResult.model.interest,null);assert.deepEqual(changed.mrftaHistory.at(-1),b.mrftaResult);
|
|
fs.mkdirSync('outputs/irpc-interest-cer',{recursive:true});fs.writeFileSync('outputs/irpc-interest-cer/calculated.json',JSON.stringify(recovered));fs.writeFileSync('outputs/irpc-interest-cer/preparation.html',mrftaHtml(b,true));
|
|
});
|
|
test('new parameters reject malformed persisted values while the installed snapshot remains current',()=>{
|
|
const d=JSON.parse(fs.readFileSync(new URL('./fixtures/irpc-mrfta-preserved.json',import.meta.url),'utf8'));const b=irpcBook(d);assert.equal(irpcCurrent(d,b),true);assert.equal(mrftaCurrent(b,true),true);const invalid={...book(),mrfta:input()};invalid.mrfta.interest.automatic.annualRate=10;assert.throws(()=>saveIrpcBook(draft(),invalid));
|
|
});
|
|
test('two calculated incomes with different treaty/internal rates remain separate without an invented declaration total',()=>{
|
|
const d=draft(),first={...row(),settlement:'complete',allocations:[{paymentId:'p',amount:'3650'}]},second={...row('two'),specialRelief:'claimed',reliefPreparation:{kind:'treaty',rule:'ptMz12',instrument:'QA CDT PT-MZ e Protocolo',article:'12',country:'PT',residenceDocument:'QA certificado Portugal2026',validFrom:'2026-01-01',validTo:'2026-12-31',conditions:'QA royalties qualificadas semEP',evidence:'QA contrato e residência',facts:{treatyResident:'yes',beneficialOwner:'yes',payerResidentMz:'yes',armLength:'yes',royaltyDefinition:'yes'}}};
|
|
const b=recordMrfta({...recordIrpc(d,{version:1,rows:[first,second],payments:[payment('3650')]}),mrfta:{...input(),codes:[...input().codes,{rowId:'two',cer:'998',reference:'QA CER998 fictício, não oficial'}]}},true);const m=b.mrftaResult.model;
|
|
assert.deepEqual(b.result.rows.map(r=>r.rate),[20,10]);assert.equal(m.interest.numerator,'25');assert.equal(m.total,null);assert.deepEqual(mrftaCases(m).map(c=>[c.rate,c.cer]),[[20,'999'],[10,'998']]);assert.match(m.pending.join(' '),/taxas ou CER distintos/);
|
|
d.modelo_a_snapshot=saveIrpcBook(d,b);fs.writeFileSync('outputs/irpc-interest-cer/mixed.json',JSON.stringify(d));
|
|
});
|