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)); });