33 lines
14 KiB
JavaScript
33 lines
14 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 {newSpecialInput,newQualification,newSpecialExpense,newAcceleratedAsset} from '../../resources/js/lib/irpc-special.ts';
|
|
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
|
|
const seed=()=>JSON.parse(fs.readFileSync(new URL('./fixtures/irpc-l6-values-preserved.json',import.meta.url),'utf8'));
|
|
const value=v=>v?Number(v.numerator)/Number(v.denominator):null;
|
|
const qualification=(state='absent',from='2026-07-01',to='2026-12-31')=>({...newQualification(),state,eligible:state==='known'?'yes':'',from,to,reference:'QA declaração/prova das condições legais no sujeito e parcela,sem inferência territorial',periodReference:'QA norma/exercício2026 e período documentado'});
|
|
function setup(){const d=seed(),b=normalBook(d);b.special=newSpecialInput();for(const part of[b.special.expenses,b.special.cooperative,b.special.acceleration])part.qualification=qualification();return{d,b};}
|
|
function expenses(){const{d,b}=setup();b.schedules.rows.push({id:'expense36g',rule:'undocumented',amount:'100',sourceCode:'operating_expenses',document:'QA-despesa',line:'1',reference:'QA despesa100 efectivamente incluída e não documentada36g',fiscalYear:'',conditions:'yes'});b.profit.topics.expenseCorrections={state:'known',amount:'100',reference:'QA acréscimo36g já conferido,sem outra correcção'};b.special.expenses.qualification=qualification('known');b.special.expenses.rows=[{...newSpecialExpense('autonomous1'),scheduleId:'expense36g',nature:'undocumented',eligible:'yes',reference:'QA qualificação legal61(4),não inferida de anexo em falta'}];return{d,b};}
|
|
function cooperative(){const{d,b}=setup();b.special.cooperative={qualification:qualification('known'),kind:'agrarian',allIncomeEligible:'yes',withheldIncome:'absent'};return{d,b};}
|
|
function acceleration(){const{d,b}=setup();delete b.periodsFx;delete b.forms;b.schedules.interest.amount='240';Object.assign(b.depreciation.assets[0],{category:'machine',description:'Máquina industrial existente QA',cost:'1000',rate:'10',accounting:'120',start:'2026-01-01',timing:'annual'});for(const part of[b.special.expenses,b.special.cooperative,b.special.acceleration])part.qualification=qualification('absent','2026-01-01','2026-12-31');b.special.acceleration={qualification:qualification('known','2026-01-01','2026-12-31'),cbfRequirements:'yes',recognition:'documented',recognitionReference:'QA instrumento do projecto/reconhecimento10,pressupostos3/9 e afectação industrial documentados',noOtherBenefits:'yes',rows:[{...newAcceleratedAsset('accelerated1'),assetId:b.depreciation.assets[0].id,kind:'industrial_equipment',eligible:'yes',reference:'QA factura/afectação da máquina ao projecto industrialCBF16'}]};return{d,b};}
|
|
test('no optionalL7 keeps exact accepted9 result and key; installed history is not recalculated on read',()=>{const d=seed(),b=normalBook(d),old=structuredClone(b.result);assert.equal(normalCurrent(d,b),true);assert.deepEqual(calculateNormal(d,b),old.model);assert.deepEqual(normalBook(d).result,old);});
|
|
test('61(4) reuses100 addback36g and computes35 autonomously without another profit adjustment',()=>{const{d,b}=expenses(),m=calculateNormal(d,b),s=m.special;assert.equal(m.version,'IRPC-normal-adjustments/10');assert.equal(value(m.taxableProfit),3230);assert.equal(value(m.colecta),1033.6);assert.equal(value(s.taxableProfit),3230);assert.equal(value(s.expenses.rows[0].l6Adjustment),100);assert.equal(value(s.expenses.rows[0].additionalProfitAdjustment),0);assert.equal(value(s.expenses.autonomousTax),35);assert.equal(value(s.combinedColecta),1068.6);assert.equal(s.finalBalance.payable,null);});
|
|
test('missing, absent and notapplicable differ; attachment presence alone never classifies an expense',()=>{const{d,b}=setup();b.special.expenses.qualification=qualification('');let s=calculateNormal(d,b).special;assert.equal(s.expenses.status,'indeterminate');assert.equal(s.expenses.autonomousTax,null);b.special.expenses.qualification=qualification('absent');s=calculateNormal(d,b).special;assert.equal(s.expenses.status,'absent');assert.equal(value(s.expenses.autonomousTax),0);b.special.expenses.qualification=qualification('not_applicable');assert.equal(calculateNormal(d,b).special.expenses.status,'not_applicable');const c=expenses();c.b.special.expenses.rows[0].nature='';assert.equal(calculateNormal(c.d,c.b).special.expenses.autonomousTax,null);});
|
|
test('false condition keeps existing addback and no autonomous tax; contradictory absence cannot erase expense',()=>{const{d,b}=expenses();b.special.expenses.rows[0].eligible='no';let m=calculateNormal(d,b);assert.equal(m.special.expenses.rows[0].status,'not_applicable');assert.equal(value(m.special.expenses.autonomousTax),0);assert.equal(value(m.taxableProfit),3230);b.special.expenses.rows=[];b.special.expenses.qualification=qualification('absent');m=calculateNormal(d,b);assert.equal(m.special.expenses.status,'indeterminate');assert.equal(value(m.taxableProfit),3230);});
|
|
test('duplicate, unlinked or uncalculated36g cannot doublecharge or manufacture tax',()=>{for(const mode of['duplicate','unlinked','uncalculated','missingcoverage']){const{d,b}=expenses();if(mode==='duplicate')b.special.expenses.rows.push({...b.special.expenses.rows[0],id:'duplicate'});if(mode==='unlinked')b.special.expenses.rows[0].scheduleId='not-found';if(mode==='uncalculated')b.schedules.rows[0].conditions='';if(mode==='missingcoverage')b.special.expenses.rows=[];assert.equal(calculateNormal(d,b).special.expenses.autonomousTax,null,mode);}});
|
|
test('common qualification rejects wrong period/year or missing identity without blocking independent L6 totals',()=>{for(const mutate of[b=>b.special.expenses.qualification.to='2025-12-31',b=>b.special.expenses.qualification.periodReference='',b=>b.base.subjectNuit='']){const{d,b}=expenses();mutate(b);const m=calculateNormal(d,b);assert.equal(m.special.expenses.autonomousTax,null);if(b.base.subjectNuit)assert.equal(value(m.colecta),1033.6);}});
|
|
test('unknown benefit does not suppress separately calculable autonomous expense or normal reference',()=>{const{d,b}=expenses();b.special.acceleration.qualification=qualification('');const m=calculateNormal(d,b);assert.equal(value(m.special.expenses.autonomousTax),35);assert.equal(value(m.special.reference.colecta),1033.6);assert.equal(m.special.combinedColecta,null);});
|
|
test('eligible cooperative uses16percent without changing reference32percent or autonomous expense rate',()=>{const{d,b}=cooperative(),m=calculateNormal(d,b);assert.equal(value(m.colecta),1001.6);assert.equal(m.special.cooperative.status,'calculated');assert.equal(value(m.special.cooperative.rate),16);assert.equal(value(m.special.colecta),500.8);assert.equal(value(m.special.taxableBase),3130);assert.equal(value(m.special.combinedColecta),500.8);const x=expenses();x.b.special.cooperative=structuredClone(b.special.cooperative);assert.equal(value(calculateNormal(x.d,x.b).special.combinedColecta),551.8);});
|
|
test('mixed, withheld, unclassified or false cooperative conditions never grant blanket reduction',()=>{for(const mutate of[c=>c.allIncomeEligible='no',c=>c.withheldIncome='known',c=>c.kind='other']){const{d,b}=cooperative();mutate(b.special.cooperative);const m=calculateNormal(d,b);assert.equal(m.special.cooperative.status,'indeterminate');assert.equal(m.special.colecta,null);assert.equal(value(m.colecta),1001.6);}const{d,b}=cooperative();b.special.cooperative.qualification.eligible='no';assert.equal(value(calculateNormal(d,b).special.colecta),1001.6);});
|
|
test('cooperative treatment does not require mislabelling special rate as L6 general; contradictory absence remains pending',()=>{const{d,b}=cooperative();b.assessment.treatment='other';let m=calculateNormal(d,b);assert.equal(m.colecta,null);assert.equal(value(m.special.colecta),500.8);b.special.cooperative.qualification=qualification('absent');m=calculateNormal(d,b);assert.equal(m.special.cooperative.status,'indeterminate');assert.equal(m.special.colecta,null);});
|
|
test('common period follows declared shortperiod even without schedules; calendar dates cannot replace it',()=>{const{d,b}=cooperative();delete b.schedules;b.special.expenses.qualification=qualification('absent','2026-01-01','2026-12-31');const m=calculateNormal(d,b);assert.equal(m.special.period.from,'2026-07-01');assert.equal(m.special.expenses.status,'indeterminate');});
|
|
test('CBF16 reuses asset and rate10to15 but respects120 accounted cap; additional deduction20 not50',()=>{const{d,b}=acceleration(),input=JSON.stringify(b.depreciation),m=calculateNormal(d,b),s=m.special;assert.equal(s.acceleration.status,'calculated',s.acceleration.reason);assert.equal(value(m.depreciation.assets[0].quota),100);assert.equal(value(s.acceleration.accelerated.assets[0].quota),150);assert.equal(value(s.acceleration.accelerated.assets[0].deduction),120);assert.equal(value(s.acceleration.additionalDeduction),20);assert.equal(value(m.taxableProfit),6260);assert.equal(value(s.taxableProfit),6240);assert.equal(value(s.taxableBase),6240);assert.equal(value(s.colecta),1996.8);assert.equal(JSON.stringify(b.depreciation),input);});
|
|
test('new project building reuses land separation and legal table rate before bounded acceleration',()=>{const{d,b}=acceleration();Object.assign(b.depreciation.assets[0],{category:'commercial_building',cost:'5000',land:'0',rate:'2'});b.special.acceleration.rows[0].kind='new_project_building';const m=calculateNormal(d,b);assert.equal(m.special.acceleration.status,'calculated');assert.equal(value(m.special.acceleration.accelerated.assets[0].quota),150);assert.equal(value(m.special.acceleration.additionalDeduction),20);});
|
|
test('lower optional normal quota remains valid L6 but is not treated as legally fixed rate in CBF16',()=>{const{d,b}=acceleration();b.depreciation.assets[0].rate='5';const m=calculateNormal(d,b);assert.equal(m.depreciation.assets[0].status,'calculated');assert.equal(m.special.acceleration.additionalDeduction,null);assert.match(m.special.acceleration.reason,/quota inferior/);});
|
|
test('location alone, wrong activity, prior quotas, unsupported periods and benefit combinations cannot accelerate',()=>{for(const mutate of[b=>b.special.acceleration.cbfRequirements='',b=>b.special.acceleration.recognitionReference='',b=>b.special.acceleration.rows[0].kind='other',b=>b.depreciation.assets[0].timing='months',b=>b.depreciation.assets[0].previousAccounting='1',b=>b.special.acceleration.noOtherBenefits='no',b=>{b.special.cooperative={qualification:qualification('known','2026-01-01','2026-12-31'),kind:'agrarian',allIncomeEligible:'yes',withheldIncome:'absent'};}]){const{d,b}=acceleration();d.province_id='ZEE-candidate';mutate(b);const m=calculateNormal(d,b);assert.equal(m.special.acceleration.additionalDeduction,null);assert.notEqual(m.taxableProfit,null);assert.equal(m.special.acceleration.status,'indeterminate');}});
|
|
test('save/recover/report preserves9history and L5/books; repeated calculation never accumulates tax or asset deduction',()=>{const{d,b}=expenses(),old=structuredClone(b.result),plan=d.business_plan_snapshot;d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T14:00:00+02:00'));let back=normalBook(JSON.parse(JSON.stringify(d)));assert.equal(normalCurrent(d,back),true);assert.deepEqual(back.history.at(-1),old);assert.equal(d.business_plan_snapshot,plan);assert.ok(JSON.parse(d.modelo_a_snapshot).irpcWithholdings);const html=normalHtml(d);assert.match(html,/imposto autónomo:35/);assert.match(html,/novo ajustamento ao lucro:0/);assert.match(html,/Saldo final a pagar\/recuperar:Indeterminado/);assert.match(JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance'])})),/Despesas — imposto autónomo/);d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,back));back=normalBook(d);assert.equal(value(back.result.model.special.expenses.autonomousTax),35);assert.equal(value(back.result.model.taxableProfit),3230);d.province_id='changed';assert.equal(normalCurrent(d,normalBook(d)),false);assert.throws(()=>normalHtml(d),/Calcule/);});
|
|
test('new evidence changes key; malformedqualification or duplicate row ID cannot overwrite book',()=>{const{d,b}=expenses();d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b));const back=normalBook(d);back.special.expenses.rows[0].reference+=' alterado';assert.equal(normalCurrent(d,back),false);back.special.expenses.rows.push(structuredClone(back.special.expenses.rows[0]));assert.throws(()=>saveNormalBook(d,back),/identificador repetido/);});
|
|
test('report presents context labels and retains provenance internally without dumping raw context snapshots',()=>{const{d,b}=setup();d.province_id_label='Província QA';d.context_opportunity_snapshot='{"internal":"QA implementation blob"}';d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b));assert.match(normalHtml(d),/Província: Província QA/);assert.doesNotMatch(normalHtml(d),/QA implementation blob/);assert.equal(normalBook(d).result.model.special.context.context_opportunity_snapshot,d.context_opportunity_snapshot);});
|
|
test('write only new QA cases for transactional API and recovered reports',()=>{const missing=expenses(),negative=expenses();missing.b.special.expenses.rows[0].nature='';negative.b.special.expenses.rows[0].eligible='no';const cases={expense:expenses(),missing,negative,absent:setup(),cooperative:cooperative(),acceleration:acceleration()};fs.mkdirSync('outputs/irpc-l7-common',{recursive:true});for(const[name,{d,b}]of Object.entries(cases)){d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T14:00:00+02:00'));fs.writeFileSync('outputs/irpc-l7-common/'+name+'.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l7-common/'+name+'.html',normalHtml(d));}});
|