import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { temporaryBenefits, temporaryBenefitNotice, temporaryBenefitVersion, } from '../../resources/js/lib/iva-temporary-benefits.ts'; import { continueTemporaryOperation, evaluateSalesOperation, newSalesOperation, operationTreatments, recordSales, salesBook, salesCurrent, saveSalesBook, } 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-annex-i-inputs.json', import.meta.url), 'utf8', ), ); function operation(benefit, index = 0) { return { ...newSalesOperation(`g3-${index}`, '2026-10', 'MZN'), treatment: benefit.id, description: `SYNTHETIC QA — ${benefit.branch} / ${benefit.label}`, establishment: 'SYNTHETIC QA estabelecimento', amount: index === 0 ? '116' : '100', basis: index === 0 ? 'included' : 'net', evidence: 'SYNTHETIC QA — natureza e contraprestação pressupostas para demonstração; não certificação', }; } export function fixture() { const draft = { ...structuredClone(frozen), project_title: 'SYNTHETIC QA — G3 / Cessação de benefícios temporários', context_opportunity: 'opportunity:322', }; const pending = recordSales(draft, { currency: 'MZN', rows: temporaryBenefits.map(operation), periods: [{ period: '2026-10', complete: true }], }); const rows = pending.rows.map((row) => { const next = continueTemporaryOperation(row, 'general'); next.confirmed = operationTreatments .find((rule) => rule.id === next.treatment) .conditions.map((_, i) => i); return next; }); const agricultural = continueTemporaryOperation( { ...operation(temporaryBenefits[5], 8), id: 'g3-cane-independent' }, 'production_agriculture', ); agricultural.confirmed = operationTreatments .find((rule) => rule.id === agricultural.treatment) .conditions.map((_, i) => i); rows.push(agricultural); const book = recordSales(draft, { ...pending, rows }); draft.modelo_a_snapshot = saveSalesBook(draft, book); return { draft, book, pending }; } void test('G3 has seven stable branches; expired benefits never become zero-rated or taxable automatically in2026', () => { assert.deepEqual( temporaryBenefits.map((item) => item.branch), ['P038', 'P039', 'P040', 'P041', 'P042', 'P043', 'P045'], ); for (const benefit of temporaryBenefits) { const row = operation(benefit), result = evaluateSalesOperation(row, 'MZN'); assert.equal(result.status, 'indeterminate'); for (const field of ['field', 'rate', 'net', 'vat', 'gross']) assert.equal(result[field], null, field); assert.match(result.reason, /terminou em31\/12\/2025/); assert.match(result.source, /Lei10\/2025/); for (const period of ['2026-01', '2026-12']) assert.match( temporaryBenefitNotice(benefit.id, period), /não é aplicável/, ); for (const period of ['2025-12', '2027-01', '']) assert.match( evaluateSalesOperation({ ...row, period }, 'MZN').reason, /fora da cobertura/, ); assert.match( evaluateSalesOperation({ ...row, amount: '', evidence: '' }, 'MZN') .reason, /ficam por determinar/, ); } assert.match( temporaryBenefits[1].historicalScope, /efectuadas pela indústria/, ); assert.match(temporaryBenefits[1].historicalScope, /9\(14\)/); assert.match(temporaryBenefits[5].historicalScope, /9\(14\)/); assert.throws(() => temporaryBenefitNotice('unknown', '2026-10')); }); void test('explicit continuation keeps amounts/origin/identity and requires new conditions; cane has an independent agricultural route', () => { for (const [i, benefit] of temporaryBenefits.entries()) { const row = operation(benefit, i), next = continueTemporaryOperation(row, 'general'); for (const field of [ 'id', 'period', 'description', 'establishment', 'amount', 'basis', 'evidence', 'sourceAmount', 'sourcePeriod', 'sourceCurrency', ]) assert.deepEqual(next[field], row[field], field); assert.equal(next.temporaryBenefitOrigin, benefit.id); assert.deepEqual(next.confirmed, []); assert.ok(next.source.startsWith(row.source)); assert.ok(next.source.includes(benefit.branch)); assert.ok(next.source.includes(temporaryBenefitVersion)); assert.equal(evaluateSalesOperation(next, 'MZN').vat, null); const confirmed = { ...next, confirmed: [0, 1] }; assert.equal(evaluateSalesOperation(confirmed, 'MZN').vat, '16.00'); assert.equal( evaluateSalesOperation({ ...confirmed, amount: '' }, 'MZN').vat, null, ); assert.equal( evaluateSalesOperation({ ...confirmed, evidence: '' }, 'MZN').vat, null, ); assert.equal( evaluateSalesOperation( { ...confirmed, sourceCurrency: 'USD' }, 'MZN', ).vat, null, ); assert.throws(() => continueTemporaryOperation(row, 'missing')); assert.throws(() => continueTemporaryOperation(row, benefit.id)); } const { book } = fixture(); assert.equal(book.result.rows.at(-1).field, '05'); assert.equal(book.result.rows.at(-1).vat, '0.00'); assert.equal(book.result.rows[0].net, '100.00'); assert.equal(book.result.rows[0].vat, '16.00'); assert.equal(book.rows[0].amount, '116'); assert.equal(book.rows[0].basis, 'included'); }); void test('actual cessation→choice→calculation→stored snapshot→report preserves all13 delivered books and history', () => { const { draft, book, pending } = fixture(); for (const value of Object.values(pending.result.periods[0].fields)) assert.equal(value, null); assert.deepEqual(book.history.at(-1), pending.result); assert.equal(book.result.periods[0].fields['01'], '700.00'); assert.equal(book.result.periods[0].fields['02'], '112.00'); assert.equal(book.result.periods[0].fields['05'], '100.00'); assert.equal(salesCurrent(draft, book), true); assert.deepEqual(salesBook(JSON.parse(JSON.stringify(draft))), book); const before = JSON.parse(frozen.modelo_a_snapshot).salesOperations, after = JSON.parse(draft.modelo_a_snapshot).salesOperations; assert.equal(Object.keys(before).length, 13); for (const [context, original] of Object.entries(before)) { assert.deepEqual(after[context], original, context); assert.equal( salesCurrent( { ...draft, context_opportunity: context }, after[context], ), true, context, ); } const model = createProjectDossierReport(draft, { selectedSections: new Set(['finance']), version: 'QA G3 — Cessação2026', }); const encoded = JSON.stringify(model); for (const token of [ 'opportunity:322', 'Manica', '2026-10', '700.00', '112.00', '100.00', '31/12/2025', temporaryBenefitVersion, 'P045', 'Lei3/2025', 'Lei22/2022', ]) assert.ok(encoded.includes(token), token); if (process.env.IVA_G3_CALCULATED) fs.writeFileSync(process.env.IVA_G3_CALCULATED, JSON.stringify(draft)); });