62 lines
11 KiB
JavaScript
62 lines
11 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} from '../../resources/js/lib/iva-acquisitions.ts';
|
|
import {closureBook,closureCurrent,closureSources,closureReportRows,recordClosure} from '../../resources/js/lib/iva-closure.ts';
|
|
import {newSubstitution,previousDeclarations,substitutionBook,recordSubstitution,saveSubstitutionBook,substitutionCurrent,substitutionReportRows} from '../../resources/js/lib/iva-substitution.ts';
|
|
const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l3c-preserved.json',import.meta.url),'utf8'));
|
|
const old=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-l3ab-preserved.json',import.meta.url),'utf8'));
|
|
function fixture(){
|
|
const d=structuredClone(frozen),base=JSON.parse(old.modelo_a_snapshot),snap=JSON.parse(d.modelo_a_snapshot);for(const[k,v]of Object.entries(base))if(k!=='version')snap[k]={...v,...snap[k]};d.modelo_a_snapshot=JSON.stringify(snap);
|
|
const sales=snap.salesOperations['opportunity:373'];sales.rows[2].amount='1375';d.modelo_a_snapshot=saveSalesBook(d,recordSales(d,sales));d.modelo_a_snapshot=saveAcquisitionBook(d,recordAcquisitions(d,acquisitionBook(d),'2026-10-25T12:30:00.000Z'));
|
|
const prior=previousDeclarations(d).find(p=>p.period==='2026-03'),source=closureSources(d).filter(s=>s.period==='2026-03').at(-1);
|
|
const row={...newSubstitution('sub-Mar'),prior,source,priorReference:'SYNTHETIC QA declaraçãoMarço',priorDate:'2026-04-15',reference:'SYNTHETIC QA substituiçãoMarço',date:'2026-05-10',reason:'SYNTHETIC QA operação de Março corrigida',interest:'documented',interestAmount:'1',interestRef:'SYNTHETIC QA juros adicionais identificados',payments:[{id:'payment2',reference:'SYNTHETIC QA reciboP2',date:'2026-05-11',amount:'20'}]};
|
|
return{d,b:{version:1,rows:[row]}};
|
|
}
|
|
const value=(r,f)=>r.fields[f]&&`${r.fields[f].numerator}/${r.fields[f].denominator}`;
|
|
function alternative(d,debit,credit){const s=JSON.parse(d.modelo_a_snapshot),calc=structuredClone(s.books['opportunity:373'].result);calc.revision+=10;calc.calculatedAt+='-unit';const p=calc.periods.find(p=>p.period==='2026-03');p.fields['17']=String(debit);p.fields['18']=String(credit);p.exactFields['17']={numerator:String(debit),denominator:'1'};p.exactFields['18']={numerator:String(credit),denominator:'1'};s.books['opportunity:373'].history.push(calc);d.modelo_a_snapshot=JSON.stringify(s);return closureSources(d).find(p=>p.revision===calc.revision&&p.period==='2026-03');}
|
|
test('real corrected L2 version gives additional60 plus1;original72/30/42 and credits remain unchanged',()=>{
|
|
const{d,b}=fixture(),before=closureBook(d),r=recordSubstitution(d,b).result.rows[0];assert.equal(r.status,'determined',r.reason);assert.equal(value(r,'21'),'60/1');assert.equal(value(r,'22'),'60/1');assert.equal(value(r,'24'),'61/1');assert.deepEqual(r.paid,{numerator:'20',denominator:'1'});assert.deepEqual(r.outstanding,{numerator:'41',denominator:'1'});assert.deepEqual(r.totalOutstanding,{numerator:'83',denominator:'1'});assert.deepEqual(r.totalPaid,{numerator:'50',denominator:'1'});for(const f of ['19','20','25','26','27'])assert.equal(r.fields[f],null);
|
|
d.modelo_a_snapshot=saveSubstitutionBook(d,recordSubstitution(d,b));assert.deepEqual(closureBook(d),before);assert.ok(closureCurrent(d,before));assert.match(closureReportRows(d).map(r=>r.join(' ')).join(' '),/Calculado24:72\/1;pagamentos registados:30\/1;por liquidar:42\/1/);
|
|
});
|
|
test('quadro9 four sign cases use exact difference;credit waits for communication and never offsets original debt',()=>{
|
|
for(const [priorKind,actual17,actual18,expected]of [['debit',220,0,'60/1'],['credit',20,0,'120/1'],['credit',0,80,'20/1'],['debit',0,40,'-200/1']]){
|
|
const{d,b}=fixture();if(priorKind==='credit'){const jan=previousDeclarations(d).find(p=>p.period==='2026-01');b.rows[0].prior={...jan};b.rows[0].prior.period='2026-03';// use an actual frozen credit-origin version in March below
|
|
const snap=JSON.parse(d.modelo_a_snapshot),ab=snap.ivaClosures['opportunity:373'],calc=structuredClone(ab.result),r=calc.rows[2];r.fields['17']={numerator:'0',denominator:'1'};r.fields['18']={numerator:'100',denominator:'1'};r.id='unit-credit-Mar';calc.inputs[2].id=r.id;ab.history.push(calc);d.modelo_a_snapshot=JSON.stringify(snap);b.rows[0].prior=previousDeclarations(d).find(p=>p.id==='unit-credit-Mar');
|
|
}
|
|
b.rows[0].source=alternative(d,actual17,actual18);const r=recordSubstitution(d,b).result.rows[0];assert.equal(r.status,'determined',r.reason);assert.equal(value(r,'21'),expected);if(expected.startsWith('-')){assert.equal(value(r,'22'),'0/1');assert.deepEqual(r.pendingCredit,{numerator:'200',denominator:'1'});assert.equal(value(r,'26'),null);assert.equal(r.totalOutstanding.numerator,'42');}
|
|
}
|
|
});
|
|
test('missing facts,wrong period,currency,unknown source and interest never produce presumed zero',()=>{
|
|
const{d,b}=fixture();for(const patch of [{prior:null},{source:null},{priorReference:''},{priorDate:''},{date:'2026-04-01'},{reference:''},{reason:''},{interest:''}]){const bad=structuredClone(b);Object.assign(bad.rows[0],patch);assert.equal(recordSubstitution(d,bad).result.rows[0].status,'indeterminate',JSON.stringify(patch));}
|
|
const bad=structuredClone(b);bad.rows[0].source=closureSources(d).find(s=>s.period==='2026-02');assert.equal(recordSubstitution(d,bad).result.rows[0].fields['21'],null);assert.equal(recordSubstitution({...d,currency:'USD'},b).result.rows[0].status,'indeterminate');
|
|
});
|
|
test('old payment cannot be re-used and two substitutions cannot repeat one predecessor or payment',()=>{
|
|
const{d,b}=fixture();b.rows[0].payments[0].reference='SYNTHETIC QA reciboP1';assert.match(recordSubstitution(d,b).result.rows[0].reason,/Pagamento já/);
|
|
const f=fixture();f.b.rows.push({...structuredClone(f.b.rows[0]),id:'copy'});assert.ok(recordSubstitution(f.d,f.b).result.rows.every(r=>r.status==='indeterminate'));
|
|
const g=fixture();g.b.rows[0].payments.push({...g.b.rows[0].payments[0],id:'copy'});assert.equal(recordSubstitution(g.d,g.b).result.rows[0].paid,null);
|
|
});
|
|
test('successive substitution links frozen predecessor,charges only new difference and preserves all payments',()=>{
|
|
const{d,b}=fixture();let book=recordSubstitution(d,b,'2026-10-25T12:31:00Z');d.modelo_a_snapshot=saveSubstitutionBook(d,book);const next=structuredClone(book.rows[0]);next.id='sub-Mar-2';next.prior=previousDeclarations(d).find(p=>p.kind==='C');next.source=alternative(d,250,0);next.priorReference=next.reference;next.priorDate=next.date;next.reference='SYNTHETIC QA segunda substituição';next.date='2026-06-10';next.interest='none';next.interestRef='SYNTHETIC QA sem juros adicionais';next.payments=[];book=recordSubstitution(d,{...book,rows:[...book.rows,next]});const r=book.result.rows[1];assert.equal(r.status,'determined',r.reason);assert.equal(value(r,'21'),'30/1');assert.deepEqual(r.totalOutstanding,{numerator:'113',denominator:'1'});assert.deepEqual(r.totalPaid,{numerator:'50',denominator:'1'});assert.equal(book.history.length,1);
|
|
next.payments=[{id:'reuse',reference:'SYNTHETIC QA reciboP2',date:'2026-06-11',amount:'20'}];assert.equal(recordSubstitution(d,{...book,rows:[book.rows[0],next]}).result.rows[1].paid,null);
|
|
});
|
|
test('save/recover retains every L1/L2/AB map;report uses frozen inputs and recalculation appends history',()=>{
|
|
const{d,b}=fixture(),before=JSON.parse(d.modelo_a_snapshot);let book=recordSubstitution(d,b);d.modelo_a_snapshot=saveSubstitutionBook(d,book);assert.ok(substitutionCurrent(d,substitutionBook(d)));const stored=JSON.parse(d.modelo_a_snapshot);for(const[k,v]of Object.entries(before))assert.deepEqual(stored[k],v,k);
|
|
book.rows[0].reference='new deliberate edit';assert.equal(substitutionCurrent(d,book),false);d.modelo_a_snapshot=saveSubstitutionBook(d,book);const report=substitutionReportRows(d).map(r=>r.join(' ')).join(' ');assert.match(report,/SYNTHETIC QA substituiçãoMarço/);assert.doesNotMatch(report,/new deliberate edit/);
|
|
const oldResult=structuredClone(book.result);book=recordSubstitution(d,book);assert.deepEqual(book.history.at(-1),oldResult);d.modelo_a_snapshot=saveSubstitutionBook(d,book);assert.ok(substitutionCurrent(d,book));assert.match(substitutionReportRows(d).map(r=>r.join(' ')).join(' '),/new deliberate edit/);
|
|
});
|
|
test('fractional delta remains exact and overpayment does not generate communicated credit',()=>{
|
|
const{d,b}=fixture();const s=alternative(d,220,0),snap=JSON.parse(d.modelo_a_snapshot),calc=snap.books['opportunity:373'].history.at(-1);calc.periods[2].exactFields['17']={numerator:'661',denominator:'3'};d.modelo_a_snapshot=JSON.stringify(snap);b.rows[0].source=closureSources(d).find(x=>x.revision===s.revision&&x.period==='2026-03');b.rows[0].payments[0].amount='100';const r=recordSubstitution(d,b).result.rows[0];assert.equal(value(r,'21'),'181/3');assert.deepEqual(r.overpayment,{numerator:'116',denominator:'3'});assert.equal(r.pendingCredit.numerator,'0');assert.equal(r.fields['20'],null);
|
|
});
|
|
test('a payment already frozen in C cannot be added again in A/B;original valid A/B remains current',()=>{
|
|
const{d,b}=fixture();d.modelo_a_snapshot=saveSubstitutionBook(d,recordSubstitution(d,b));const ab=closureBook(d);assert.ok(closureCurrent(d,ab));ab.rows[2].payments.push({...b.rows[0].payments[0]});assert.equal(closureCurrent(d,ab),false);const r=recordClosure(d,ab).result.rows[2];assert.equal(r.paid,null);assert.match(r.reason,/Pagamento/);
|
|
});
|
|
test('a newly conflicting payment in recovered AB makes C stale and requires reconciliation',()=>{
|
|
const{d,b}=fixture();const book=recordSubstitution(d,b);d.modelo_a_snapshot=saveSubstitutionBook(d,book);assert.ok(substitutionCurrent(d,book));const s=JSON.parse(d.modelo_a_snapshot);s.ivaClosures['opportunity:373'].rows[2].payments.push({...b.rows[0].payments[0]});d.modelo_a_snapshot=JSON.stringify(s);assert.equal(substitutionCurrent(d,book),false);assert.equal(recordSubstitution(d,book).result.rows[0].paid,null);
|
|
});
|
|
test('a substitution cannot link back through a circular predecessor chain',()=>{
|
|
const{d,b}=fixture();let book=recordSubstitution(d,b,'2026-10-25T12:31:00Z');d.modelo_a_snapshot=saveSubstitutionBook(d,book);const child={...structuredClone(book.rows[0]),id:'child',prior:previousDeclarations(d).find(p=>p.kind==='C'),priorReference:book.rows[0].reference,priorDate:book.rows[0].date,date:'2026-06-10',reference:'child-ref',payments:[]};book=recordSubstitution(d,{...book,rows:[...book.rows,child]},'2026-10-25T12:32:00Z');d.modelo_a_snapshot=saveSubstitutionBook(d,book);book.rows[0].prior=previousDeclarations(d).find(p=>p.kind==='C'&&p.id==='child');assert.ok(recordSubstitution(d,book).result.rows.every(r=>/circular/.test(r.reason)));
|
|
});
|
|
test('export synthetic demonstrated correction for guarded private API and actual component proof',()=>{
|
|
const{d,b}=fixture();const result=recordSubstitution(d,b,'2026-10-25T12:31:00.000Z');const again=recordSubstitution(d,result,'2026-10-25T12:32:00.000Z');d.modelo_a_snapshot=saveSubstitutionBook(d,again);assert.ok(substitutionCurrent(d,again));if(process.env.IVA_L3C_CALCULATED)fs.writeFileSync(process.env.IVA_L3C_CALCULATED,JSON.stringify(d));
|
|
});
|