34 lines
10 KiB
JavaScript
34 lines
10 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,normalFinancialSource}from'../../resources/js/lib/irpc-normal.ts';
|
||
import{newNormalPeriodFx,mapNormalPeriodFx}from'../../resources/js/lib/irpc-normal-period-fx.ts';
|
||
import{emptyFxSelection,selectFx}from'../../resources/js/lib/iva-fx-selection.ts';
|
||
import{createProjectDossierReport}from'../../resources/js/lib/project-dossier-report.ts';
|
||
import{newCurrencyEvent}from'../../resources/js/lib/iva-currency-events.ts';
|
||
import{irpcFxSources}from'../../resources/js/lib/irpc-fx.ts';
|
||
const seed=()=>JSON.parse(fs.readFileSync(new URL('./fixtures/irpc-depreciation-preserved.json',import.meta.url),'utf8')),val=f=>f?Number(f.numerator)/Number(f.denominator):null;
|
||
function fx(direction='mzn_per_unit',rate='64'){const s={...emptyFxSelection(),nature:'commercial',submissionDate:'2026-10-26',bulletinDate:'2026-10-26',bulletinRef:'QA pressuposto sintético64; não boletim real',source:'https://www.bancomoc.mz/',sourceCurrency:'USD',direction,mean:rate};return{...s,applied:selectFx(s)};}
|
||
function setup(currency='MZN',kind='start'){
|
||
const d=seed(),b=normalBook(d),p=JSON.parse(d.business_plan_snapshot);p.plan.currency=currency;d.business_plan_snapshot=JSON.stringify(p);
|
||
b.rows=[];b.costs=undefined;b.schedules.rows=[];b.schedules.interest.amount=currency==='USD'?'7680':'120';
|
||
for(const t of Object.values(b.profit.topics)){t.state='absent';t.amount='';t.reference='QA ausência declarada no período seleccionado';}
|
||
b.base.losses=[];b.base.lossesState='absent';b.base.lossesReference='QA ausência de perdas reportadas';
|
||
b.depreciation.assets[0].accounting=currency==='USD'?'3840':'60';b.depreciation.assets[0].cost=currency==='USD'?'32000':'500';b.depreciation.assets[0].start='2026-07-01';b.depreciation.assets[0].timing='months';
|
||
b.periodsFx={...newNormalPeriodFx(),kind,from:'2026-07-01',to:'2026-12-31',planStart:'2026-01-01',reference:'QA início documentado1Julho;correspondência completa dos meses7–12 ao período do sujeito',mznMapsReference:'QA activo/quotas/juros e demais mapas reconciliados explicitamente emMZN no período',conversionReference:currency==='USD'?'QA pressuposto de simulação64MZN/USD aplicado aos meses seleccionados;sem regra fiscal anual confirmada':''};
|
||
if(currency==='USD')b.periodsFx.months=Array.from({length:6},(_,n)=>({period:n+7,currency:'USD',fxSelection:fx()}));
|
||
return{d,b};
|
||
}
|
||
test('installed7 stays current without opt-in; optional8 preserves original result and plan while selecting6 months',()=>{const d=seed(),old=normalBook(d);assert.equal(normalCurrent(d,old),true);const {d:next,b}=setup(),m=calculateNormal(next,b);assert.equal(m.version,'IRPC-normal-adjustments/8');assert.equal(val(m.accountingProfit),3120);assert.equal(val(m.depreciation.assets[0].quota),50);assert.equal(val(m.taxableProfit),3130);assert.equal(val(m.taxableBase),3130);assert.equal(val(m.colecta),1001.6);assert.equal(m.assessment.period.from,'2026-07-01');assert.equal(m.assessment.finalBalance.payable,null);assert.equal(JSON.parse(next.business_plan_snapshot).plan.periods,12);});
|
||
test('monthly conversion reuses delivered selection and direction; originalUSD and assumptions remain reportable',()=>{const{d,b}=setup('USD'),m=calculateNormal(d,b);assert.equal(val(m.accountingProfit),199680);assert.equal(val(m.depreciation.adjustment),640);assert.equal(val(m.taxableProfit),200320);assert.equal(val(m.colecta),64102.4);assert.equal(m.periodsFx.originalCurrency,'USD');assert.equal(m.periodsFx.rows[0].evidence.column,'mean');assert.match(m.periodsFx.rule,/simulação/);const inv=setup('USD');for(const e of inv.b.periodsFx.months)e.fxSelection=fx('units_per_mzn','0.015625');assert.equal(val(calculateNormal(inv.d,inv.b).colecta),64102.4);});
|
||
test('annual plan in another currency and shorterplan aligned to July both reconcile without annualising',()=>{const a=setup('USD');a.b.periodsFx.kind='annual';a.b.periodsFx.from='2026-01-01';a.b.periodsFx.months=Array.from({length:12},(_,n)=>({period:n+1,currency:'USD',fxSelection:fx()}));a.b.schedules.interest.amount='15360';a.b.depreciation.assets[0].accounting='7680';a.b.depreciation.assets[0].start='2026-01-01';assert.equal(val(calculateNormal(a.d,a.b).colecta),128204.8);const{d,b}=setup();const p=JSON.parse(d.business_plan_snapshot);p.plan.periods=6;p.plan.assetDepreciation='60';p.plan.depreciationLifeMonths=6;d.business_plan_snapshot=JSON.stringify(p);b.periodsFx.planStart='2026-07-01';assert.equal(val(calculateNormal(d,b).colecta),1001.6);});
|
||
test('cessation and same-year start/end use documented dates; unsupportedpartial months and contradictions remain pending',()=>{const{d,b}=setup();b.periodsFx.kind='cessation';b.periodsFx.from='2026-01-01';b.periodsFx.to='2026-06-30';b.depreciation.assets[0].start='2026-01-01';assert.equal(val(calculateNormal(d,b).colecta),1001.6);b.periodsFx.kind='same_year';b.periodsFx.from='2026-04-01';b.periodsFx.to='2026-09-30';assert.equal(val(calculateNormal(d,b).colecta),1001.6);for(const patch of[{from:'2026-07-15'},{to:'2026-12-30'},{planStart:''},{from:'2026-01-01',kind:'start'},{to:'2026-02-31'},{reference:''},{mznMapsReference:''}]){const x=setup();Object.assign(x.b.periodsFx,patch);assert.equal(calculateNormal(x.d,x.b).colecta,null);}});
|
||
test('missingmonth selection,sourcecurrency mismatch and editedapplied rate never become defaultFX',()=>{for(const mutate of[i=>i.months.pop(),i=>i.months[0].currency='EUR',i=>i.months[0].fxSelection.mean='65',i=>i.conversionReference='',i=>i.months.push({...i.months[0],period:1})]){const{d,b}=setup('USD');mutate(b.periodsFx);const m=calculateNormal(d,b);assert.equal(m.accountingProfit,null);assert.equal(m.colecta,null);assert.ok(m.periodsFx.rows.some(r=>r.value));}});
|
||
test('fractionalcent conversion blocks dependenttotals while retaining exact source and otherknownmonths',()=>{const{d,b}=setup('USD');b.periodsFx.months[0].fxSelection=fx('mzn_per_unit','0.33333333');const m=calculateNormal(d,b);assert.equal(m.colecta,null);assert.ok(m.periodsFx.pending.some(s=>s.includes('fracção de cêntimo')));assert.equal(m.periodsFx.rows[0].originalCurrency,'USD');});
|
||
test('shortperiod priorloss expiry and annualcaps stay explicitlypending; no change to independent arithmetic',()=>{const x=setup();x.b.base=structuredClone(normalBook(seed()).base);const m=calculateNormal(x.d,x.b);assert.equal(val(m.taxableProfit),3130);assert.equal(m.taxableBase,null);assert.ok(m.base.pending.some(s=>s.includes('41(5)')));const y=setup();y.b.costs=structuredClone(normalBook(seed()).costs);assert.equal(calculateNormal(y.d,y.b).colecta,null);assert.ok(calculateNormal(y.d,y.b).costs.pending.some(s=>s.includes('Limite anual neste período curto')));y.b.costs=undefined;y.b.depreciation.assets[0].timing='annual';assert.equal(calculateNormal(y.d,y.b).colecta,null);});
|
||
test('save/recovery/report preserves7 history and sourceplan; changed period makes8 historical',()=>{const{d,b}=setup('USD'),original=d.business_plan_snapshot,old=structuredClone(b.result);d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T10:00:00+02:00'));const 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,original);assert.match(normalHtml(d),/64102.40/);assert.match(normalHtml(d),/regra fiscal anual não confirmada/);assert.match(JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance'])})),/USD/);back.periodsFx.reference+='changed';assert.equal(normalCurrent(d,back),false);});
|
||
test('newcases prepare privateAPI persistence and dossiers including insufficientinformation',()=>{const cases={short:setup(),fx:setup('USD'),missing:setup('USD'),loss:setup()};cases.missing.b.periodsFx.months.pop();cases.loss.b.base=structuredClone(normalBook(seed()).base);fs.mkdirSync('outputs/irpc-l6-periods-fx',{recursive:true});for(const[name,{d,b}]of Object.entries(cases)){d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b,'2026-10-26T10:00:00+02:00'));fs.writeFileSync('outputs/irpc-l6-periods-fx/'+name+'.json',JSON.stringify(d));fs.writeFileSync('outputs/irpc-l6-periods-fx/'+name+'.html',normalHtml(d));}});
|
||
test('different monthrates require MZN map reconciliation; UI source uses converted subset without relabellingUSD credit balances',()=>{const{d,b}=setup('USD');b.periodsFx.months[0].fxSelection=fx('mzn_per_unit','65');assert.equal(calculateNormal(d,b).colecta,null);b.depreciation.assets[0].accounting='3850';b.schedules.interest.amount='7700';assert.equal(val(calculateNormal(d,b).colecta),64272);d.modelo_a_snapshot=saveNormalBook(d,b);const source=normalFinancialSource(d);assert.equal(source.currency,'MZN');assert.equal(source.periods,6);assert.equal(source.accountingLines.find(l=>l.code==='depreciation').total,'3850.00');assert.deepEqual(source.cashSchedule,[]);});
|
||
test('noncalendar annual mapping cannot bypass existing authorised period and unsupportedshortdates stay pending',()=>{const{d,b}=setup();b.periodsFx.kind='annual';b.periodsFx.from='2025-07-01';b.periodsFx.to='2026-06-30';b.periodsFx.planStart='2025-07-01';assert.equal(calculateNormal(d,b).colecta,null);assert.ok(calculateNormal(d,b).pending.some(s=>s.includes('autorizado/comunicado')));});
|
||
test('reusedFX link invalidates current result when existing source changes and never changes historical result',()=>{const{d,b}=setup('USD'),snapshot=JSON.parse(d.modelo_a_snapshot);snapshot.currencyEvents??={};snapshot.currencyEvents[d.context_opportunity]={version:1,rows:[{...newCurrencyEvent('fx-shared','MZN'),sourceCurrency:'USD',fxSelection:fx()}]};d.modelo_a_snapshot=JSON.stringify(snapshot);const linked=irpcFxSources(d)[0];assert.ok(linked);b.periodsFx.months[0].fxLink={id:linked.id,fingerprint:linked.fingerprint};b.periodsFx.months[0].fxSelection=linked.selection;d.modelo_a_snapshot=saveNormalBook(d,recordNormal(d,b));const book=normalBook(d),old=structuredClone(book.result);assert.equal(normalCurrent(d,book),true);const changed=JSON.parse(d.modelo_a_snapshot);changed.currencyEvents[d.context_opportunity].rows[0].description='New source context';d.modelo_a_snapshot=JSON.stringify(changed);assert.equal(normalCurrent(d,normalBook(d)),false);assert.deepEqual(normalBook(d).result,old);assert.equal(calculateNormal(d,normalBook(d)).colecta,null);});
|
||
|
||
test('known nondepreciable asset in shortperiod keeps full addback without asking irrelevant timing',()=>{const{d,b}=setup();Object.assign(b.depreciation.assets[0],{category:'nondepreciable',timing:'',method:''});const m=calculateNormal(d,b);assert.equal(val(m.depreciation.adjustment),60);assert.equal(val(m.colecta),1017.6);});
|