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

65 lines
12 KiB
JavaScript

import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
import {recordSales,saveSalesBook} from '../../resources/js/lib/iva-operations.ts';
import {recordAcquisitions,saveAcquisitionBook,acquisitionBook,acquisitionCurrent} from '../../resources/js/lib/iva-acquisitions.ts';
import {closureBook,closureSources,newClosure,newClosureCredit,recordClosure,saveClosureBook,adoptPreviousCredit,closureCurrent,closureReportRows} from '../../resources/js/lib/iva-closure.ts';
const preserved=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l3ab-preserved.json',import.meta.url),'utf8'));
export function closureFixture(){
const draft={...structuredClone(preserved),context_opportunity:'opportunity:373',project_title:'SYNTHETIC QA — L3-A/B fecho do IVA'};const s=JSON.parse(draft.modelo_a_snapshot),base=s.books['opportunity:370'];
const sales=['2026-01','2026-02','2026-03'].map((period,i)=>({...structuredClone(s.salesOperations['opportunity:370'].rows[0]),id:'sale-'+i,period,amount:i===1?'500':'1000',sourcePeriod:period}));
draft.modelo_a_snapshot=saveSalesBook(draft,recordSales(draft,{currency:'MZN',rows:sales,periods:sales.map(r=>({period:r.period,complete:true}))}));
const purchase={...structuredClone(base.rows[0]),id:'purchase-Jan',period:'2026-01',documentDate:'2026-01-10',documentRef:'SYNTHETIC QA aquisiçãoJaneiro',net:'1625',vat:'260',gross:'1885',d:{...structuredClone(base.rows[0].d),numerator:'1',denominator:'1'}};
const acquisitions=recordAcquisitions(draft,{currency:'MZN',taxpayerRegistered:'yes',rows:[purchase],periods:sales.map(r=>({period:r.period,complete:true,importsComplete:true,absent:['12','13']}))});draft.modelo_a_snapshot=saveAcquisitionBook(draft,acquisitions);
const make=period=>({...newClosure('close-'+period,closureSources(draft).find(s=>s.period===period),'SYNTHETIC QA entidade373'),firstDeclaration:'yes',creditsComplete:'yes',filing:'timely',submissionDate:`2026-${String(Number(period.slice(5))+1).padStart(2,'0')}-15`,filingRef:'SYNTHETIC QA entrega em prazo assumida',interest:'none',interestRef:'SYNTHETIC QA pressuposto sem juros',destination:'report'});
const jan=make('2026-01');jan.credits=[{...newClosureCredit('opening','opening'),period:'2025-12',reference:'SYNTHETIC QA saldoabertura',amount:'50'}];
let book=recordClosure(draft,{version:1,rows:[jan]});const feb=make('2026-02');feb.credits=[adoptPreviousCredit(newClosureCredit('carry-Feb','previous'),book.result.rows[0])];book=recordClosure(draft,{...book,rows:[jan,feb]});
const mar=make('2026-03');mar.credits=[adoptPreviousCredit(newClosureCredit('carry-Mar','previous'),book.result.rows[1]),{...newClosureCredit('communication','communication'),period:'2026-02',reference:'SYNTHETIC QA comunicaçãoAT20',date:'2026-03-10',amount:'20'}];mar.interest='documented';mar.interestAmount='2';mar.interestRef='SYNTHETIC QA jurosidentificados';mar.payments=[{id:'payment1',reference:'SYNTHETIC QA reciboP1',date:'2026-04-16',amount:'30'}];
book=recordClosure(draft,{...book,rows:[jan,feb,mar]});return{draft,book,make,acquisitions};
}
const amount=(r,f)=>r.fields[f]&&`${r.fields[f].numerator}/${r.fields[f].denominator}`;
test('three periods reconcile opening credit,carry,communicated credit,interest and payment without double deduction',()=>{
const{draft,book}=closureFixture();assert.deepEqual(book.result.rows.map(r=>r.status),['determined','determined','determined']);assert.deepEqual(book.result.rows.map(r=>amount(r,'26')),['150/1','70/1','0/1']);const mar=book.result.rows[2];assert.equal(amount(mar,'19'),'70/1');assert.equal(amount(mar,'20'),'20/1');assert.equal(amount(mar,'22'),'70/1');assert.equal(amount(mar,'24'),'72/1');assert.deepEqual(mar.paid,{numerator:'30',denominator:'1'});assert.deepEqual(mar.outstanding,{numerator:'42',denominator:'1'});assert.equal(mar.fields['21'],null);assert.equal(amount(mar,'27'),'0/1');
assert.ok(acquisitionCurrent(draft,acquisitionBook(draft)));assert.equal(book.history.length,2);
});
test('origin,coverage,deadline and unsupported paths remain explicit;unknown never becomes zero',()=>{
const{draft,book}=closureFixture();for(const patch of [{creditsComplete:''},{filing:''},{filing:'late'},{firstDeclaration:'no'},{entityRef:''},{submissionDate:''},{destination:'refund'}]){const changed=structuredClone(book);changed.rows[0]={...changed.rows[0],...patch};const r=recordClosure(draft,changed).result.rows;assert.equal(r[0].status,'indeterminate',JSON.stringify(patch));assert.equal(r[1].status,'indeterminate');}
const interest=structuredClone(book);interest.rows[2].interest='';const r=recordClosure(draft,interest).result.rows[2];assert.equal(amount(r,'22'),'70/1');assert.equal(r.fields['24'],null);
const future=structuredClone(book);future.rows[2].credits[1].date='2026-05-01';assert.equal(recordClosure(draft,future).result.rows[2].fields['20'],null);
const effective=structuredClone(book);effective.rows[0].origin='effective';assert.match(recordClosure(draft,effective).result.rows[0].reason,/projectados/);
});
test('duplicate documents,opening versus communication and payment references cannot create duplicate use',()=>{
const{draft,book}=closureFixture();const b=structuredClone(book);b.rows[0].credits.push({...b.rows[0].credits[0],id:'copy'});assert.equal(recordClosure(draft,b).result.rows[0].fields['19'],null);
const cross=structuredClone(book);cross.rows[0].credits.push({...cross.rows[0].credits[0],id:'copy',kind:'communication',date:'2026-01-05'});assert.equal(recordClosure(draft,cross).result.rows[0].fields['19'],null);
const payments=structuredClone(book);payments.rows[2].payments.push({...payments.rows[2].payments[0],id:'copy'});const p=recordClosure(draft,payments).result.rows[2];assert.equal(p.paid,null);assert.equal(amount(p,'26'),'0/1');assert.equal(amount(p,'24'),'72/1');
const opening=structuredClone(book);opening.rows[1].credits=[{...opening.rows[0].credits[0],id:'new-opening',period:'2026-01'}];assert.match(recordClosure(draft,opening).result.rows[1].reason,/reutilize26/);
});
test('changing a predecessor preserves historical results and requires explicit carry adoption;payment edit does not change carry',()=>{
const{draft,book}=closureFixture();const first=structuredClone(book.result);let changed=structuredClone(book);changed.rows[0].credits[0].amount='60';assert.equal(closureCurrent(draft,changed),false);changed=recordClosure(draft,changed);assert.equal(amount(changed.result.rows[0],'26'),'160/1');assert.equal(changed.result.rows[1].status,'indeterminate');assert.deepEqual(changed.history.at(-1),first);
changed.rows[1].credits[0]=adoptPreviousCredit(changed.rows[1].credits[0],changed.result.rows[0]);changed=recordClosure(draft,changed);assert.equal(amount(changed.result.rows[1],'26'),'80/1');assert.equal(changed.result.rows[2].status,'indeterminate');
changed.rows[2].credits[0]=adoptPreviousCredit(changed.rows[2].credits[0],changed.result.rows[1]);changed=recordClosure(draft,changed);assert.equal(amount(changed.result.rows[2],'22'),'60/1');
const pay=structuredClone(book);pay.rows[0].payments=[{id:'unexpected',reference:'SYNTHETIC QA pagamentoanterior',date:'2026-02-16',amount:'1'}];const v=recordClosure(draft,pay);assert.equal(v.result.rows[0].carryKey,first.rows[0].carryKey);assert.equal(v.result.rows[1].status,'determined');
});
test('overpayment remains a payment fact and does not create credit20/25/26',()=>{
const{draft,book}=closureFixture();book.rows[2].payments[0].amount='100';const r=recordClosure(draft,book).result.rows[2];assert.deepEqual(r.overpayment,{numerator:'28',denominator:'1'});assert.equal(amount(r,'20'),'20/1');assert.equal(amount(r,'25'),'0/1');assert.equal(amount(r,'26'),'0/1');
});
test('carry between periods preserves exact fractions instead of adding rounded presentation',()=>{
const{draft,book}=closureFixture();let a=acquisitionBook(draft);a.rows[0].net='3125';a.rows[0].vat='500';a.rows[0].gross='3625';a.rows[0].d.denominator='3';a=recordAcquisitions(draft,a);draft.modelo_a_snapshot=saveAcquisitionBook(draft,a);book.rows[0].source=closureSources(draft).find(s=>s.revision===a.result.revision&&s.period==='2026-01');let b=recordClosure(draft,book);assert.deepEqual(b.result.rows[0].fields['26'],{numerator:'170',denominator:'3'});b.rows[1].credits[0]=adoptPreviousCredit(b.rows[1].credits[0],b.result.rows[0]);b=recordClosure(draft,b);assert.deepEqual(b.result.rows[1].fields['22'],{numerator:'70',denominator:'3'});
});
test('recovered opportunities keep scenarios separate and expose collisions of effective scope',()=>{
const{draft,book}=closureFixture();const other=structuredClone(book);const store=JSON.parse(draft.modelo_a_snapshot);store.ivaClosures={'opportunity:other':other};draft.modelo_a_snapshot=JSON.stringify(store);assert.equal(recordClosure(draft,book).result.rows[0].status,'determined','projected scenarios do not consume each other');
book.rows[0].origin='effective';other.rows[0].origin='effective';store.ivaClosures['opportunity:other']=other;draft.modelo_a_snapshot=JSON.stringify(store);assert.match(recordClosure(draft,book).result.rows[0].reason,/noutra oportunidade/);
});
test('frozen L2 sources are reused exactly and incomplete or fabricated sources cannot close balances',()=>{
const{draft,book}=closureFixture();const b=structuredClone(book);b.rows[0].source.credit={numerator:'999',denominator:'1'};assert.equal(recordClosure(draft,b).result.rows[0].status,'indeterminate');
const removed={...draft};const s=JSON.parse(draft.modelo_a_snapshot);delete s.books['opportunity:373'].result;removed.modelo_a_snapshot=JSON.stringify(s);assert.equal(recordClosure(removed,book).result.rows[0].fields['18'],null);
assert.deepEqual(acquisitionBook(draft).result.periods.map(p=>[p.fields['17'],p.fields['18']]),[['0.00','100.00'],['80.00','0.00'],['160.00','0.00']]);
const unchanged=recordAcquisitions(draft,acquisitionBook(draft));const extraVersion={...draft,modelo_a_snapshot:saveAcquisitionBook(draft,unchanged)};assert.ok(closureCurrent(extraVersion,book),'adding an unrelated L2 version does not rewrite or stale the selected frozen results');
});
test('save recovery and report preserve every prior L1/L2 map,values and closure histories',()=>{
let{draft,book}=closureFixture();const before=JSON.parse(preserved.modelo_a_snapshot);const acquired=structuredClone(acquisitionBook(draft));draft.modelo_a_snapshot=saveClosureBook(draft,book);const recovered=JSON.parse(JSON.stringify(draft));assert.deepEqual(closureBook(recovered),book);assert.ok(closureCurrent(recovered,book));assert.ok(acquisitionCurrent(recovered,acquisitionBook(recovered)));assert.deepEqual(acquisitionBook(recovered),acquired);
const after=JSON.parse(recovered.modelo_a_snapshot);for(const[k,map]of Object.entries(before)){if(k==='version')continue;for(const[id,v]of Object.entries(map))assert.deepEqual(after[k][id],v,k+id);}
const report=JSON.stringify(closureReportRows(recovered));for(const token of ['2026-01','2026-02','2026-03','150/1','70/1','72/1','30/1','42/1','saldoabertura','comunicaçãoAT20','reciboP1','opportunity:373'])assert.ok(report.includes(token),token);
const malformed=JSON.parse(recovered.modelo_a_snapshot);malformed.ivaClosures=null;assert.throws(()=>closureBook({...recovered,modelo_a_snapshot:JSON.stringify(malformed)}),/não recuperável/);
book.rows[0].credits[0].amount='999';const edited={...recovered,modelo_a_snapshot:saveClosureBook(recovered,book)};assert.equal(closureCurrent(edited,book),false);assert.match(JSON.stringify(closureReportRows(edited)),/50/);assert.equal(book.result.inputs[0].credits[0].amount,'50');
if(process.env.IVA_L3AB_CALCULATED)fs.writeFileSync(process.env.IVA_L3AB_CALCULATED,JSON.stringify(recovered));
});