67 lines
11 KiB
JavaScript
67 lines
11 KiB
JavaScript
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));
|
||
});
|