import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { directExportTreatments } from '../../resources/js/lib/iva-export-operations.ts'; import { newSalesOperation, evaluateSalesOperation, recordSales, saveSalesBook, salesBook, salesCurrent } from '../../resources/js/lib/iva-operations.ts'; import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts'; const frozen=JSON.parse(fs.readFileSync(new URL('./fixtures/iva-g3-inputs.json',import.meta.url),'utf8')); function row(rule,index=0){return {...newSalesOperation(`g4a-${index}`,'2026-11','MZN'),description:`SYNTHETIC QA — ${rule.label}`,establishment:'SYNTHETIC QA estabelecimento',amount:'100',basis:index%2?'included':'net',treatment:rule.id,confirmed:rule.conditions.map((_,i)=>i),evidence:'SYNTHETIC QA — intervenientes,origem/destino e ligação descritos como pressupostos da projecção; não certificação documental'};} function fixture(){const draft={...structuredClone(frozen),context_opportunity:'opportunity:323',project_title:'SYNTHETIC QA — G4A / Exportações directas'};const book=recordSales(draft,{currency:'MZN',rows:directExportTreatments.map(row),periods:[{period:'2026-11',complete:true}]});draft.modelo_a_snapshot=saveSalesBook(draft,book);return {draft,book};} test('five autonomous article13 rules classify field05 only after their own facts and evidence are determined',()=>{ assert.equal(directExportTreatments.length,5); for(const rule of directExportTreatments){const input=row(rule),result=evaluateSalesOperation(input,'MZN');assert.equal(result.field,'05');assert.equal(result.net,'100.00');assert.equal(result.vat,'0.00');assert.equal(result.gross,'100.00');assert.match(result.source,/13\(1\)/); for(const missing of input.confirmed){const r=evaluateSalesOperation({...input,confirmed:input.confirmed.filter(i=>i!==missing)},'MZN');assert.equal(r.status,'indeterminate');assert.equal(r.vat,null);} for(const patch of [{amount:''},{basis:''},{evidence:''},{description:''},{establishment:''},{sourceCurrency:'USD'},{period:'2025-11'}])assert.equal(evaluateSalesOperation({...input,...patch},'MZN').vat,null); assert.equal(evaluateSalesOperation({...input,amount:'0'},'MZN').net,'0.00'); } assert.match(directExportTreatments[0].conditions[1],/Caminhos de Ferro de Moçambique/); assert.match(directExportTreatments[1].conditions[2],/nacionalidade/); assert.match(directExportTreatments[2].conditions[3],/não estabelecido/); assert.match(directExportTreatments[3].conditions[2],/Banco de Moçambique/); assert.match(directExportTreatments[4].conditions[2],/afectação/); }); test('missing evidence leaves period indeterminate and recalculation preserves the previous result',()=>{ const {draft,book}=fixture();const next=recordSales(draft,{...book,rows:book.rows.map((r,i)=>i===2?{...r,evidence:''}:r)});assert.equal(next.result.periods[0].fields['05'],null);assert.deepEqual(next.history.at(-1),book.result); assert.equal(book.result.periods[0].fields['05'],'500.00');assert.equal(book.result.periods[0].fields['01'],'0.00'); }); test('snapshot/report retain context and sources while all14 delivered books stay byte-equivalent and current',()=>{ const {draft,book}=fixture();assert.equal(salesCurrent(draft,book),true);assert.deepEqual(salesBook(JSON.parse(JSON.stringify(draft))),JSON.parse(JSON.stringify(book))); const before=JSON.parse(frozen.modelo_a_snapshot).salesOperations,after=JSON.parse(draft.modelo_a_snapshot).salesOperations;assert.equal(Object.keys(before).length,14); for(const [context,original]of Object.entries(before)){assert.deepEqual(after[context],original);assert.equal(salesCurrent({...draft,context_opportunity:context},after[context]),true);} const model=createProjectDossierReport(draft,{selectedSections:new Set(['finance']),version:'QA G4A'});for(const value of ['500.00','opportunity:323','Manica','2026-11','MZN','13(1)(o)','13(1)(p)','13(1)(q)','13(1)(r)','13(1)(t)'])assert.ok(JSON.stringify(model).includes(value),value); if(process.env.IVA_G4A_CALCULATED)fs.writeFileSync(process.env.IVA_G4A_CALCULATED,JSON.stringify(draft)); });