41 lines
18 KiB
JavaScript
41 lines
18 KiB
JavaScript
import{calculateNormalBase}from'../../resources/js/lib/irpc-normal-base.ts';import{fraction}from'../../resources/js/lib/fiscal-rational.ts';
|
|
import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
|
|
import{normalBook,normalCurrent,calculateNormal,recordNormal,saveNormalBook,normalHtml,normalReportRows,normalFinancialSource}from'../../resources/js/lib/irpc-normal.ts';
|
|
import{newNormalCosts,newCostRow,calculateNormalCosts,reusableCreditBalance,costRules,normalCostReport}from'../../resources/js/lib/irpc-normal-costs.ts';
|
|
import{createProjectDossierReport}from'../../resources/js/lib/project-dossier-report.ts';
|
|
const seed=()=>JSON.parse(fs.readFileSync(new URL('./fixtures/irpc-schedules-preserved.json',import.meta.url),'utf8'));const val=f=>f?Number(f.numerator)/Number(f.denominator):null;
|
|
function row(id,rule,amount,facts={},sourceCode='operating_expenses'){return{...newCostRow(id),rule,amount,sourceCode,document:'QA-'+id,line:'1',reference:'QA documento e factos qualificados para o período anual2026; valores sintéticos',conditions:'yes',facts};}
|
|
function reconcile(b){const totals={};for(const r of b.schedules.rows){const k={tax_own:'expenseCorrections',tax_other:'expenseCorrections',undocumented:'expenseCorrections',capital_in:'positivePatrimonial',patrimonial_gain:'positivePatrimonial',capital_out:'negativePatrimonial',patrimonial_cost:'negativePatrimonial',future_cost:'timing',future_income:'timing',current_cost:'timing',current_income:'timing'}[r.rule];totals[k]=(totals[k]??0)+Number(r.amount);}for(const r of b.costs.rows){const k=costRules[r.rule][1];totals[k]=(totals[k]??0)+Number(r.amount);}for(const[k,a]of Object.entries(totals))b.profit.topics[k]={state:'known',amount:String(a),reference:'QA reconciliação aritmética por documento/linha dos mapas'};}
|
|
function setup(rows){const d=seed(),b=normalBook(d);b.costs=newNormalCosts();b.costs.rows=rows;b.costs.advertisingReference='QA publicidade completa e vendas normais12000 integralmente reconciliadas';b.costs.salesOrdinary='yes';b.costs.creditRegime='ordinary';b.costs.creditBase='10000';b.costs.creditPrevious='550';b.costs.creditReference='QA créditos ordinários finais10000 e provisão fiscal anterior550 documentados, sem regime financeiro';reconcile(b);return{d,b};}
|
|
function joint(){return setup([
|
|
row('allowance','allowance','100',{billed:'20',taxedNotBilled:'20'}),row('representation','representation','50'),row('advertising','advertising','80',{campaign:'no'}),row('credit','credit','200'),
|
|
row('stock','stock','60',{balanceKey:'inventory-A',lossObserved:'yes',observedLoss:'40',previous:'10'}),row('litigation','litigation','90',{balanceKey:'court-A',caseOngoing:'yes',chargeDeductible:'yes',obligation:'100',previous:'20'}),row('fuel','fuel','50',{supportedNormal:'40',passenger:'no'}),row('prior','prior_cost','80',{originalYear:'2025',unknownAtClose:'no',closingEvidence:'QA factura conhecida no fecho2025'}),
|
|
row('work','work','1000',{projectKey:'project-A',method:'percentage',price:'2000',totalCost:'1000',incurred:'500',priorRevenue:'600',priorCost:'300',bookCost:'200',costSource:'cost_of_sales',partialBilling:'yes',ownFractionalDelivery:'no',consistentMethod:'yes',deferredReceipt:'0'},'revenue'),
|
|
]);}
|
|
function model(r){const{d,b}=setup([r]);return calculateNormal(d,b);}
|
|
const item=m=>m.costs.rows[0];
|
|
test('compatible costs/provisions/prior timing/work produce exact profit6730 base4430 colecta1417.60 from existing6240 result',()=>{const{d,b}=joint(),m=calculateNormal(d,b);assert.equal(val(m.accountingProfit),6240);assert.equal(val(m.schedules.net),520);assert.equal(val(m.costs.net),-250);assert.equal(val(m.taxableProfit),6730);assert.equal(val(m.taxableBase),4430);assert.equal(val(m.colecta),1417.6);assert.equal(m.version,'IRPC-normal-adjustments/6');assert.equal(m.tax,null);assert.equal(m.assessment.finalBalance.payable,null);assert.deepEqual(m.costs.rows.map(r=>val(r.adjustment)),[30,40,0,150,30,10,10,80,-600]);assert.equal(m.costs.pending.length,0);});
|
|
test('allowance exceptions are disjoint amounts; accepted70 plus addback30; known zero differs from missing',()=>{let m=model(row('e','allowance','100',{billed:'20',taxedNotBilled:'20'}));assert.equal(val(item(m).accepted),70);assert.equal(val(item(m).adjustment),30);for(const facts of[{billed:'0',taxedNotBilled:'0'},{billed:'100',taxedNotBilled:'0'}])assert.notEqual(model(row('e','allowance','100',facts)).taxableProfit,null);for(const facts of[{billed:'',taxedNotBilled:'0'},{billed:'60',taxedNotBilled:'50'}])assert.equal(model(row('e','allowance','100',facts)).taxableProfit,null);});
|
|
test('representation20percent accepted; fractional cents remain exact without fiscal rounding or autonomous tax',()=>{const m=model(row('f','representation','0.01'));assert.deepEqual(item(m).accepted,{numerator:'1',denominator:'500'});assert.deepEqual(item(m).adjustment,{numerator:'1',denominator:'125'});assert.equal(m.tax,null);assert.equal(m.assessment.finalBalance.scope,'L8');});
|
|
test('advertising shares one annual1percent cap across documents and has no per-invoice duplicate allowance',()=>{const{d,b}=setup([row('n1','advertising','100',{campaign:'no'}),row('n2','advertising','100',{campaign:'no'})]);let m=calculateNormal(d,b);assert.deepEqual(m.costs.rows.map(r=>val(r.accepted)),[60,60]);assert.equal(val(m.costs.net),80);assert.deepEqual(m.costs.rows.map(r=>val(r.limit)),[120,120]);b.costs.rows.reverse();assert.equal(val(calculateNormal(d,b).costs.net),80);b.costs.salesOrdinary='';m=calculateNormal(d,b);assert.equal(m.taxableProfit,null);assert.equal(m.schedules.net.numerator,'520');const c=setup([row('ad-a','advertising','100',{campaign:'no'}),row('ad-b','advertising','250',{campaign:'no'})]);const f=calculateNormal(c.d,c.b);assert.match(JSON.stringify(normalCostReport(f.costs)),/exacto 240\/7/);});
|
|
test('campaign36A never silently receives ordinary advertising treatment',()=>{const m=model(row('campaign','advertising','100',{campaign:'yes'}));assert.equal(m.taxableProfit,null);assert.equal(item(m).accepted,null);assert.match(item(m).reason,/36-A/);});
|
|
test('vehicle costs distinguish ordinary passenger50percent, documented public/rental exception and nonpassenger',()=>{for(const[facts,accepted]of[[{passenger:'yes',publicOrRental:'no'},50],[{passenger:'yes',publicOrRental:'yes'},100],[{passenger:'no'},100]])assert.equal(val(item(model(row('v','vehicle','100',facts))).accepted),accepted);assert.equal(model(row('v','vehicle','100',{passenger:'yes'})).taxableProfit,null);});
|
|
test('fuel and rent cover independent rules; overlapping36h-i and36-4 stays pending without an invented combination',()=>{let m=model(row('fuel','fuel','100',{supportedNormal:'80',passenger:'yes',publicOrRental:'no'}));assert.equal(m.taxableProfit,null);assert.match(item(m).reason,/Articulação/);m=model(row('fuel','fuel','100',{supportedNormal:'100',passenger:'yes',publicOrRental:'no'}));assert.equal(val(item(m).accepted),50);m=model(row('rent','rental','100',{nonAcceptedQuota:'20',quotaReference:'QA quota36h documentada; não calculada automaticamente',passenger:'yes',publicOrRental:'yes'}));assert.equal(val(item(m).accepted),80);assert.equal(val(item(m).adjustment),20);assert.match(item(m).reason,/não apura automaticamente/);assert.equal(model(row('fuel','fuel','100',{supportedNormal:'101',passenger:'no'})).taxableProfit,null);assert.equal(model(row('rent','rental','100',{nonAcceptedQuota:'',quotaReference:'QA',passenger:'yes',publicOrRental:'no'})).taxableProfit,null);});
|
|
test('credit provision caps both annual1.5percent and cumulative6percent; multiple rows share headroom and do not consume source balance',()=>{const{d,b}=setup([row('c1','credit','100'),row('c2','credit','100')]);let m=calculateNormal(d,b);assert.deepEqual(m.costs.rows.map(r=>val(r.accepted)),[25,25]);assert.equal(val(m.costs.net),150);assert.equal(b.costs.creditPrevious,'550');b.costs.creditPrevious='0';m=calculateNormal(d,b);assert.equal(val(m.costs.net),50);b.costs.creditPrevious='600';assert.equal(val(calculateNormal(d,b).costs.net),200);b.costs.creditPrevious='601';assert.equal(calculateNormal(d,b).taxableProfit,null);b.costs.creditRegime='regulated';assert.equal(calculateNormal(d,b).taxableProfit,null);b.costs.creditRegime='ordinary';b.costs.creditPrevious='';assert.equal(calculateNormal(d,b).taxableProfit,null);});
|
|
test('inventory allowance respects observed loss less existing fiscal provision; absent observation is known rejection, not unknown zero',()=>{let m=model(row('s','stock','60',{balanceKey:'stock',lossObserved:'yes',observedLoss:'40',previous:'10'}));assert.equal(val(item(m).accepted),30);assert.equal(val(item(m).adjustment),30);m=model(row('s','stock','60',{lossObserved:'no'}));assert.equal(val(item(m).accepted),0);assert.equal(val(item(m).adjustment),60);m=model(row('s','stock','60',{balanceKey:'stock',lossObserved:'yes',observedLoss:'40',previous:''}));assert.equal(m.taxableProfit,null);});
|
|
test('litigation needs current court process and deductible underlying charge; known failure adds back full amount',()=>{for(const facts of[{caseOngoing:'no'},{caseOngoing:'yes',chargeDeductible:'no'}]){const m=model(row('l','litigation','90',facts));assert.equal(val(item(m).accepted),0);assert.equal(val(item(m).adjustment),90);}const m=model(row('l','litigation','90',{caseOngoing:'yes',chargeDeductible:'yes',balanceKey:'court',obligation:'100',previous:'20'}));assert.equal(val(item(m).accepted),80);assert.equal(model(row('l','litigation','90',{caseOngoing:'yes',chargeDeductible:''})).taxableProfit,null);});
|
|
test('reversal before deducted is taxed once; taxed-provision reversal is excluded only with prior fiscal evidence',()=>{let m=model(row('r','provision_reversal','50',{balanceKey:'old',previous:'70',eventEnded:'yes',inResult:'yes'},'revenue'));assert.equal(val(item(m).adjustment),0);m=model(row('r','provision_reversal','50',{balanceKey:'old',previous:'70',eventEnded:'yes',inResult:'no'},''));assert.equal(val(item(m).adjustment),50);m=model(row('r','taxed_reversal','50',{balanceKey:'old',previous:'70',eventEnded:'yes',inResult:'yes'},'revenue'));assert.equal(val(item(m).adjustment),-50);assert.equal(model(row('r','taxed_reversal','80',{balanceKey:'old',previous:'70',eventEnded:'yes',inResult:'yes'},'revenue')).taxableProfit,null);});
|
|
test('same fiscal object cannot reuse stock/litigation/reversal balance with another document',()=>{const{d,b}=setup([row('s1','stock','50',{balanceKey:'same',lossObserved:'yes',observedLoss:'100',previous:'0'}),row('s2','stock','50',{balanceKey:'same',lossObserved:'yes',observedLoss:'100',previous:'0'})]);const m=calculateNormal(d,b);assert.equal(m.taxableProfit,null);assert.ok(m.costs.rows.every(r=>r.adjustment===null));});
|
|
test('prior-period cost and income distinguish knowledge at previous closing; never edit earlier returns or recorded histories',()=>{for(const[rule,src,unknown,expected]of[['prior_cost','operating_expenses','yes',0],['prior_cost','operating_expenses','no',100],['prior_income','revenue','yes',0],['prior_income','revenue','no',-100]]){const m=model(row('p',rule,'100',{originalYear:'2025',unknownAtClose:unknown,closingEvidence:'QA fecho anterior'},src));assert.equal(val(item(m).adjustment),expected);}assert.equal(model(row('p','prior_cost','100',{originalYear:'2025',unknownAtClose:'',closingEvidence:'QA'})).taxableProfit,null);});
|
|
test('work percentage uses accumulated performance less prior recognized revenue and cost, no double recognition',()=>{const{d,b}=joint(),m=calculateNormal(d,b),w=m.costs.rows.at(-1);assert.equal(val(w.limit),400);assert.equal(val(w.accepted),200);assert.equal(val(w.adjustment),-600);assert.match(w.reason,/grau=incorrido/);assert.equal(b.costs.rows.at(-1).facts.priorRevenue,'600');});
|
|
test('closing work respects mandatory percentage,95percent threshold and missing facts; estimated loss is separate full addback',()=>{const{d,b}=joint(),w=b.costs.rows.at(-1);w.facts.method='closing';assert.equal(calculateNormal(d,b).taxableProfit,null);w.facts.partialBilling='no';w.facts.priorRevenue='0';w.facts.priorCost='0';w.facts.publicReception='no';let m=calculateNormal(d,b);assert.equal(val(m.costs.rows.at(-1).limit),0);w.facts.incurred='950';m=calculateNormal(d,b);assert.equal(val(m.costs.rows.at(-1).limit),2000);w.facts.deferredReceipt='1';assert.equal(calculateNormal(d,b).taxableProfit,null);assert.equal(val(item(model(row('estimate','estimated_work_loss','100'))).adjustment),100);});
|
|
test('cross-map duplicate identity and cumulative source over-allocation block only dependent totals',()=>{const{d,b}=joint();b.costs.rows[0].document=b.rows[0].document+' ';b.costs.rows[0].line=b.rows[0].line;let m=calculateNormal(d,b);assert.equal(m.taxableProfit,null);assert.match(m.costs.rows[0].reason,/repetidos/);assert.equal(m.costs.rows[0].adjustment,null);assert.equal(val(m.costs.rows[1].adjustment),40);const c=joint();c.b.costs.rows[0].amount='1000';reconcile(c.b);m=calculateNormal(c.d,c.b);assert.equal(m.taxableProfit,null);assert.match(m.costs.pending.join(' '),/excedem a linha/);});
|
|
test('missing one credit parameter retains known independent corrections and makes downstream totals unknown; upstream records remain',()=>{const{d,b}=joint();b.costs.creditPrevious='';const m=calculateNormal(d,b);assert.equal(m.taxableProfit,null);assert.equal(m.taxableBase,null);assert.equal(m.colecta,null);assert.equal(val(m.accountingProfit),6240);assert.equal(val(m.costs.rows[1].adjustment),40);assert.equal(val(m.schedules.net),520);assert.equal(m.schedules.pending.length,0);});
|
|
test('credit reuse requires known financial closing balances; absent schedule never creates zero',()=>{assert.equal(reusableCreditBalance(null),null);assert.equal(reusableCreditBalance({cashSchedule:[{salesReceivablesBalance:null,openingReceivablesBalance:'0'}]}),null);assert.equal(val(reusableCreditBalance({cashSchedule:[{salesReceivablesBalance:'100',openingReceivablesBalance:'20'}]})),120);});
|
|
test('installed5 stays current without recalculation; version6 API-ready snapshots preserve books/history and export exact rules/amounts',()=>{const old=seed(),before=normalBook(old);assert.equal(normalCurrent(old,before),true);assert.equal(before.result.model.version,'IRPC-normal-adjustments/5');const{d,b}=joint(),store=JSON.parse(d.modelo_a_snapshot);d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T00:30:00+02:00'));const back=normalBook(JSON.parse(JSON.stringify(d)));assert.equal(normalCurrent(d,back),true);assert.deepEqual(back.history.at(-1),before.result);const saved=JSON.parse(d.modelo_a_snapshot);assert.deepEqual(saved.books,store.books);assert.deepEqual(saved.irpcWithholdings,store.irpcWithholdings);assert.match(normalHtml(d),/1417.60/);assert.match(normalHtml(d),/parcela aceite/);assert.match(JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance'])})),/36\(1\)n/);assert.match(JSON.stringify(normalReportRows(d)),/M20 — correspondência/);fs.mkdirSync('outputs/irpc-l6-costs',{recursive:true});fs.writeFileSync('outputs/irpc-l6-costs/calculated.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l6-costs/preparation.html',normalHtml(d));const missing=normalBook(d);missing.costs.creditPrevious='';d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,missing));fs.writeFileSync('outputs/irpc-l6-costs/missing.json',JSON.stringify(d));const changed=normalBook(d);changed.costs.rows[0].facts.billed='30';d.modelo_a_snapshot=saveNormalBook(d,changed);assert.equal(normalCurrent(d,normalBook(d)),false);assert.throws(()=>normalHtml(d));});
|
|
test('malformed versions, unknown rules and non-string facts are refused with saved originals intact',()=>{const{d,b}=joint();for(const change of[x=>x.version=2,x=>x.rows[0].rule='__proto__',x=>x.rows[0].facts.billed=20]){const c=structuredClone(b);change(c.costs);assert.throws(()=>saveNormalBook(d,c));}assert.equal(normalCurrent(d,normalBook(d)),true);});
|
|
|
|
test('additional categories,shared limits and closing method generate separate recoverable compatible cases',()=>{const cases={additional:setup([row('vehicle','vehicle','100',{passenger:'yes',publicOrRental:'no'}),row('rental','rental','100',{nonAcceptedQuota:'20',quotaReference:'QA quota36h documentada',passenger:'yes',publicOrRental:'yes'}),row('estimate','estimated_work_loss','50'),row('reverse','provision_reversal','50',{balanceKey:'provision-old-deducted',previous:'50',eventEnded:'yes',inResult:'no'},''),row('taxed-reverse','taxed_reversal','50',{balanceKey:'provision-old-taxed',previous:'50',eventEnded:'yes',inResult:'yes'},'revenue'),row('prior-income','prior_income','50',{originalYear:'2025',unknownAtClose:'no',closingEvidence:'QA receita conhecida no encerramento anterior'},'revenue')]),limits:setup([row('ad1','advertising','100',{campaign:'no'}),row('ad2','advertising','100',{campaign:'no'}),row('cr1','credit','100'),row('cr2','credit','100')]),closing:joint()};const w=cases.closing.b.costs.rows.at(-1);Object.assign(w.facts,{method:'closing',partialBilling:'no',priorRevenue:'0',priorCost:'0',publicReception:'no'});const expected={additional:[7050,4750,1520],limits:[7210,4910,1571.2],closing:[6530,4230,1353.6]};for(const[name,{d,b}]of Object.entries(cases)){const m=calculateNormal(d,b);assert.deepEqual([val(m.taxableProfit),val(m.taxableBase),val(m.colecta)],expected[name]);d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b));fs.writeFileSync('outputs/irpc-l6-costs/'+name+'.json',JSON.stringify(d));}});
|
|
|
|
test('percentage-generated loss below one cent remains a negative fiscal result, never a silently truncated zero',()=>{const{b}=joint();b.base.lossesState='absent';b.base.losses=[];assert.equal(calculateNormalBase(b.base,fraction(-1n,1000n),'2026').taxableBase,null);assert.equal(val(calculateNormalBase(b.base,fraction(-1n,1000n),'2026','QA perda corrente exacta').taxableBase),0);assert.equal(val(calculateNormalBase(b.base,fraction(1n,1000n),'2026').taxableBase),0.001);});
|