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

42 lines
8.0 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {newCompositionBase,newCompositionPart,switchCompositionPart,compositionTreatments,compositionConditions,evaluateComposition,recordCompositions,saveCompositionBook,compositionBook,compositionCurrent,compositionReportRows} from '../../resources/js/lib/iva-composition-base.ts';
import {noncashBaseCurrent} from '../../resources/js/lib/iva-noncash-base.ts';
import {fuelBaseCurrent} from '../../resources/js/lib/iva-fuel-base.ts';
import {createProjectDossierReport} from '../../resources/js/lib/project-dossier-report.ts';
const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-g7f-preserved-inputs.json',import.meta.url),'utf8'));
const part=(treatment='tax_component',id=treatment)=>({...newCompositionPart(id),treatment,description:'SYNTHETIC QA '+(treatment==='tax_component'?'imposição A':'transporte por conta do cliente'),amount:treatment==='tax_component'?'100.10':'50.25',included:'no',reference:'SYNTHETIC QA referência '+id,natureConfirmed:'yes',ownVatExcluded:'yes'});
const row=()=>({...newCompositionBase('g7f-operation-A','MZN'),description:'SYNTHETIC QA composição operação A',period:'2026-12',operationRef:'SYNTHETIC QA operação A',initialAmount:'1000.25',initialRef:'SYNTHETIC QA preço sem próprioIVA/exclusões15(6)consideradas',documentRef:'SYNTHETIC QA G7F documento A',documentDate:'2026-12-12',compositionRef:'SYNTHETIC QA parcelas A,B e montante inicial reconciliados,sem duplicação',evidence:'SYNTHETIC QA pressupostos expressos,não documento real',conditions:Object.fromEntries(compositionConditions.map(([key])=>[key,'yes'])),parts:compositionTreatments.map(t=>part(t.id))});
const draft=()=>({...structuredClone(frozen),context_opportunity:'opportunity:344',project_title:'SYNTHETIC QA — G7F / Composição da contraprestação'});
test('P101 operation and each tax/accessory condition required;ownIVA or wrong nature not silently included',()=>{
const input=row(),ok=evaluateComposition(input,'MZN');assert.equal(ok.baseAmount,'1150.60');assert.equal(ok.vat,null);assert.equal(ok.rate,null);
for(const[key]of compositionConditions)for(const value of ['','unknown','no']){const r=evaluateComposition({...input,conditions:{...input.conditions,[key]:value}},'MZN');assert.equal(r.status,value==='no'?'not_applicable':'indeterminate');assert.equal(r.baseAmount,null);}
for(const t of compositionTreatments)for(const key of ['natureConfirmed','ownVatExcluded'])for(const value of ['','unknown','no']){const p={...part(t.id),[key]:value},r=evaluateComposition({...input,parts:[p]},'MZN');assert.equal(r.status,value==='no'?'not_applicable':'indeterminate');assert.equal(r.baseAmount,null);assert.equal(r.vat,null);}
for(const patch of [{description:''},{operationRef:''},{initialRef:''},{documentRef:''},{documentDate:'2026-02-30'},{compositionRef:''},{evidence:''},{period:'2025-12'},{currency:'USD'}])assert.equal(evaluateComposition({...input,...patch},'MZN').baseAmount,null);
for(const patch of [{treatment:''},{description:''},{reference:''},{included:''},{included:'unknown'},{amount:''}])assert.equal(evaluateComposition({...input,parts:[{...part(),...patch}]},'MZN').baseAmount,null);
});
test('single operation base adds missing components only;alreadyincluded does not doublecount;exactdecimal and preserved edits',()=>{
const input=row();for(const[parts,expected]of [[[part('tax_component')],'1100.35'],[[part('accessory_component')],'1050.50'],[[{...part(),included:'yes'},part('accessory_component')],'1050.50'],[input.parts.map(p=>({...p,included:'yes'})),'1000.25']])assert.equal(evaluateComposition({...input,parts},'MZN').baseAmount,expected);
assert.equal(evaluateComposition({...input,initialAmount:'0',parts:[{...part(),amount:'0'}]},'MZN').baseAmount,'0.00');assert.equal(evaluateComposition({...input,initialAmount:'1.000002',parts:[{...part(),amount:'0.000001'}]},'MZN').baseAmount,'1.000003');assert.equal(evaluateComposition({...input,initialAmount:'999999999999999.999999',parts:[{...part(),amount:'999999999999999.999999'}]},'MZN').baseAmount,'1999999999999999.999998');
for(const amount of ['-1','NaN','0.0000001',''])assert.equal(evaluateComposition({...input,parts:[{...part(),amount}]},'MZN').baseAmount,null);assert.equal(evaluateComposition({...input,initialAmount:''},'MZN').baseAmount,null);
const switched=switchCompositionPart(part(),'accessory_component');assert.equal(switched.natureConfirmed,'');assert.equal(switched.amount,'100.10');assert.equal(switched.reference,part().reference);assert.equal(evaluateComposition({...input,parts:[switched]},'MZN').baseAmount,null);
assert.equal(evaluateComposition({...input,parts:[]},'MZN').baseAmount,null);assert.equal(evaluateComposition({...input,parts:[part(),part()]},'MZN').baseAmount,null);
});
test('save/recovery/history/report keep one base peroperation and all83 previous books/currentkeys unchanged',()=>{
const d=draft(),prior=JSON.parse(d.modelo_a_snapshot);assert.deepEqual(compositionBook(d),{rows:[]});assert.deepEqual(compositionReportRows(d),[]);
let book=recordCompositions(d,{rows:[{...row(),conditions:{}}]});book=recordCompositions(d,{...book,rows:[{...row(),parts:[{...part(),ownVatExcluded:'no'}]}]});book=recordCompositions(d,{...book,rows:[row()]});assert.equal(book.history.length,2);assert.ok(book.history.every(h=>h.rows.every(r=>r.baseAmount===null&&r.vat===null)));assert.ok(compositionCurrent(d,book));assert.equal(book.result.rows.length,1);assert.equal(book.result.inputs[0].parts.length,2);
for(const field of ['amount','included','reference','treatment'])assert.ok(!compositionCurrent(d,{...book,rows:book.rows.map(r=>({...r,parts:r.parts.map(p=>({...p,[field]:'changed'}))}))}));for(const patch of [{context_opportunity:'opportunity:345'},{currency:'USD'},{selection_labels:{province_id:'Maputo'}}])assert.ok(!compositionCurrent({...d,...patch},book));
d.modelo_a_snapshot=saveCompositionBook(d,book);assert.deepEqual(compositionBook(JSON.parse(JSON.stringify(d))),book);const next=JSON.parse(d.modelo_a_snapshot);const{considerationCompositions,...untouched}=next;assert.deepEqual(untouched,prior);for(const[scope,previous]of Object.entries(prior.noncashBases))assert.ok(noncashBaseCurrent({...d,context_opportunity:scope},previous));for(const[scope,previous]of Object.entries(prior.regulatedFuelBases))assert.ok(fuelBaseCurrent({...d,context_opportunity:scope},previous));
assert.equal(next.noncashBases['opportunity:343'].result.rows[1].baseAmount,'2700.75');assert.equal(next.regulatedFuelBases['opportunity:342'].result.rows[0].baseAmount,'1400.15');assert.equal(next.regulatedFuelBases['opportunity:342'].result.rows[1].baseAmount,'1500.25');assert.equal(next.importExemptions['opportunity:338'].result.rows[1].importVat,'400.00');assert.equal(next.books['opportunity:329'].result.periods[0].fields['11'],null);
const report=JSON.stringify(createProjectDossierReport(d,{selectedSections:new Set(['finance']),version:'QA G7F'}));for(const token of ['Base P101','1150.60','IVA: Indeterminado','opportunity:344','Manica','2026-12','operação A','15(5)','100.10','50.25','accessory_component','campo11'])assert.ok(report.includes(token),token);assert.equal(compositionReportRows(d).length,1);assert.deepEqual(compositionReportRows({...d,context_opportunity:'opportunity:345'}),[]);
if(process.env.IVA_G7F_CALCULATED)fs.writeFileSync(process.env.IVA_G7F_CALCULATED,JSON.stringify(d));
});
test('corrupt nested components/store cannot overwrite originals;duplicate operation ids cannot be recorded',()=>{
const d=draft(),original=d.modelo_a_snapshot;for(const patch of [{initialAmount:4},{conditions:[]},{parts:{}},{parts:[{...part(),amount:4}]},{parts:[null]}])assert.throws(()=>saveCompositionBook(d,{rows:[{...row(),...patch}]}),/originais preservados/);assert.equal(d.modelo_a_snapshot,original);assert.throws(()=>recordCompositions(d,{rows:[row(),row()]}),/distintos/);assert.throws(()=>recordCompositions(d,{rows:[]}),/distintos/);assert.throws(()=>compositionBook({...d,modelo_a_snapshot:JSON.stringify({...JSON.parse(original),considerationCompositions:[]})}),/originais preservados/);
});