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 }; } void 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/); }); void 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'); }); void 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)); });