34 lines
15 KiB
JavaScript
34 lines
15 KiB
JavaScript
import {ispcEligibilityBook,saveIspcEligibilityBook}from '../../resources/js/lib/ispc-eligibility.ts';
|
|
import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
|
|
import {ispcResidualBook,saveIspcResidualBook,recordIspcResidual,ispcResidualCurrent,ispcResidualReportRows,ispcFormalPreparationHtml}from '../../resources/js/lib/ispc-residuals.ts';
|
|
import {ispcWithholdingBook,saveIspcWithholdingBook}from '../../resources/js/lib/ispc-withholding.ts';
|
|
import {createProjectDossierReport}from '../../resources/js/lib/project-dossier-report.ts';
|
|
const seed=JSON.parse(fs.readFileSync(new URL('./fixtures/ispc-quarter-preserved.json',import.meta.url),'utf8'));
|
|
function own(p={}){const d=structuredClone(seed),b=ispcResidualBook(d,'occasional');b.input={...b.input,personType:'singular',currency:'MZN',origin:'projected',operationDate:'2026-06-15',quarter:'2',noHabitual:'yes',territory:'yes',sporadic:'yes',activity:'agriculture',reference:'SYNTHETIC QA colheita não reiterada',amount:'100000',allQuarterOperations:'yes',priorYearTurnover:'0',priorYearRef:'SYNTHETIC QA2025zeroexpresso',organizedRequired:'no',option:'preparation',optionRef:'',optionDate:'',settlement:'self',annual2026:'100000',annualOrigin:'forecast',annualRef:'SYNTHETIC QA2026',priorInYear:'0',...p};return{d,b};}
|
|
function acq(p={},wpatch={}){const d=structuredClone(seed),w=ispcWithholdingBook(d);w.input={...w.input,applicable:'yes',quarter:'2',base:'200000',priorAnnualPurchases:'2400000',naturalOccasional:'yes',unregistered:'yes',related:'yes',activity:'agriculture',reference:'SYNTHETIC QA aquisição/ordem e acumulado',...wpatch};d.modelo_a_snapshot=saveIspcWithholdingBook(d,w);const b=ispcResidualBook(d,'acquisitions');b.input={...b.input,...p};return{d,b};}
|
|
const calc=({d,b})=>recordIspcResidual(d,b,'2026-10-25T15:40:00Z');const number=x=>x?Number(x.numerator)/Number(x.denominator):null;
|
|
test('positive ownoccasional agricultural isolated8h route;self3/4/5 andnoautomaticIRPS20',()=>{for(const[annual,n]of [['100000',3],['2000000',4],['3000000',5]]){const r=calc(own({annual2026:annual})).result;assert.equal(r.status,'calculated',r.missing.join(';'));assert.equal(number(r.amounts.tax),1000*n);assert.match(r.source,/8\(3h,4\)/);assert.equal(r.details.IRPS.includes('Não se infere'),true);}});
|
|
test('unknown notnonapplicable;known exclusions andactivityfamilyownscope',()=>{assert.equal(calc(own({personType:''})).result.status,'indeterminate');for(const p of [{personType:'collective'},{noHabitual:'no'},{sporadic:'no'},{territory:'no'},{activity:'other'}])assert.equal(calc(own(p)).result.status,'not_applicable');for(const p of [{territory:''},{currency:'USD'},{quarter:'3'},{reference:''},{priorYearTurnover:''},{organizedRequired:''}])assert.equal(calc(own(p)).result.status,'indeterminate');});
|
|
test('ownminimum500withoutlosingdeclaration;zeroexplicit and4mexcessnotreused',()=>{let r=calc(own({amount:'1000'})).result;assert.equal(number(r.amounts.tax),30);assert.equal(number(r.amounts.payment),0);assert.match(r.details.declaration,/Mantida/);r=calc(own({amount:'0'})).result;assert.equal(number(r.amounts.tax),0);assert.equal(calc(own({amount:''})).result.status,'indeterminate');r=calc(own({amount:'200000',priorInYear:'3900000',annual2026:'4100000'})).result;assert.equal(number(r.amounts.ordinary),100000);assert.equal(number(r.amounts.excess),100000);assert.equal(number(r.amounts.tax),25000);});
|
|
test('documentedliberatory5meansno secondselftax;smallwithholdingnotminimum500;divergenceunknown',()=>{const p={settlement:'withheld',origin:'effective',buyerIspc:'yes',unregistered:'yes',buyerAnnualPurchases:'200000',withheldActual:'5000',withheldRef:'SYNTHETIC QA recibo5'};let r=calc(own(p)).result;assert.equal(r.status,'settled_by_withholding',r.missing.join(';'));assert.equal(number(r.amounts.withheld),5000);assert.equal(r.amounts.payment,undefined);assert.match(r.reason,/não somar nova liquidação/);r=calc(own({...p,amount:'100',withheldActual:'5'})).result;assert.equal(r.status,'settled_by_withholding');assert.equal(number(r.amounts.tax),5);assert.equal(calc(own({...p,withheldActual:'4000'})).result.status,'indeterminate');assert.equal(calc(own({...p,buyerAnnualPurchases:'2500000.000001'})).result.status,'indeterminate');});
|
|
test('selfeffective requiresapplicableoption;forecastcannot masqueradeasactual;nonoptionnoISPC',()=>{assert.equal(calc(own({origin:'effective'})).result.status,'indeterminate');assert.equal(calc(own({option:'not_opted'})).result.status,'not_applicable');const r=calc(own({origin:'effective',operationDate:'2026-12-31',quarter:'4',option:'opted',optionRef:'QA opção',optionDate:'2026-01-01',annualOrigin:'closed_year'})).result;assert.equal(r.status,'calculated',r.missing.join(';'));assert.equal(calc(own({origin:'effective',option:'opted',optionRef:'QA',optionDate:'2026-07-01'})).result.status,'indeterminate');});
|
|
test('above2.5m splits100kcovered/100kexcess;5kknownbuttotalnullwithunknownroute',()=>{const r=calc(acq()).result;assert.equal(r.status,'partial');assert.equal(number(r.amounts.covered),100000);assert.equal(number(r.amounts.coveredTax),5000);assert.equal(number(r.amounts.excess),100000);assert.equal(r.amounts.excessTax,null);assert.equal(r.amounts.total,null);});
|
|
test('positiveIRPS57k20onidentifiedgrossact—separatefromISPCexcess20andownbuyerpayment',()=>{const p={route:'irps_isolated',noIspcOnExcess:'yes',isolatedAct:'yes',incomeInMoz:'yes',grossExcess:'100000',grossRef:'SYNTHETIC QA base ilíquida',reference:'SYNTHETIC QA8h/57k sem opção5'};const r=calc(acq(p)).result;assert.equal(r.status,'calculated',r.missing.join(';'));assert.equal(number(r.amounts.excessTax),20000);assert.equal(number(r.amounts.total),25000);assert.match(r.reason,/não são crédito/);for(const patch of [{grossExcess:''},{grossExcess:'80000'},{noIspcOnExcess:''},{isolatedAct:''},{incomeInMoz:'no'},{reference:''}]){const r=calc(acq({...p,...patch})).result;assert.equal(r.status,'partial');assert.equal(r.amounts.total,null);}});
|
|
test('supplierISPCnotautomaticallyIRPS;crossingexactboundaryandprioroverlimit;noimplicit5onexcess',()=>{let r=calc(acq({route:'ispc_supplier',supplierOptionRef:'QA opção aplicável'})).result;assert.equal(r.status,'partial');assert.equal(r.amounts.excessTax,null);assert.match(r.reason,/art5impede/);r=calc(acq({}, {base:'100000'})).result;assert.equal(r.status,'not_applicable');assert.equal(number(r.amounts.excess),0);r=calc(acq({}, {priorAnnualPurchases:'2500001'})).result;assert.equal(number(r.amounts.covered),0);assert.equal(number(r.amounts.coveredTax),0);assert.equal(number(r.amounts.excess),200000);});
|
|
test('newbooks preservehabitualandL3exactly;dependenciesstale withoutrewritingfrozenresults',()=>{let{d,b}=acq();const old=JSON.parse(d.modelo_a_snapshot);b=calc({d,b});d.modelo_a_snapshot=saveIspcResidualBook(d,b);for(const[k,v]of Object.entries(old))assert.deepEqual(JSON.parse(d.modelo_a_snapshot)[k],v);assert.equal(ispcResidualCurrent(d,b),true);const w=ispcWithholdingBook(d);w.input.base='300000';d.modelo_a_snapshot=saveIspcWithholdingBook(d,w);assert.equal(ispcResidualCurrent(d,b),false);assert.equal(number(b.result.amounts.excess),100000);const next=recordIspcResidual(d,b);assert.equal(next.history.length,1);assert.equal(number(next.result.amounts.excess),200000);assert.equal(number(next.history[0].amounts.excess),100000);});
|
|
test('formalBGeneral mappingfromexistingcurrentquarterwithoutrecalculation;monthnotinferred;old30notused',()=>{const d=structuredClone(seed),b=ispcResidualBook(d,'formalities'),before=d.modelo_a_snapshot;const result=recordIspcResidual(d,b);assert.equal(result.result.status,'partial');assert.equal(number(result.result.amounts.tax),24000);assert.equal(number(result.result.amounts.payment),24000);assert.match(result.result.details.formPeriod,/Indeterminado/);assert.equal(result.result.details.paymentMonth,'Julho2026');assert.equal(result.input.formMonth,'');d.modelo_a_snapshot=saveIspcResidualBook(d,result);const html=ispcFormalPreparationHtml(d);assert.match(html,/24000.00/);assert.match(html,/Quadro8/);assert.match(html,/Quadro10/);assert.match(html,/não é formulário oficial/);assert.deepEqual(JSON.parse(d.modelo_a_snapshot).ispcQuarters,JSON.parse(before).ispcQuarters);});
|
|
test('formalinputsguardedandexternalactsdeclaredonly;signed/officialareasnotautofilled;HTMLescaped',()=>{let d=structuredClone(seed),b=ispcResidualBook(d,'formalities');b.input={...b.input,name:'QA <script>bad()</script>',nuit:'123456789',fiscalArea:'QAUGC',address:'QAendereço',formMonth:'06',formYear:'2026',formPeriodRef:'SYNTHETIC QA correspondência período',interestSituation:'none',interestRef:'SYNTHETIC QA sem juros'};b=recordIspcResidual(d,b);assert.equal(b.result.status,'calculated',b.result.missing.join(';'));d.modelo_a_snapshot=saveIspcResidualBook(d,b);const html=ispcFormalPreparationHtml(d);assert.ok(html.includes('<script>'));assert.ok(!html.includes('<script>bad'));assert.match(html,/não preencher.*assinatura/i);b.input.externalStatus='paid';assert.equal(recordIspcResidual(d,b).result.status,'partial');});
|
|
test('documentedwithholdingownroutepreventsduplicatedformalselfpayment',()=>{let{d,b}=own({settlement:'withheld',buyerIspc:'yes',unregistered:'yes',buyerAnnualPurchases:'200000',withheldActual:'5000',withheldRef:'QA recibo5'});b=recordIspcResidual(d,b);d.modelo_a_snapshot=saveIspcResidualBook(d,b);const f=ispcResidualBook(d,'formalities');f.input.sourceKind='occasional';const r=recordIspcResidual(d,f).result;assert.equal(r.status,'not_applicable');assert.match(r.reason,/duplicada/);});
|
|
test('invalidstoredmaps/enumspreserveerrors;noemptyfallback',()=>{const d=structuredClone(seed),s=JSON.parse(d.modelo_a_snapshot);s.ispcResiduals=[];assert.throws(()=>ispcResidualBook({...d,modelo_a_snapshot:JSON.stringify(s)},'occasional'));const b=ispcResidualBook(d,'occasional');b.input.activity='services12';assert.throws(()=>saveIspcResidualBook(d,b));});
|
|
test('exports separate seller376/buyer375 and report;habitualbooksunchanged',()=>{let{d,b}=own();d.context_opportunity='opportunity:376';d.person_type='singular';d.project_title='SYNTHETIC QA — ISPC residuais';const e=ispcEligibilityBook(d);e.input.mode='occasional';e.input.personType='singular';d.modelo_a_snapshot=saveIspcEligibilityBook(d,e);b=recordIspcResidual(d,b,'2026-10-25T15:40:00Z');d.modelo_a_snapshot=saveIspcResidualBook(d,b);d.context_opportunity='opportunity:375';d.person_type='collective';const a=acq({route:'irps_isolated',noIspcOnExcess:'yes',isolatedAct:'yes',incomeInMoz:'yes',grossExcess:'100000',grossRef:'QA base ilíquida',reference:'QA acto57k'});const aw=ispcWithholdingBook(a.d);d.modelo_a_snapshot=saveIspcWithholdingBook(d,aw);d.modelo_a_snapshot=saveIspcResidualBook(d,recordIspcResidual(d,a.b,'2026-10-25T15:41:00Z'));const f=ispcResidualBook(d,'formalities');d.modelo_a_snapshot=saveIspcResidualBook(d,recordIspcResidual(d,f,'2026-10-25T15:42:00Z'));const report=createProjectDossierReport(d,{selectedSections:new Set(['finance']),version:'QA residuais',generatedAt:'2026-10-25T15:43:00Z'});assert.match(JSON.stringify(report),/25000.00/);assert.match(JSON.stringify(report),/24000.00/);assert.match(JSON.stringify(ispcResidualReportRows(d)),/IRPS57/);d.context_opportunity='opportunity:376';assert.equal(ispcResidualBook(d,'occasional').result.status,'calculated');assert.equal(number(ispcResidualBook(d,'occasional').result.amounts.tax),3000);d.context_opportunity='opportunity:375';if(process.env.ISPC_RESIDUAL_OUTPUT)fs.writeFileSync(process.env.ISPC_RESIDUAL_OUTPUT,JSON.stringify(d,null,2));});
|
|
|
|
test('new uniqueformsource preserves deliberatechoice;documentedoptionwithannualforecast staysscenario',()=>{let{d,b}=own({origin:'effective',option:'opted',optionRef:'QA opção',optionDate:'2026-01-01'});b=recordIspcResidual(d,b);assert.equal(b.result.status,'calculated');assert.equal(b.result.details.projected,'yes');assert.match(b.result.reason,/Cenário declarado/);const e=ispcEligibilityBook(d);e.input.mode='occasional';d.modelo_a_snapshot=saveIspcEligibilityBook(d,e);d.modelo_a_snapshot=saveIspcResidualBook(d,b);let f=ispcResidualBook(d,'formalities');assert.equal(f.input.sourceKind,'occasional');f.input.sourceKind='quarter';d.modelo_a_snapshot=saveIspcResidualBook(d,f);assert.equal(ispcResidualBook(d,'formalities').input.sourceKind,'quarter');});
|
|
|
|
test('acquisitionformalpreparation distinguishesISPC5andIRPS20andkeepsspecificexternalmodelpending',()=>{let{d,b}=acq({route:'irps_isolated',noIspcOnExcess:'yes',isolatedAct:'yes',incomeInMoz:'yes',grossExcess:'100000',grossRef:'QA base ilíquida',reference:'QA57k'});b=recordIspcResidual(d,b);d.modelo_a_snapshot=saveIspcResidualBook(d,b);let f=ispcResidualBook(d,'formalities');f.input.sourceKind='acquisitions';f=recordIspcResidual(d,f);assert.equal(number(f.result.amounts.tax),25000);assert.ok(f.result.missing.some(s=>s.includes('Modelo/código')));assert.equal(f.result.status,'partial');d.modelo_a_snapshot=saveIspcResidualBook(d,f);const html=ispcFormalPreparationHtml(d);assert.match(html,/ISPC—retenção12/);assert.match(html,/IRPS—acto isolado57/);assert.match(html,/5000.00/);assert.match(html,/20000.00/);assert.match(html,/Por corresponder/);});
|
|
|
|
test('knownownaccess exclusions are concrete notrequestsforrevalidation;unknownstillindeterminate',()=>{for(const patch of [{organizedRequired:'yes'},{priorYearTurnover:'4000000.000001'}]){const r=calc(own(patch)).result;assert.equal(r.status,'not_applicable');assert.equal(r.amounts.payment,undefined);assert.match(r.reason,/fora/i);}assert.equal(calc(own({organizedRequired:''})).result.status,'indeterminate');});
|
|
|
|
test('below500paymentdispensa iscalculatedbutformalrepresentationnotinventedaszerocalculatedtax',()=>{let{d,b}=own({amount:'1000'});b=recordIspcResidual(d,b);d.modelo_a_snapshot=saveIspcResidualBook(d,b);let f=ispcResidualBook(d,'formalities');f.input={...f.input,sourceKind:'occasional',name:'QA vendedor',nuit:'123456789',fiscalArea:'QAUGC',address:'QA endereço',formMonth:'06',formYear:'2026',formPeriodRef:'QA correspondência',interestSituation:'none',interestRef:'QA sem juros'};f=recordIspcResidual(d,f);assert.equal(number(f.result.amounts.tax),30);assert.equal(number(f.result.amounts.payment),0);assert.equal(f.result.status,'partial');assert.ok(f.result.missing.some(s=>s.includes('dispensa')));d.modelo_a_snapshot=saveIspcResidualBook(d,f);const html=ispcFormalPreparationHtml(d);assert.match(html,/Imposto calculado:30.00;prestação legal:0.00/);});
|
|
|
|
test('switchfromdocumentedliberatoryroute cannotgenerate secondduewithoutconcretereconciliation',()=>{let{d,b}=own({settlement:'withheld',buyerIspc:'yes',unregistered:'yes',buyerAnnualPurchases:'200000',withheldActual:'5000',withheldRef:'QA retenção definitiva'});b=recordIspcResidual(d,b);assert.equal(b.result.status,'settled_by_withholding');b.input.settlement='self';let next=recordIspcResidual(d,b);assert.equal(next.result.status,'partial');assert.equal(next.result.amounts.payment,null);assert.equal(next.result.amounts.tax,null);assert.equal(number(next.result.amounts.withheld),5000);assert.equal(next.history[0].status,'settled_by_withholding');const fresh=structuredClone(b);fresh.input.reference='QA outra operação';assert.equal(recordIspcResidual(d,fresh).result.status,'calculated');b.input.withholdingCorrected='yes';b.input.withholdingCorrectionRef='QA documento externo de rectificação';next=recordIspcResidual(d,b);assert.equal(next.result.status,'calculated');assert.equal(number(next.result.amounts.payment),3000);assert.equal(next.result.details.priorWithholdingRef,'QA retenção definitiva');assert.equal(next.history[0].status,'settled_by_withholding');});
|