import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { exportServiceTreatments } from '../../resources/js/lib/iva-export-services.ts'; import { newSalesOperation, operationTreatments, evaluateSalesOperation, referenceExportOperation, 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-g4b-inputs.json', import.meta.url), 'utf8', ), ); const row = (rule, i = 0) => ({ ...newSalesOperation(`g4c-${i}`, '2026-12', 'MZN'), description: `SYNTHETIC QA — ${rule.label}`, establishment: 'SYNTHETIC QA estabelecimento', amount: '100', basis: i % 2 ? 'included' : 'net', treatment: rule.id, confirmed: rule.conditions.map((_, n) => n), evidence: 'SYNTHETIC QA — documento/pressuposto externo identifica bens,regime/localização,mandante e ligação do serviço;operação do intermediário localizada emMoçambique;sem certificação', }); function fixture() { const draft = { ...structuredClone(frozen), context_opportunity: 'opportunity:325', project_title: 'SYNTHETIC QA — G4C / Serviços ligados', }; const goods = { ...row( operationTreatments.find((r) => r.id === 'export_goods'), 9, ), id: 'g4c-goods', description: 'SYNTHETIC QA — mercadorias expedidas pelo vendedor', amount: '1000', }; const services = exportServiceTreatments.map(row); services[1] = referenceExportOperation(services[1], goods); services[1].confirmed = exportServiceTreatments[1].conditions.map( (_, i) => i, ); const book = recordSales(draft, { currency: 'MZN', rows: [goods, ...services], periods: [{ period: '2026-12', complete: true }], }); draft.modelo_a_snapshot = saveSalesBook(draft, book); return { draft, book }; } void test('three services require each legal condition,local service scope and evidence;no automatic classification from client or sector', () => { assert.equal(exportServiceTreatments.length, 3); assert.match( exportServiceTreatments[0].conditions[2], /admissão temporária/, ); assert.match(exportServiceTreatments[0].conditions[3], /artigo9/); assert.match(exportServiceTreatments[1].conditions[2], /artigo9/); assert.match( exportServiceTreatments[2].conditions[1], /não actuação em nome próprio/, ); for (const [i, rule] of exportServiceTreatments.entries()) { const input = row(rule, i); assert.equal(evaluateSalesOperation(input, 'MZN').field, '05'); assert.equal(evaluateSalesOperation(input, 'MZN').vat, '0.00'); for (const missing of input.confirmed) assert.equal( evaluateSalesOperation( { ...input, confirmed: input.confirmed.filter((n) => n !== missing), }, 'MZN', ).vat, null, ); for (const patch of [ { amount: '' }, { evidence: '' }, { sourceCurrency: 'USD' }, { period: '2027-01' }, { basis: '' }, ]) assert.equal( evaluateSalesOperation({ ...input, ...patch }, 'MZN').vat, null, ); } }); void test('reference reuse keeps service price/identity/evidence and requires deliberate confirmation of new relationship', () => { const service = row(exportServiceTreatments[1]), related = { ...row( operationTreatments.find((r) => r.id === 'export_goods'), 8, ), amount: '1000', }, linked = referenceExportOperation(service, related); assert.deepEqual(linked.relatedExport, { id: related.id, description: related.description, treatment: related.treatment, }); assert.deepEqual(linked.confirmed, []); for (const field of [ 'id', 'amount', 'basis', 'period', 'description', 'evidence', 'establishment', 'sourceAmount', 'sourcePeriod', 'sourceCurrency', 'source', ]) assert.deepEqual(linked[field], service[field], field); assert.equal(linked.amount, '100'); assert.equal(evaluateSalesOperation(linked, 'MZN').vat, null); assert.throws(() => referenceExportOperation(service, service)); assert.throws(() => referenceExportOperation(related, service)); }); void test('deleted/changed linked operation blocks current calculation,keeps history and requires explicit reference update', () => { const { draft, book } = fixture(); assert.equal(book.result.periods[0].fields['05'], '1300.00'); for (const rows of [ book.rows.filter((r) => r.id !== 'g4c-goods'), book.rows.map((r) => r.id === 'g4c-goods' ? { ...r, description: 'Changed goods' } : r, ), ]) { const changed = recordSales(draft, { ...book, rows }); assert.equal( changed.result.rows.find((r) => r.id === 'g4c-1').vat, null, ); assert.match( changed.result.rows.find((r) => r.id === 'g4c-1').reason, /removida ou alterada/, ); assert.equal(changed.result.periods[0].fields['05'], null); assert.deepEqual(changed.history.at(-1), book.result); assert.equal(changed.rows.find((r) => r.id === 'g4c-1').amount, '100'); } const changedGoods = { ...book.rows[0], description: 'Reviewed goods' }; const linked = referenceExportOperation(book.rows[2], changedGoods); linked.confirmed = exportServiceTreatments[1].conditions.map((_, i) => i); const next = recordSales(draft, { ...book, rows: [changedGoods, book.rows[1], linked, book.rows[3]], }); assert.equal(next.result.periods[0].fields['05'], '1300.00'); const invalid = recordSales(draft, { ...book, rows: book.rows.map((r) => r.id === 'g4c-1' ? { ...r, relatedExport: { id: r.id, description: r.description, treatment: r.treatment, }, } : r, ), }); assert.equal(invalid.result.periods[0].fields['05'], null); }); void test('stored service reference survives report/recovery;all16 prior books remain unchanged/current without recalculation', () => { const { draft, book } = fixture(); assert.ok(salesCurrent(draft, book)); 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, 16); for (const [context, original] of Object.entries(before)) { assert.deepEqual(after[context], original); assert.ok( salesCurrent( { ...draft, context_opportunity: context }, after[context], ), ); } const report = JSON.stringify( createProjectDossierReport(draft, { selectedSections: new Set(['finance']), version: 'QA G4C', }), ); for (const token of [ 'opportunity:325', 'Manica', '2026-12', '1300.00', '13(1)(m)', '13(1)(n)', '13(1)(s)', 'g4c-goods', 'Operação relacionada', ]) assert.ok(report.includes(token), token); if (process.env.IVA_G4C_CALCULATED) fs.writeFileSync(process.env.IVA_G4C_CALCULATED, JSON.stringify(draft)); });