Files
Investidor-V2/tests/frontend/iva-regularizations.test.mjs
T

67 lines
11 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 {newRegularization,evaluateRegularization,recordRegularizations,saveRegularizationBook,regularizationBook,regularizationFields,regularizationsCurrent,frozenAssetRows} from '../../resources/js/lib/iva-regularizations.ts';
import {acquisitionBook,recordAcquisitions,saveAcquisitionBook,acquisitionCurrent,acquisitionReportRows,readAcquisitionStore} from '../../resources/js/lib/iva-acquisitions.ts';
import {recordSales,saveSalesBook} from '../../resources/js/lib/iva-operations.ts';
const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l2e-preserved.json',import.meta.url),'utf8'));
export function regularizationFixture(){
const draft=structuredClone(frozen);draft.context_opportunity='opportunity:370';draft.project_title='SYNTHETIC QA — L2-E activos e regularizações';
const source=structuredClone(acquisitionBook(draft).result);
const annual={...newRegularization('annual-2026','annual',source),numerator:'2',denominator:'3',reference:'SYNTHETIC QA contas anuais2026',reason:'SYNTHETIC QA âmbito anual da entidade',facts:{scope:'yes',recorded:'yes',annual:'yes',exclusions:'yes'}};
const correction={...newRegularization('credit-note','purchase_correction',source),rowIds:['actual'],receivedDate:'2026-12-15',correctedDeductible:'4',reference:'SYNTHETIC QA nota de créditoNC1',reason:'SYNTHETIC QA redução da aquisição',facts:{scope:'yes',recorded:'yes',supplierCorrection:'yes',rightUnchanged:'yes'}};
return{draft,annual,correction,book:{version:1,events:[annual,correction],completePeriods:['2026-12']}};
}
test('annual adjustment uses frozen C-limited input and actually deducted D,ceil final ratio and both signs',()=>{
const{draft,annual}=regularizationFixture();let r=evaluateRegularization(draft,annual);assert.equal(r.status,'determined');assert.deepEqual(r.rawRatio,{numerator:'2',denominator:'3'});assert.deepEqual(r.ratio,{numerator:'67',denominator:'100'});assert.deepEqual(r.delta,{numerator:'132',denominator:'25'});assert.match(r.reason,/campo12/);
r=evaluateRegularization(draft,{...annual,numerator:'1',denominator:'2'});assert.deepEqual(r.delta,{numerator:'-4',denominator:'25'});assert.match(r.reason,/campo13/);
for(const patch of [{period:'2026-11'},{denominator:'0'},{numerator:'4'},{reference:''},{rowIds:['actual']},{rowIds:['missing']},{facts:{...annual.facts,annual:''}},{facts:{...annual.facts,recorded:''}}])assert.equal(evaluateRegularization(draft,{...annual,...patch}).delta,null,JSON.stringify(patch));
const altered=structuredClone(draft);const book=acquisitionBook(altered);book.rows[1].d.numerator='3';altered.modelo_a_snapshot=saveAcquisitionBook(altered,book);assert.deepEqual(evaluateRegularization(altered,annual).delta,{numerator:'132',denominator:'25'},'current edits never recalculate historical deduction');
const fabricated=structuredClone(annual);fabricated.source.rows[1].deductibleExact.numerator='0';assert.equal(evaluateRegularization(draft,fabricated).delta,null);
});
test('credit note keeps exact historical fraction,receipt deadline and source conditions;unsupported increase stays unknown',()=>{
const{draft,correction}=regularizationFixture();const r=evaluateRegularization(draft,correction);assert.deepEqual(r.delta,{numerator:'-4',denominator:'3'});assert.match(r.reason,/campo13/);
for(const patch of [{period:'2026-11'},{receivedDate:'2026-02-30'},{receivedDate:'2026-10-10'},{correctedDeductible:'6'},{correctedDeductible:''},{facts:{...correction.facts,supplierCorrection:''}},{rowIds:['actual','diesel']}])assert.equal(evaluateRegularization(draft,{...correction,...patch}).delta,null,JSON.stringify(patch));
assert.deepEqual(evaluateRegularization(draft,{...correction,correctedDeductible:'0'}).delta,{numerator:'-16',denominator:'3'});
});
test('F uses exact 12/13 once,requires coverage and detects stale E without overwriting D history or other opportunities',()=>{
let{draft,book}=regularizationFixture();const before=JSON.parse(draft.modelo_a_snapshot);book=recordRegularizations(draft,book);draft.modelo_a_snapshot=saveRegularizationBook(draft,book);
const acquired=acquisitionBook(draft);assert.equal(acquisitionCurrent(draft,acquired),false);assert.throws(()=>recordAcquisitions(draft,acquired),/ausência12\/13/);
acquired.periods[0].absent=acquired.periods[0].absent.filter(f=>!['12','13'].includes(f));const adjusted=recordAcquisitions(draft,acquired);draft.modelo_a_snapshot=saveAcquisitionBook(draft,adjusted);
const p=adjusted.result.periods[0];assert.deepEqual(['12','13','15','16','17','18'].map(f=>p.fields[f]),['5.28','1.333333','29.44','161.333333','131.893333','0.00']);assert.deepEqual(p.exactFields['13'],{numerator:'4',denominator:'3'});assert.ok(acquisitionCurrent(draft,adjusted));assert.ok(regularizationsCurrent(draft,book));
assert.deepEqual(adjusted.history.at(-1),before.books['opportunity:370'].result);const after=JSON.parse(draft.modelo_a_snapshot);for(const[k,map]of Object.entries(before)){if(k==='version')continue;for(const[id,v]of Object.entries(map)){if(k==='books'&&id==='opportunity:370')continue;assert.deepEqual(after[k][id],v,k+id);}}
for(const id of ['opportunity:368','opportunity:369','opportunity:371'])assert.ok(acquisitionCurrent({...draft,context_opportunity:id},after.books[id]));
const incomplete={...book,completePeriods:[]};assert.equal(regularizationFields({...draft,modelo_a_snapshot:saveRegularizationBook(draft,incomplete)},'2026-12').fields['12'],null);
const divergent=structuredClone(adjusted);divergent.rows[1].d.numerator='2';assert.equal(recordAcquisitions(draft,divergent).result.periods[0].fields['15'],null,'changed original deduction must be reconciled before F');
const changed=structuredClone(book);changed.events[0].numerator='1';draft.modelo_a_snapshot=saveRegularizationBook(draft,changed);assert.equal(acquisitionCurrent(draft,adjusted),false);assert.equal(regularizationFields(draft,'2026-12').fields['13'],null);assert.deepEqual(changed.result,book.result);
});
test('F retains reduced-output uncertainty even when only E events remain in the current period book',()=>{
let{draft,book}=regularizationFixture();book=recordRegularizations(draft,book);draft.modelo_a_snapshot=saveRegularizationBook(draft,book);const acquisitions={...acquisitionBook(draft),rows:[],periods:[{period:'2026-12',complete:true,importsComplete:true,absent:[]}]};const first=recordAcquisitions(draft,acquisitions);draft.modelo_a_snapshot=saveAcquisitionBook(draft,first);assert.equal(first.result.periods[0].fields['15'],'5.28');
const s=JSON.parse(draft.modelo_a_snapshot);s.regulatedReducedBases['opportunity:370']=structuredClone(s.regulatedReducedBases['opportunity:371']);draft.modelo_a_snapshot=JSON.stringify(s);assert.equal(acquisitionCurrent(draft,first),false);const result=recordAcquisitions(draft,first).result;assert.equal(result.periods[0].fields['15'],'5.28');for(const f of ['14','16','17','18'])assert.equal(result.periods[0].fields[f],null,f);
});
test('annual adjustment composes C exclusion before D exactly once,including a projected investment asset',()=>{
let{draft}=regularizationFixture();let b=acquisitionBook(draft);const diesel=structuredClone(b.rows[2]);diesel.id='diesel-pro-rata';diesel.field='08';diesel.documentRef='SYNTHETIC QA activo gasóleo';diesel.d.method='pro_rata';
b=recordAcquisitions(draft,{...b,rows:[diesel],result:undefined,history:undefined});draft.modelo_a_snapshot=saveAcquisitionBook(draft,b);const e={...newRegularization('annual-diesel','annual',b.result),numerator:'2',denominator:'3',reference:'SYNTHETIC QA anual',reason:'SYNTHETIC QA âmbito',facts:{scope:'yes',recorded:'yes',annual:'yes',exclusions:'yes'}};
assert.deepEqual(b.result.rows[0].allocationCalculation.before,{numerator:'8',denominator:'1'});assert.deepEqual(evaluateRegularization(draft,e).delta,{numerator:'66',denominator:'25'});
});
test('deduplication includes original document/line across revisions and rejects unsequenced annual plus credit note',()=>{
const{draft,book,annual,correction}=regularizationFixture();assert.throws(()=>recordRegularizations(draft,{...book,events:[annual,{...annual,id:'duplicate'}]}),/Mesma aquisição/);
assert.throws(()=>recordRegularizations(draft,{...book,events:[correction,{...correction,id:'other'}]}),/Mesma aquisição/);
assert.throws(()=>recordRegularizations(draft,{...book,events:[annual,{...correction,rowIds:['pro-rata']}]}),/sequencial/);
});
test('investment imports retain customs field11 while entering the frozen asset control register',()=>{
const{draft}=regularizationFixture();let b=acquisitionBook(draft);b.rows[3].investmentAsset=true;b=recordAcquisitions(draft,b);draft.modelo_a_snapshot=saveAcquisitionBook(draft,b);assert.equal(b.result.periods[0].fields['11'],'10.72');const register=JSON.stringify(frozenAssetRows(draft));assert.match(register,/import-due/);assert.match(register,/268\/25/);assert.equal(b.rows[3].field,'11');
});
test('unrecoverable E source or fraction is visible and a missing calculated event never turns into zero',()=>{
const{draft,book}=regularizationFixture();const saved=recordRegularizations(draft,book);const incomplete=structuredClone(saved);incomplete.result.events=[];const altered={...draft,modelo_a_snapshot:saveRegularizationBook(draft,incomplete)};assert.equal(regularizationFields(altered,'2026-12').fields['12'],null);
for(const mutate of [b=>b.events[0].source={},b=>b.result.events[0].delta.denominator='0',b=>b.events[0].facts.scope='maybe']){const bad=structuredClone(saved);mutate(bad);assert.throws(()=>readAcquisitionStore(saveRegularizationBook(draft,bad)),/Regulariza|regulariza/);}
});
test('asset register reuses frozen acquisition values and E save/recovery/report preserves source,history and prior maps',()=>{
let{draft,book}=regularizationFixture();book=recordRegularizations(draft,book);const first=structuredClone(book.result);book=recordRegularizations(draft,book);assert.deepEqual(book.history,[first]);draft.modelo_a_snapshot=saveRegularizationBook(draft,book);
let acquired=acquisitionBook(draft);acquired.periods[0].absent=acquired.periods[0].absent.filter(f=>!['12','13'].includes(f));acquired=recordAcquisitions(draft,acquired);draft.modelo_a_snapshot=saveAcquisitionBook(draft,acquired);
const assetDraft={...draft,context_opportunity:'opportunity:372'};const s=JSON.parse(draft.modelo_a_snapshot);assetDraft.modelo_a_snapshot=saveSalesBook(assetDraft,recordSales(assetDraft,structuredClone(s.salesOperations['opportunity:370'])));
const asset=structuredClone(acquired.rows[0]);asset.id='asset-control';asset.field='08';asset.description='SYNTHETIC QA activo de investimento';asset.documentRef='SYNTHETIC QA activo1';
const assetBook=recordAcquisitions(assetDraft,{currency:'MZN',taxpayerRegistered:'yes',rows:[asset],periods:[{period:'2026-12',complete:true,importsComplete:true,absent:['12','13']}]});assetDraft.modelo_a_snapshot=saveAcquisitionBook(assetDraft,assetBook);
const report=JSON.stringify(frozenAssetRows(assetDraft));for(const token of ['activo1','2026-12-10','16/3','RCIVA27','IVA suportado16'])assert.ok(report.includes(token),token);
const recovered=JSON.parse(JSON.stringify({...assetDraft,context_opportunity:'opportunity:370'}));assert.deepEqual(regularizationBook(recovered),book);const text=JSON.stringify(acquisitionReportRows(recovered));for(const token of ['Regularização annual-2026','5.28','4/3','131.893333','Pressuposto','22(4–8)'])assert.ok(text.includes(token),token);
if(process.env.IVA_L2E_CALCULATED)fs.writeFileSync(process.env.IVA_L2E_CALCULATED,JSON.stringify(recovered));
});