19 lines
7.8 KiB
JavaScript
19 lines
7.8 KiB
JavaScript
import test from'node:test';import assert from'node:assert/strict';import fs from'node:fs';
|
|
import{normalBook,normalCurrent,calculateNormal,recordNormal,saveNormalBook,normalHtml}from'../../resources/js/lib/irpc-normal.ts';
|
|
import{newNormalBaseInput,newNormalLoss,calculateNormalBase}from'../../resources/js/lib/irpc-normal-base.ts';
|
|
import{fraction}from'../../resources/js/lib/fiscal-rational.ts';import{createProjectDossierReport}from'../../resources/js/lib/project-dossier-report.ts';
|
|
const seed=()=>JSON.parse(fs.readFileSync(new URL('./fixtures/irpc-profit-preserved.json',import.meta.url),'utf8'));
|
|
const loss=(id,year,assessed,used,requested)=>({...newNormalLoss(id),year,subjectNuit:'123456789',assessed,previouslyUsed:used,requested,reference:'QA saldo fiscal corrigido,sujeito/exercício '+year,kind:'ordinary',annualPeriods:'yes',generalActivity:'yes',balanceConfirmed:'yes'});
|
|
const input=()=>({...newNormalBaseInput(),subjectNuit:'123456789',identityReference:'QA sujeito do lucro e dos saldos',method:'direct',methodReference:'QA declaração e contabilidade organizada',lossesState:'known',lossesReference:'QA inventário completo dos saldos fiscais do sujeito',unchangedObject:'yes',objectReference:'QA actividade e objecto mantidos ao termo2026',losses:[loss('old','2021','1000','200','800'),loss('new','2024','3000','500','1500')],benefits:{state:'known',amount:'0',reference:'QA deduções/benefícios à base,valor conhecido zero'}});
|
|
test('6700 is reused as profit;2300 documented loss use yields4400 taxable base,never tax payable',()=>{const d=seed(),b=normalBook(d);b.base=input();const m=calculateNormal(d,b);assert.equal(m.taxableProfit.numerator,'6700');assert.equal(m.base.requestedLosses.numerator,'2300');assert.equal(m.base.deductedLosses.numerator,'2300');assert.equal(m.taxableBase.numerator,'4400');assert.equal(m.tax,null);assert.deepEqual(m.base.losses.map(l=>l.available.numerator),['800','2500']);assert.deepEqual(m.base.losses.map(l=>l.remaining.numerator),['0','1000']);assert.deepEqual(m.base.losses.map(l=>l.lastOrdinaryYear),[2026,2029]);});
|
|
test('supported absence of losses and benefits retains6700 base;missing benefit information does not mean zero',()=>{const i={...input(),lossesState:'absent',losses:[],unchangedObject:'',objectReference:'',benefits:{state:'absent',amount:'',reference:'QA sem benefício aplicável'}};let m=calculateNormalBase(i,fraction(6700n),'2026');assert.equal(m.taxableBase.numerator,'6700');i.benefits={state:'',amount:'',reference:''};m=calculateNormalBase(i,fraction(6700n),'2026');assert.equal(m.taxableBase,null);assert.equal(m.taxableProfit.numerator,'6700');});
|
|
test('expired ordinary2020 loss cannot be deducted;2021 remains within last ordinary year2026',()=>{const i=input();i.losses=[loss('expired','2020','500','0','0')];i.unchangedObject='';i.objectReference='';let m=calculateNormalBase(i,fraction(6700n),'2026');assert.equal(m.losses[0].status,'excluded');assert.equal(m.losses[0].lastOrdinaryYear,2025);assert.equal(m.taxableBase.numerator,'6700');i.losses[0].requested='1';assert.equal(calculateNormalBase(i,fraction(6700n),'2026').taxableBase,null);});
|
|
test('losses cannot exceed available balance or profit;no implicit allocation order or percentage cap',()=>{const i=input();i.losses[0].requested='801';assert.equal(calculateNormalBase(i,fraction(6700n),'2026').taxableBase,null);i.losses=[loss('a','2021','5000','0','5000'),loss('b','2024','5000','0','4000')];let m=calculateNormalBase(i,fraction(6700n),'2026');assert.equal(m.taxableBase,null);assert.match(m.pending.join(' '),/reparta explicitamente/);i.losses[0].requested='2700';m=calculateNormalBase(i,fraction(6700n),'2026');assert.equal(m.taxableBase.numerator,'0');assert.equal(m.deductedLosses.numerator,'6700');});
|
|
test('same taxpayer,annual periods,ordinary origin,reconciled balance and activity facts are specific dependencies',()=>{for(const patch of [{subjectNuit:'987654321'},{annualPeriods:'no'},{generalActivity:'no'},{balanceConfirmed:''},{kind:'transfer'},{year:'2026'},{previouslyUsed:'1100'},{requested:''}]){const i=input();Object.assign(i.losses[0],patch);assert.equal(calculateNormalBase(i,fraction(6700n),'2026').taxableBase,null);}});
|
|
test('changed object explicitly excludes deduction;duplicate annual balances and indirect methods remain pending',()=>{const i=input();i.unchangedObject='no';i.losses.forEach(l=>l.requested='0');let m=calculateNormalBase(i,fraction(6700n),'2026');assert.equal(m.taxableBase.numerator,'6700');assert.ok(m.losses.every(l=>l.status==='excluded'));i.unchangedObject='yes';i.losses[1].year='2021';assert.equal(calculateNormalBase(i,fraction(6700n),'2026').taxableBase,null);i.losses[1].year='2024';i.method='indirect';assert.equal(calculateNormalBase(i,fraction(6700n),'2026').taxableBase,null);});
|
|
test('positive known benefits are preserved without free subtraction;absence contradictions remain explicit',()=>{const i=input();i.benefits={state:'known',amount:'200',reference:'QA benefício positivo,regra por corresponder'};let m=calculateNormalBase(i,fraction(6700n),'2026');assert.equal(m.input.benefits.amount,'200');assert.equal(m.taxableBase,null);i.benefits={state:'absent',amount:'200',reference:'QA ausência contraditória'};assert.equal(calculateNormalBase(i,fraction(6700n),'2026').taxableBase,null);i.benefits={state:'known',amount:'0',reference:'QA zero conhecido'};i.lossesState='absent';assert.equal(calculateNormalBase(i,fraction(6700n),'2026').taxableBase,null);});
|
|
test('unknown or negative current profit is preserved,not transformed to zero or current-year loss carryforward',()=>{for(const p of [null,fraction(-100n)]){const m=calculateNormalBase(input(),p,'2026');assert.equal(m.taxableBase,null);assert.deepEqual(m.taxableProfit,p);}});
|
|
test('legacy current result and all books survive;new3 history,save/recovery/dossier and repeated simulation do not consume official balances',()=>{const d=seed(),legacy=normalBook(d);assert.equal(normalCurrent(d,legacy),true);assert.equal(legacy.result.model.version,'IRPC-normal-adjustments/2');const b={...legacy,base:input()},before=JSON.parse(d.modelo_a_snapshot),saved=recordNormal(d,b,'2026-10-25T23:00:00Z');assert.deepEqual(saved.history.at(-1),legacy.result);assert.equal(saved.result.model.version,'IRPC-normal-adjustments/3');d.modelo_a_snapshot=saveNormalBook(d,saved);assert.deepEqual(JSON.parse(d.modelo_a_snapshot).irpcWithholdings,before.irpcWithholdings);const back=JSON.parse(JSON.stringify(d));assert.equal(normalCurrent(back,normalBook(back)),true);assert.match(normalHtml(back),/matéria colectável simulada:4400/);assert.match(normalHtml(back),/imposto a pagar:Indeterminado/);assert.match(JSON.stringify(createProjectDossierReport(back,{selectedSections:new Set(['finance'])})),/4400/);const repeated=recordNormal(back,normalBook(back));assert.equal(repeated.result.model.taxableBase.numerator,'4400');assert.equal(repeated.base.losses[1].previouslyUsed,'500');assert.equal(repeated.base.losses[1].requested,'1500');const changed=normalBook(back);changed.base.losses[0].requested='700';back.modelo_a_snapshot=saveNormalBook(back,changed);assert.equal(normalCurrent(back,normalBook(back)),false);assert.throws(()=>normalHtml(back));
|
|
fs.mkdirSync('outputs/irpc-l6-base',{recursive:true});fs.writeFileSync('outputs/irpc-l6-base/calculated.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l6-base/preparation.html',normalHtml(d));const missing=normalBook(d);missing.base.benefits={state:'',amount:'',reference:''};const md=structuredClone(d);md.modelo_a_snapshot=saveNormalBook(md,recordNormal(md,missing));fs.writeFileSync('outputs/irpc-l6-base/missing.json',JSON.stringify(md));});
|
|
test('malformed future input versions and duplicate IDs fail without resetting originals',()=>{const d=seed(),b=normalBook(d);b.base=input();b.base.version=2;assert.throws(()=>saveNormalBook(d,b));b.base.version=1;b.base.losses[1].id=b.base.losses[0].id;assert.throws(()=>saveNormalBook(d,b));});
|