import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { newIrpcRow, emptyIrpcOrdinaryFacts, recordIrpc, saveIrpcBook, irpcBook, irpcCurrent, irpcReportRows, } from '../../resources/js/lib/irpc-withholdings.ts'; import { newMrftaInput, recordMrfta, mrftaCurrent, mrftaHtml, prepareMrfta, } from '../../resources/js/lib/irpc-mrfta.ts'; import { newIrpcInterestRule, calculateIrpcInterest, } from '../../resources/js/lib/irpc-interest.ts'; import { mrftaCases, mrftaCaseHtml, } from '../../resources/js/lib/irpc-mrfta-cases.ts'; import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts'; const draft = () => JSON.parse( fs.readFileSync( new URL('./fixtures/irpc-grouped-preserved.json', import.meta.url), 'utf8', ), ); const rule = () => ({ ...newIrpcInterestRule(), annualRate: '10', rateSource: 'QA parâmetro fictício; não é taxa fiscal2026', fiscalBasis: 'QA fundamento sintético para ensaio aritmético', validFrom: '2026-01-01', validTo: '2026-12-31', dayBasis: '365', dayBasisRef: 'QA fonte sintética da base365', rounding: 'total_cent_half_up', roundingRef: 'QA fonte sintética do arredondamento do total', retention: 'withheld', occurrenceRef: 'QA retenção e atraso documentados', }); const row = (id = 'one') => ({ ...newIrpcRow(id), counterparty: 'QA contraparte', documentRef: 'QA-' + id, base: '36500', kind: 'royalty', recipientIrpc: 'yes', payerIrpc: 'yes', residence: 'nonresident', permanentEstablishment: 'no', mozambique: 'yes', qualificationRef: 'QA incidência e residência', specialRelief: 'none', eventsComplete: 'yes', events: [{ kind: 'cost', date: '2026-10-02', reference: 'QA custo' }], settlement: 'none', settlementRef: 'QA sem entregas', ordinary: { ...emptyIrpcOrdinaryFacts(), nonDigital: 'yes', reference: 'QA royalties não digitais', }, }); const book = (payments = []) => { const r = row(); if (payments.length) { r.settlement = 'complete'; r.allocations = payments.map((p) => ({ paymentId: p.id, amount: p.amount, })); } return recordIrpc(draft(), { version: 1, rows: [r], payments }); }; const payment = (amount, date = '2026-11-25', id = 'p') => ({ id, kind: 'remittance', reference: 'QA entrega-' + id, date, amount, currency: 'MZN', }); const calc = (b = book(), r = rule(), due = '2026-11-20', end = '2026-11-30') => calculateIrpcInterest( b, true, '2026-10', due, 'QA prazo documentado', end, r, ); const input = () => ({ ...newMrftaInput(), period: '2026-10', declaration: 'initial', timing: 'late', nuit: '999999999', name: 'QA empresa', taxArea: 'QA área fiscal', address: 'QA domicílio', completeness: 'yes', completenessRef: 'QA reconciliação do período', otherRubrics: 'none', otherRubricsRef: 'QA outras rubricas não devidas', codes: [ { rowId: 'one', cer: '999', reference: 'QA CER fictício, não oficial' }, ], interest: { ...newMrftaInput().interest, mode: '', calculation: 'automatic', period: '2026-10', date: '2026-11-30', dueDate: '2026-11-20', dueReference: 'QA prazo documentado', reference: 'QA ocorrência', automatic: rule(), }, }); void test('daily interest uses documented parameters and explains principal,days and exact arithmetic', () => { const v = calc(); assert.deepEqual(v.pending, []); assert.equal(v.total.numerator, '20'); assert.equal(v.rows[0].segments[0].from, '2026-11-21'); assert.equal(v.rows[0].segments[0].to, '2026-11-30'); assert.equal(v.rows[0].segments[0].days, 10); assert.equal(v.rows[0].segments[0].principal.numerator, '7300'); }); void test('partial remittance reduces principal after its payment day without duplicate daily interest', () => { const v = calc(book([payment('3650')])); assert.equal(v.total.numerator, '15'); assert.deepEqual( v.rows[0].segments.map((s) => [s.days, s.principal.numerator]), [ [5, '7300'], [5, '3650'], ], ); const sameDay = calc( book([ payment('1825', '2026-11-25', 'p1'), payment('1825', '2026-11-25', 'p2'), ]), ); assert.equal(sameDay.total.numerator, '15'); assert.equal(sameDay.rows[0].segments.length, 2); }); void test('full payment stops charges and pre-deadline payment reduces the opening debt', () => { assert.equal(calc(book([payment('7300')])).total.numerator, '10'); assert.equal( calc(book([payment('3650', '2026-11-18')])).total.numerator, '10', ); }); void test('missing rate,reference,day basis or applicable version never becomes a default tax rate', () => { for (const patch of [ { annualRate: '' }, { annualRate: '0' }, { annualRate: '-1' }, { rateSource: '' }, { fiscalBasis: '' }, { dayBasis: '' }, { dayBasisRef: '' }, { rounding: '' }, { roundingRef: '' }, { validTo: '2017-12-31' }, { occurrenceRef: '' }, ]) { const v = calc(book(), { ...rule(), ...patch }); assert.equal(v.total, null); assert.ok(v.pending.length); } assert.equal( calculateIrpcInterest( book(), false, '2026-10', '2026-11-20', 'QA prazo', '2026-11-30', rule(), ).total, null, ); }); void test('on-account not-withheld cases remain separate;definitive not-withheld uses its own supported branch', () => { const r = row(); r.residence = 'resident'; const b = recordIrpc(draft(), { version: 1, rows: [r], payments: [] }); assert.equal(calc(b, { ...rule(), retention: 'not_withheld' }).total, null); assert.match( calc(b, { ...rule(), retention: 'not_withheld' }).pending.join(' '), /declaração anual/, ); assert.equal(calc(b).total.numerator, '20'); assert.equal( calc(book(), { ...rule(), retention: 'not_withheld' }).total.numerator, '20', ); }); void test('documented actual-year basis splits year boundary including leap year without floating money', () => { const r = { ...rule(), validTo: '2028-12-31', dayBasis: 'actual_year' }, v = calc(book(), r, '2027-12-30', '2028-01-02'); assert.deepEqual( v.rows[0].segments.map((s) => [s.days, s.denominator]), [ [1, 365], [2, 366], ], ); assert.deepEqual(v.total, { numerator: '599', denominator: '100' }); }); void test('rounding is explicit,on total only,and matches the documented convention', () => { assert.deepEqual(calc(book(), { ...rule(), annualRate: '10.0025' }).total, { numerator: '2001', denominator: '100', }); assert.equal( calc(book(), { ...rule(), annualRate: '10.0025', rounding: 'total_cent_down', }).total.numerator, '20', ); }); void test('unreconciled payments and payments after liquidation keep the affected calculation indeterminate', () => { assert.equal(calc(book([payment('100', '2026-12-01')])).total, null); const b = book(); b.result.rows[0].settled = null; assert.equal(calc(b).total, null); }); void test('distinct rates and CER produce stable working cases,not invented declarations or merged rates', () => { const b = { ...book(), mrfta: input() }; const second = { ...b.result.rows[0], id: 'two', rate: 10 }; b.result.rows.push(second); b.result.inputs.push(row('two')); b.mrfta.codes.push({ rowId: 'two', cer: '998', reference: 'QA second fictionalCER', }); const m = prepareMrfta(b, true), cases = mrftaCases(m); assert.equal(cases.length, 2); assert.notEqual(cases[0].key, cases[1].key); assert.deepEqual( cases.map((c) => [c.rate, c.cer]), [ [20, '999'], [10, '998'], ], ); assert.equal(m.total, null); const html = mrftaCaseHtml(m, cases[0].key); assert.match(html, /Não é uma declaração autónoma/); assert.ok(html.includes('Pendências do período')); assert.deepEqual( mrftaCases({ ...m, lines: [...m.lines].reverse() }) .map((c) => c.key) .sort(), cases.map((c) => c.key).sort(), ); }); void test('missing CER references remain visible within each case sheet without presumed correspondence', () => { const b = { ...book(), mrfta: input() }; b.mrfta.codes[0].reference = ''; const m = prepareMrfta(b, true); assert.match(mrftaCases(m)[0].pending, /referência por corresponder/); b.mrfta.codes = []; assert.match( mrftaCases(prepareMrfta(b, true))[0].pending, /sem preenchimento presumido/, ); }); void test('automatic result persists,recovers and reaches dossier with traceable parameters and history', () => { const d = draft(), b = recordMrfta({ ...book([payment('3650')]), mrfta: input() }, true); d.modelo_a_snapshot = saveIrpcBook(d, b); const recovered = JSON.parse(JSON.stringify(d)), saved = irpcBook(recovered); assert.equal(irpcCurrent(recovered, saved), true); assert.equal(mrftaCurrent(saved, true), true); assert.equal(saved.mrftaResult.model.interest.numerator, '15'); assert.equal(saved.mrftaResult.model.total.numerator, '3665'); assert.equal(saved.mrftaResult.model.automatic.rows[0].segments.length, 2); const report = JSON.stringify(irpcReportRows(recovered)); assert.match(report, /Taxa anual:10%/); assert.match(report, /QA parâmetro fictício/); assert.match(report, /5dias\/365/); assert.match(report, /metade por excesso/); assert.ok(!report.includes('total_cent_half_up')); assert.match( JSON.stringify( createProjectDossierReport(recovered, { selectedSections: new Set(['finance']), }), ), /3665/, ); assert.match( mrftaHtml(saved, true), /Juros calculados por parâmetros documentados/, ); assert.match( mrftaHtml(saved, true), /simulação pelos pressupostos introduzidos, sem confirmação fiscal/, ); assert.match( mrftaHtml(saved, true), /Códigos provisórios ou de simulação não são correspondências oficiais/, ); assert.match(report, /sem validação por catálogo oficial/); assert.match( mrftaCases(saved.mrftaResult.model)[0].pending, /Código provisório ou de simulação não é correspondência oficial/, ); saved.mrfta.interest.automatic.annualRate = ''; assert.equal(mrftaCurrent(saved, true), false); const changed = recordMrfta(saved, true); assert.equal(changed.mrftaResult.model.interest, null); assert.deepEqual(changed.mrftaHistory.at(-1), b.mrftaResult); fs.mkdirSync('outputs/irpc-interest-cer', { recursive: true }); fs.writeFileSync( 'outputs/irpc-interest-cer/calculated.json', JSON.stringify(recovered), ); fs.writeFileSync( 'outputs/irpc-interest-cer/preparation.html', mrftaHtml(b, true), ); }); void test('new parameters reject malformed persisted values while the installed snapshot remains current', () => { const d = JSON.parse( fs.readFileSync( new URL('./fixtures/irpc-mrfta-preserved.json', import.meta.url), 'utf8', ), ); const b = irpcBook(d); assert.equal(irpcCurrent(d, b), true); assert.equal(mrftaCurrent(b, true), true); const invalid = { ...book(), mrfta: input() }; invalid.mrfta.interest.automatic.annualRate = 10; assert.throws(() => saveIrpcBook(draft(), invalid)); }); void test('two calculated incomes with different treaty/internal rates remain separate without an invented declaration total', () => { const d = draft(), first = { ...row(), settlement: 'complete', allocations: [{ paymentId: 'p', amount: '3650' }], }, second = { ...row('two'), specialRelief: 'claimed', reliefPreparation: { kind: 'treaty', rule: 'ptMz12', instrument: 'QA CDT PT-MZ e Protocolo', article: '12', country: 'PT', residenceDocument: 'QA certificado Portugal2026', validFrom: '2026-01-01', validTo: '2026-12-31', conditions: 'QA royalties qualificadas semEP', evidence: 'QA contrato e residência', facts: { treatyResident: 'yes', beneficialOwner: 'yes', payerResidentMz: 'yes', armLength: 'yes', royaltyDefinition: 'yes', }, }, }; const b = recordMrfta( { ...recordIrpc(d, { version: 1, rows: [first, second], payments: [payment('3650')], }), mrfta: { ...input(), codes: [ ...input().codes, { rowId: 'two', cer: '998', reference: 'QA CER998 fictício, não oficial', }, ], }, }, true, ); const m = b.mrftaResult.model; assert.deepEqual( b.result.rows.map((r) => r.rate), [20, 10], ); assert.equal(m.interest.numerator, '25'); assert.equal(m.total, null); assert.deepEqual( mrftaCases(m).map((c) => [c.rate, c.cer]), [ [20, '999'], [10, '998'], ], ); assert.match(m.pending.join(' '), /taxas ou CER distintos/); d.modelo_a_snapshot = saveIrpcBook(d, b); fs.writeFileSync('outputs/irpc-interest-cer/mixed.json', JSON.stringify(d)); });