Files
Investidor-V2/tests/frontend/irpc-normal.test.mjs
T

38 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {serializeBusinessPlanSnapshot} from '../../resources/js/lib/business-financial-plan.ts';
import {normalBook,newNormalBook,newNormalAdjustment,normalFinancialSource,calculateNormal,recordNormal,normalCurrent,saveNormalBook,normalReportRows,normalHtml} from '../../resources/js/lib/irpc-normal.ts';
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
import {irpcBook} from '../../resources/js/lib/irpc-withholdings.ts';
const plan=()=>({currency:'MZN',openingCash:'0',products:[{label:'Serviço',monthlyUnits:'10',unitPrice:'100',unitVariableCost:'30'}],monthlyCollections:'800',monthlyVariableCostPayments:'250',monthlyOperatingExpenses:'100',monthlyOperatingPayments:'80',monthlyPayrollExpense:'50',monthlyPayrollPayments:'40',assetInvestment:'0',inventory:'0',receivables:'0',payables:'0',equity:'0',shareholderLoans:'0',bankLoanProceeds:'0',grants:'0',assetDepreciation:'0',depreciationLifeMonths:12,monthlyInterestExpense:'10',monthlyInterestPayment:'5',monthlyPrincipalRepayment:'60',periods:12});
const draft=()=>({...JSON.parse(fs.readFileSync(new URL('./fixtures/irpc-mrfta-preserved.json',import.meta.url),'utf8')),fiscal_component:'irpc',business_plan_snapshot:serializeBusinessPlanSnapshot(plan())});
const row=(id='a')=>({...newNormalAdjustment(id),sourceCode:'operating_expenses',document:'QA aviso multa sintético',line:id,amount:'100',rule:'noncontractual_fine',included:'yes',reference:'QA pressuposto:multa não contratual incluída no custo operacional'});
const book=()=>({...newNormalBook(),year:'2026',mappingReference:'QA plano meses1–12=Jan–Dez2026',scope:'resident_general',scopeReference:'QA residente geral sem regime especial',rows:[row(),{...row('b'),sourceCode:'interest_expense',amount:'20',rule:'compensatory_interest',document:'QA liquidação juros sintética',reference:'QA pressuposto:juros compensatórios incluídos no custo financeiro'}]});
test('L6 reuses reconciled profit and expense sources,without cash repayment or received withholding becoming expense',()=>{
const d=draft(),s=normalFinancialSource(d),m=calculateNormal(d,book());assert.equal(s.accountingProfitBeforeTax,'6480.00');assert.equal(m.accountingProfit.numerator,'6480');assert.equal(m.additions.numerator,'120');assert.equal(m.partialAdjustedResult.numerator,'6600');assert.equal(m.taxableProfit,null);assert.equal(m.taxableBase,null);assert.equal(m.tax,null);assert.equal(m.origin,'projected');assert.match(m.pending.join(' '),/L6 parcial/);
const p=plan();p.monthlyPrincipalRepayment='0';p.monthlyInterestPayment='0';d.business_plan_snapshot=serializeBusinessPlanSnapshot(p);assert.deepEqual(calculateNormal(d,book()).partialAdjustedResult,m.partialAdjustedResult);
});
test('missing qualification affects its own adjustment;confirmed additions remain visible without a full adjusted total',()=>{
const b=book();b.rows[1].reference='';const m=calculateNormal(draft(),b);assert.equal(m.rows[0].status,'calculated');assert.equal(m.rows[1].adjustment,null);assert.equal(m.additions.numerator,'100');assert.equal(m.partialAdjustedResult,null);b.rows[1].rule='other';assert.equal(calculateNormal(draft(),b).rows[1].status,'outside_scope');
});
test('duplicate document lines and oversubscribed source costs cannot double count',()=>{
const b=book();b.rows.push({...b.rows[0],id:'duplicate'});let m=calculateNormal(draft(),b);assert.equal(m.rows[0].adjustment,null);assert.equal(m.rows[2].adjustment,null);b.rows=[{...row('one'),amount:'700'},{...row('two'),amount:'600'}];m=calculateNormal(draft(),b);assert.ok(m.rows.every(r=>r.adjustment===null));assert.match(m.pending.join(' '),/excedem/);
});
test('already excluded expenses,revenue sources,negative and overprecision amounts do not become additions',()=>{
for(const patch of [{included:'no'},{sourceCode:'revenue'},{amount:'-10'},{amount:'1.001'},{rule:''}]){const b=book();b.rows=[{...row(),...patch}];assert.equal(calculateNormal(draft(),b).rows[0].adjustment,null);}
});
test('year,mapping,currency,horizon and regime stay explicit;no global annual assessment inferred',()=>{
for(const patch of [{year:''},{year:'2025'},{mappingReference:''},{scope:'other'},{scopeReference:''}])assert.equal(calculateNormal(draft(),{...book(),...patch}).partialAdjustedResult,null);
for(const p of [{...plan(),currency:'USD'},{...plan(),periods:6}]){const d=draft();d.business_plan_snapshot=serializeBusinessPlanSnapshot(p);assert.equal(calculateNormal(d,book()).partialAdjustedResult,null);}
const d=draft();d.business_plan_snapshot='';assert.equal(calculateNormal(d,book()).accountingProfit,null);assert.equal(calculateNormal(d,{...book(),rows:[]}).partialAdjustedResult,null);
});
test('calculation save recovery report preserves installed books and invalidates history on financial or adjustment changes',()=>{
const d=draft(),before=JSON.parse(d.modelo_a_snapshot),b=recordNormal(d,book(),'2026-10-25T20:00:00Z');d.modelo_a_snapshot=saveNormalBook(d,b);const recovered=JSON.parse(JSON.stringify(d)),saved=normalBook(recovered);assert.equal(normalCurrent(recovered,saved),true);assert.deepEqual(JSON.parse(d.modelo_a_snapshot).irpcWithholdings,before.irpcWithholdings);assert.ok(irpcBook(d));assert.match(JSON.stringify(normalReportRows(recovered)),/6600/);assert.match(JSON.stringify(createProjectDossierReport(recovered,{selectedSections:new Set(['finance'])})),/6600/);assert.match(normalHtml(recovered),/Não é demonstração contabilística/);
const p=plan();p.monthlyOperatingExpenses='110';recovered.business_plan_snapshot=serializeBusinessPlanSnapshot(p);assert.equal(normalCurrent(recovered,saved),false);assert.throws(()=>normalHtml(recovered));const next=recordNormal(recovered,saved);assert.deepEqual(next.history[0],b.result);assert.equal(next.result.model.partialAdjustedResult.numerator,'6480');
fs.mkdirSync('outputs/irpc-l6',{recursive:true});fs.writeFileSync('outputs/irpc-l6/calculated.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l6/preparation.html',normalHtml(d));
});
test('opportunities remain separate and malformed persisted data is preserved with explicit error',()=>{
const d=draft();d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,book()));d.context_opportunity='another';assert.equal(normalBook(d).rows.length,0);const b=book();b.rows[0].amount=100;assert.throws(()=>saveNormalBook(d,b));const old=d.modelo_a_snapshot;assert.throws(()=>saveNormalBook(d,{...book(),rows:[row(),row()]}));assert.equal(d.modelo_a_snapshot,old);
});