import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { irpcBook, newIrpcRow, recordIrpc, saveIrpcBook, emptyIrpcOrdinaryFacts, irpcCurrent, irpcReportRows, irpcPreparationHtml, } from '../../resources/js/lib/irpc-withholdings.ts'; import { irpcFxSources, updateIrpcFx } from '../../resources/js/lib/irpc-fx.ts'; import { emptyFxSelection, selectFx, } from '../../resources/js/lib/iva-fx-selection.ts'; import { currencyEventBook, newCurrencyEvent, saveCurrencyEventBook, } from '../../resources/js/lib/iva-currency-events.ts'; const accepted = JSON.parse( fs.readFileSync( new URL('./fixtures/irpc-l5-preserved.json', import.meta.url), 'utf8', ), ); const draft = () => structuredClone(accepted); const row = (id = 'normal') => ({ ...newIrpcRow(id), counterparty: 'SYNTHETIC IRPC counterparty', documentRef: 'SYNTHETIC-' + id, base: '100000', kind: 'royalty', recipientIrpc: 'yes', payerIrpc: 'yes', residence: 'nonresident', permanentEstablishment: 'no', mozambique: 'yes', qualificationRef: 'SYNTHETIC classified non-digital income,IRPCpayer/recipient,Mozambique source;residence andEP explicitly recorded', specialRelief: 'none', eventsComplete: 'yes', events: [ { kind: 'cost', date: '2026-10-02', reference: 'SYNTHETIC applicable cost', }, { kind: 'payment', date: '2026-11-02', reference: 'SYNTHETIC payment' }, ], settlement: 'none', settlementRef: 'SYNTHETIC no remittance', ordinary: { ...emptyIrpcOrdinaryFacts(), nonDigital: 'yes', reference: 'SYNTHETIC exact category facts;CIRPC62/67', }, }); const book = (r = row()) => ({ version: 1, rows: [r], payments: [] }); const calc = (r) => recordIrpc(draft(), book(r)).result.rows[0]; const fx = (patch = {}) => { const s = { ...emptyFxSelection(), nature: 'commercial', submissionDate: '2026-10-20', bulletinDate: '2026-10-20', bulletinRef: 'SYNTHETIC fictional BM bulletin for QA', source: 'SYNTHETIC archived QA exemplar', issuer: 'Banco de Moçambique', sourceCurrency: 'USD', direction: 'mzn_per_unit', mean: '64', ...patch, }; return { ...s, applied: selectFx(s) }; }; void test('ordinary nonresident royalties equipment services and nonlisted capitals use20%,not digital10%', () => { for (const kind of ['royalty', 'equipment', 'services', 'capital']) { const r = row(); r.kind = kind; if (kind === 'capital') r.ordinary.capitalType = 'unlisted_interest'; const v = calc(r); assert.equal(v.status, 'calculated'); assert.equal(v.rate, 20); assert.equal(v.tax.numerator, '20000'); assert.equal(v.nature, 'definitive'); } }); void test('resident royalties and ordinary rent are on account;predial nonresident is also on account', () => { for (const patch of [ { kind: 'royalty', residence: 'resident' }, { kind: 'rent', residence: 'resident' }, { kind: 'rent', residence: 'nonresident' }, ]) { const r = { ...row(), ...patch }; r.ordinary.rentOwnPropertyManagement = 'no'; const v = calc(r); assert.equal(v.status, 'calculated'); assert.equal(v.nature, 'on_account'); assert.equal(v.tax.numerator, '20000'); assert.equal('annualCredit' in v, false); } }); void test('identified dispensa branches and unknown facts block only their own income line', () => { const d = draft(), rs = [ { ...row('resident-services'), residence: 'resident', kind: 'services', }, { ...row('property'), kind: 'rent' }, { ...row('capital'), residence: 'resident', kind: 'capital', ordinary: { ...row().ordinary, capitalType: 'unlisted_interest', }, }, row('independent'), ]; const out = recordIrpc(d, { version: 1, rows: rs, payments: [] }).result .rows; assert.ok( out .slice(0, 3) .every((v) => v.status === 'indeterminate' && v.tax === null), ); assert.equal(out[3].tax.numerator, '20000'); }); void test('special categories62(4)a-c retain10% with exact facts,not generic20% or genericinstallation10%', () => { for (const kind of [ 'telecom', 'international_transport', 'connected_installation', 'aircraft_maintenance_charter', 'rural_public_electrification', 'maritime_fishing_cabotage', ]) { const r = { ...row(), kind }; r.ordinary.specialFacts = 'yes'; const v = calc(r); assert.equal(v.rate, 10); assert.equal(v.tax.numerator, '10000'); assert.match(v.reason, /62\(4\)/); } const unknown = { ...row(), kind: 'connected_installation' }; assert.equal(calc(unknown).tax, null); const generic = { ...row(), kind: 'services' }; assert.equal(calc(generic).rate, 20); }); void test('special62(4) entity condition is distinct from digital noEP/nonattributable branch', () => { for (const patch of [ { residence: 'resident' }, { permanentEstablishment: 'yes', attributable: 'no' }, ]) { const r = { ...row(), kind: 'telecom', ...patch }; r.ordinary.specialFacts = 'yes'; assert.equal(calc(r).tax, null); } }); void test('listed debt and other BMsecurities remain distinct;liquidity repos are not assumed', () => { for (const [capitalType, rate] of [ ['listed_debt_mz', 20], ['listed_other_mz', 10], ]) { const r = { ...row(), kind: 'capital' }; r.ordinary.capitalType = capitalType; r.ordinary.specialFacts = 'yes'; assert.equal(calc(r).rate, rate); } const resident = { ...row(), kind: 'capital', residence: 'resident' }; resident.ordinary.capitalType = 'listed_debt_mz'; resident.ordinary.capitalSeatAndManagement = 'yes'; assert.equal(calc(resident).rate, 20); assert.equal(calc(resident).nature, 'definitive'); resident.ordinary.capitalType = 'liquidity_repo'; assert.equal(calc(resident).rate, 20); assert.equal(calc(resident).nature, 'definitive'); }); void test('expired2025 agricultural measures do not extend into2026 foreign services/interest', () => { for (const kind of ['services', 'capital']) { const r = { ...row(), kind, description: 'SYNTHETIC nationalagriculturalcompany / external agricultural financing', }; r.ordinary.capitalType = 'unlisted_interest'; const v = calc(r); assert.equal(v.rate, 20); assert.match(v.reason, /31\/12\/2025/); } const r = row(); r.events[0].date = '2025-12-31'; assert.equal(calc(r).tax, null); }); void test('applied existing BMmean converts base, preserves original and keeps event/date separate', () => { const r = { ...row(), currency: 'USD', base: '1000', fxSelection: fx() }; const v = calc(r); assert.equal(v.base.numerator, '64000'); assert.equal(v.tax.numerator, '12800'); assert.equal(v.period, '2026-10'); assert.equal(v.eventDate, '2026-10-02'); assert.equal(v.conversion.date, '2026-10-20'); assert.equal(v.conversion.originalAmount, '1000'); assert.equal(v.conversion.originalCurrency, 'USD'); assert.equal(r.base, '1000'); assert.equal(r.currency, 'USD'); }); void test('purchase/sale perspective,inverse quote and fiscalexception use deliveredselector', () => { const opposite = fx({ nature: 'purchase', relation: 'opposite', bulletinPerspective: 'SYNTHETIC issuer', buy: '63', sell: '65', }); let r = { ...row(), currency: 'USD', base: '100', fxSelection: opposite }; assert.equal(calc(r).base.numerator, '6500'); const inverse = fx({ direction: 'units_per_mzn', mean: '0.02' }); r.fxSelection = inverse; assert.equal(calc(r).base.numerator, '5000'); r.fxSelection = fx({ mode: 'exception', exceptionRef: 'SYNTHETIC identifiedfiscalprovision', exceptionScope: 'SYNTHETIC retention scope', exceptionDate: '2026-10-02', exceptionRate: '66', }); assert.equal(calc(r).base.numerator, '6600'); assert.equal(calc(r).conversion.column, 'exception'); }); void test('unapplied,stale,missingmean,wrongcurrency or undated FX remains indeterminate', () => { const samples = [ undefined, { ...fx(), applied: undefined }, { ...fx(), mean: '65' }, { ...fx(), sourceCurrency: 'EUR' }, { ...fx(), bulletinDate: '2026-10-19' }, ]; for (const f of samples) { const v = calc({ ...row(), currency: 'USD', base: '1000', fxSelection: f, }); assert.equal(v.tax, null); assert.match(v.reason, /conversão/); } }); void test('existing saved FX selections are linked,reused,and changed source marks only linked results stale', () => { const d = draft(), cb = currencyEventBook(d), s = fx(), f = { ...newCurrencyEvent('shared-fx', 'MZN'), fxSelection: s, sourceCurrency: 'USD', fxDirection: s.direction, fxRate: s.applied.rate, fxDate: s.applied.date, fxSource: s.applied.source, description: 'SYNTHETIC existing FX', }; d.modelo_a_snapshot = saveCurrencyEventBook(d, { ...cb, rows: [...cb.rows, f], }); const source = irpcFxSources(d).find((s) => s.id === f.id), r = { ...row(), currency: 'USD', base: '1000', fxSelection: source.selection, fxLink: { id: source.id, fingerprint: source.fingerprint }, }, b = recordIrpc(d, book(r)); d.modelo_a_snapshot = saveIrpcBook(d, b); assert.equal(irpcCurrent(d, b), true); const current = currencyEventBook(d); d.modelo_a_snapshot = saveCurrencyEventBook(d, { ...current, rows: current.rows.map((v) => v.id === f.id ? { ...v, description: 'SYNTHETIC revisedsource' } : v, ), }); assert.equal(irpcCurrent(d, b), false); assert.match(recordIrpc(d, b).result.rows[0].reason, /origem alterada/); const m = recordIrpc(d, book(row('independentMZN'))); assert.equal(m.result.rows[0].tax.numerator, '20000'); }); void test('foreign withholding proof converts available amount once;allocations remainMZN', () => { const r = { ...row(), direction: 'received', settlement: 'complete', settlementRef: 'SYNTHETIC provedpartialwithholding', allocations: [{ paymentId: 'p', amount: '640' }], }, p = { id: 'p', kind: 'withholding_proof', reference: 'SYNTHETIC-USDproof', date: '2026-10-20', currency: 'USD', amount: '10', fxSelection: fx(), }; const d = draft(), b = recordIrpc(d, { ...book(r), payments: [p] }); assert.equal(b.result.rows[0].settled.numerator, '640'); assert.equal(b.result.rows[0].outstanding.numerator, '19360'); assert.equal(b.result.paymentConversions.p.originalAmount, '10'); d.modelo_a_snapshot = saveIrpcBook(d, b); assert.match(irpcReportRows(d)[0][3], /SYNTHETIC-USDproof/); assert.match(irpcReportRows(d)[0][3], /Original: 10 USD/); }); void test('foreign payment to beneficiary or purported ATremittance is never converted into taxproof', () => { for (const kind of ['beneficiary', 'remittance']) { const r = { ...row(), settlement: 'complete', allocations: [{ paymentId: 'p', amount: '640' }], }, p = { id: 'p', kind, reference: 'SYNTHETIC-wrongpayment', date: '2026-10-20', currency: 'USD', amount: '10', fxSelection: fx(), }; assert.equal( recordIrpc(draft(), { ...book(r), payments: [p] }).result.rows[0] .status, 'indeterminate', ); } }); void test('converted shared proof cannot exceed availableMZN or change FX between contexts', () => { const d = draft(), p = { id: 'p', kind: 'withholding_proof', reference: 'SYNTHETIC-sharedUSD', date: '2026-10-20', currency: 'USD', amount: '10', fxSelection: fx(), }, r = { ...row('one'), direction: 'received', settlement: 'complete', allocations: [{ paymentId: 'p', amount: '400' }], }; d.modelo_a_snapshot = saveIrpcBook( d, recordIrpc(d, { ...book(r), payments: [p] }), ); d.context_opportunity = 'other'; const other = { ...row('two'), direction: 'received', settlement: 'complete', allocations: [{ paymentId: 'p', amount: '300' }], }; assert.equal( recordIrpc(d, { ...book(other), payments: [p] }).result.rows[0].status, 'indeterminate', ); other.allocations[0].amount = '200'; assert.equal( recordIrpc(d, { ...book(other), payments: [p] }).result.rows[0].status, 'calculated', ); assert.equal( recordIrpc(d, { ...book(other), payments: [{ ...p, fxSelection: fx({ mean: '65' }) }], }).result.rows[0].status, 'indeterminate', ); }); void test('accepted v1MZN10% results remain current and bytepreserved without recomputation', () => { const d = draft(), b = irpcBook(d), before = JSON.stringify(b); assert.equal(irpcCurrent(d, b), true); assert.equal(JSON.stringify(irpcBook(d)), before); assert.deepEqual( irpcBook({ ...d, modelo_a_snapshot: saveIrpcBook(d, b) }), b, ); }); void test('new normal/FX result preserves old maps and acceptedresult in history,report frozen after change', () => { const d = draft(), b = irpcBook(d), old = structuredClone(b.result), s = JSON.parse(d.modelo_a_snapshot); const next = recordIrpc(d, { ...b, rows: [ ...b.rows, { ...row('fx'), currency: 'USD', base: '1000', fxSelection: fx() }, ], }); d.modelo_a_snapshot = saveIrpcBook(d, next); for (const [k, v] of Object.entries(s)) if (k !== 'irpcWithholdings') assert.deepEqual(JSON.parse(d.modelo_a_snapshot)[k], v); assert.deepEqual(next.history.at(-1), old); next.rows.at(-1).fxSelection.mean = '65'; assert.equal(irpcCurrent(d, next), false); d.modelo_a_snapshot = saveIrpcBook(d, next); const report = irpcReportRows(d).at(-1); assert.match(report[2], /12800.00/); assert.match(report[3], /Histórico/); assert.match(report[3], /taxa: 64/); }); void test('export keeps currencies,normal/special distinctions,payment refs and externalformalities separate', () => { const d = draft(), r = { ...row(), currency: 'USD', base: '1000', fxSelection: fx() }; d.modelo_a_snapshot = saveIrpcBook(d, recordIrpc(d, book(r))); const h = irpcPreparationHtml(d); assert.match(h, /Original: 1000 USD/); assert.ok(!h.includes('selectionKey')); assert.match(h, /20%/); assert.match(h, /CER/); assert.match(h, /sem submissão/); assert.match(h, /2026-10/); }); if (process.env.IRPC_GROUPED_FIXTURE_OUTPUT) { const d = draft(), old = irpcBook(d), s = fx(), f = { ...newCurrencyEvent('retention-fx', 'MZN'), description: 'SYNTHETIC existing BMselection for retention', fxSelection: s, sourceCurrency: 'USD', fxDirection: s.direction, fxRate: s.applied.rate, fxDate: s.applied.date, fxSource: s.applied.source, }; const cb = currencyEventBook(d); d.modelo_a_snapshot = saveCurrencyEventBook(d, { ...cb, rows: [...cb.rows, f], }); const source = irpcFxSources(d).find((v) => v.id === f.id); const foreign = { ...row('foreign-royalty'), currency: 'USD', base: '1000', fxSelection: source.selection, fxLink: { id: source.id, fingerprint: source.fingerprint }, settlement: 'complete', settlementRef: 'SYNTHETIC IRPC remittance5000', allocations: [{ paymentId: 'p3', amount: '5000' }], }, special = { ...row('special-telecom'), kind: 'telecom', ordinary: { ...row().ordinary, specialFacts: 'yes' }, }, resident = { ...row('resident-royalty'), residence: 'resident', base: '50000', }, received = { ...row('received-foreign-royalty'), direction: 'received', currency: 'USD', base: '100', fxSelection: s, settlement: 'complete', settlementRef: 'SYNTHETIC proof10USD converted640MZN', allocations: [{ paymentId: 'p4', amount: '640' }], }; const b = recordIrpc(d, { ...old, rows: [...old.rows, foreign, special, resident, received], payments: [ ...old.payments, { id: 'p3', kind: 'remittance', reference: 'SYNTHETIC-IRPC-AT5000', date: '2026-10-20', amount: '5000', currency: 'MZN', }, { id: 'p4', kind: 'withholding_proof', reference: 'SYNTHETIC-IRPC-USD10', date: '2026-10-20', amount: '10', currency: 'USD', fxSelection: s, }, ], }); d.modelo_a_snapshot = saveIrpcBook(d, b); fs.writeFileSync( process.env.IRPC_GROUPED_FIXTURE_OUTPUT, JSON.stringify(d, null, 2) + '\n', ); } void test('62(3) definitive capital needs exact seat/management facts and is not an annual credit', () => { const r = { ...row(), residence: 'resident', kind: 'capital' }; r.ordinary.capitalType = 'listed_debt_mz'; assert.equal(calc(r).tax, null); r.ordinary.capitalSeatAndManagement = 'yes'; assert.equal(calc(r).nature, 'definitive'); assert.equal(calc(r).rate, 20); assert.equal('annualCredit' in calc(r), false); }); void test('confirming unchanged FX preserves source link;deliberate input change detaches only that selection', () => { const input = { currency: 'USD', fxSelection: fx(), fxLink: { id: 'saved', fingerprint: 'original' }, }; const confirmed = { ...input.fxSelection, applied: selectFx(input.fxSelection), }; assert.deepEqual(updateIrpcFx(input, confirmed).fxLink, input.fxLink); assert.equal( updateIrpcFx(input, { ...input.fxSelection, mean: '65' }).fxLink, undefined, ); assert.equal(input.fxSelection.mean, '64'); assert.equal(input.fxLink.id, 'saved'); });