import { pecCase } from './helpers/irpc-pec-case.mjs'; import test from 'node:test'; import assert from 'node:assert/strict'; import { annualBook, calculateAnnual, recordAnnual, saveAnnualBook, annualCurrent, annualReportRows, annualHtml, } from '../../resources/js/lib/irpc-annual-settlement.ts'; import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts'; const value = (x) => (x ? Number(x.numerator) / Number(x.denominator) : null); const now = '2026-10-27T15:00:00+02:00'; const model = ({ d, b }) => calculateAnnual(d, b); const indeterminate = (c) => { const m = model(c); assert.equal(m.status, 'indeterminate', JSON.stringify(m.pending)); assert.equal(m.arithmeticBalance, null); assert.equal(m.pecDeduction.deducted, null); assert.equal(m.pecDeduction.remaining, null); return m; }; void test('PEC precedes PPC; 1408 - 200 - 500 = 708, no official consumption', () => { const m = model(pecCase()); assert.deepEqual(m.pending, []); assert.equal(value(m.pecDeduction.deducted), 200); assert.equal(value(m.pecDeduction.afterPec), 1208); assert.equal(value(m.payable), 708); assert.equal(m.pecDeduction.carryCreated, false); assert.equal(m.officialBalancesConsumed, false); }); void test('existing received L5 proof is reused after PEC and cannot become another PEC receipt', () => { const c = pecCase('200', '500', '300'); assert.equal(value(model(c).payable), 408); Object.assign(c.b.pecPayments.rows[0], { document: 'QA-RF-PEC-2026', proofAmount: '300', }); assert.match(indeterminate(c).pending.join(' '), /natureza/); }); void test('PEC cap never generates refund; PPC can still generate recoverable balance', () => { const a = model(pecCase('2000', '0')); assert.equal(value(a.pecDeduction.deducted), 1408); assert.equal(value(a.pecDeduction.remaining), 592); assert.equal(value(a.arithmeticBalance), 0); assert.equal(value(a.recoverable), 0); const b = model(pecCase('2000', '500')); assert.equal(value(b.recoverable), 500); assert.equal(value(b.pecDeduction.remaining), 592); assert.equal(value(b.pecDeduction.afterPec), 0); }); void test('previous use explicit, precise decimals and available portion are respected', () => { const c = pecCase('200.25', '0'); Object.assign(c.b.pecPayments.rows[0], { proofAmount: '300.75', previouslyUsed: '100.50', }); assert.equal(value(model(c).payable), 1207.75); c.b.pecPayments.rows[0].amount = '200.26'; indeterminate(c); }); void test('absence, known zero and missing PEC are distinct; contradictory rows block', () => { const c = pecCase(); c.b.pecPayments.rows = []; c.b.pecPayments.state = 'absent'; assert.equal(value(model(c).payable), 908); c.b.pecPayments.state = ''; indeterminate(c); c.b.pecPayments.state = 'known'; indeterminate(c); const z = pecCase('0'); assert.equal(value(model(z).payable), 908); z.b.pecPayments.rows[0].previouslyUsed = ''; indeterminate(z); z.b.pecPayments.state = 'absent'; indeterminate(z); }); void test('payment proof, unused balance, entity, year and currency cannot be presumed', () => { for (const change of [ { effective: '' }, { effective: 'no' }, { document: '' }, { reference: '' }, { priorUseReference: '' }, { previouslyUsed: '' }, { previouslyUsed: '201' }, { amount: '201' }, { amount: '1.001' }, { subjectNuit: '999999999' }, { year: '2025' }, { date: '2025-08-31' }, { date: '2026-02-30' }, { currency: 'USD' }, ]) { const c = pecCase(); Object.assign(c.b.pecPayments.rows[0], change); indeterminate(c); } }); void test('same physical proof cannot be duplicated, re-dated or relabelled PPC', () => { const a = pecCase(); a.b.pecPayments.rows.push({ ...a.b.pecPayments.rows[0], id: 'copy', date: '2026-09-01', }); assert.match( indeterminate(a).pending.join(' '), /repetido|saldo disponível/, ); const b = pecCase(); Object.assign(b.b.pecPayments.rows[0], { document: b.b.ppc.rows[0].document, proofAmount: '500', }); assert.match( indeterminate(b).pending.join(' '), /naturezas|natureza incompatível/, ); }); void test('cross-context allocations respect prior use and changing external claims invalidates current result', () => { const c = pecCase('100', '0'), r = c.b.pecPayments.rows[0]; r.proofAmount = '500'; r.previouslyUsed = '100'; const other = structuredClone(c.b); other.pecPayments.rows[0].amount = '300'; const s = JSON.parse(c.d.modelo_a_snapshot); s.irpcAnnual = { 'opportunity:other': other }; c.d.modelo_a_snapshot = JSON.stringify(s); c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, c.b, now)); const saved = annualBook(c.d); assert.equal(annualCurrent(c.d, saved), true); assert.equal(value(saved.result.model.payable), 1308); const edit = JSON.parse(c.d.modelo_a_snapshot); edit.irpcAnnual['opportunity:other'].pecPayments.rows[0].amount = '301'; c.d.modelo_a_snapshot = JSON.stringify(edit); assert.equal(annualCurrent(c.d, saved), false); assert.match( indeterminate({ ...c, b: saved }).pending.join(' '), /saldo disponível/, ); edit.irpcAnnual['opportunity:other'].pecPayments.rows[0].amount = '100'; edit.irpcAnnual['opportunity:other'].pecPayments.rows[0].previouslyUsed = '0'; c.d.modelo_a_snapshot = JSON.stringify(edit); indeterminate({ ...c, b: saved }); }); void test('other contexts cannot change PEC titular, origin or payment nature', () => { for (const change of [{ subjectNuit: '999999999' }, { year: '2025' }]) { const c = pecCase('100', '0'), other = structuredClone(c.b); other.pecPayments.rows[0].proofAmount = '500'; Object.assign(other.pecPayments.rows[0], change); const s = JSON.parse(c.d.modelo_a_snapshot); s.irpcAnnual = { 'opportunity:other': other }; c.d.modelo_a_snapshot = JSON.stringify(s); indeterminate(c); } const c = pecCase('100', '0'), other = structuredClone(c.b); other.ppc.rows = [ { ...c.b.pecPayments.rows[0], amount: '50', proofAmount: '100' }, ]; other.ppc.state = 'known'; other.pecPayments.rows = []; other.pecPayments.state = 'absent'; const s = JSON.parse(c.d.modelo_a_snapshot); s.irpcAnnual = { 'opportunity:other': other }; c.d.modelo_a_snapshot = JSON.stringify(s); indeterminate(c); }); void test('positive special credits remain scoped and cannot be silently skipped before PEC', () => { const c = pecCase(); c.b.credits.amount = '10'; assert.match(indeterminate(c).pending.join(' '), /Créditos/); assert.equal(value(model(c).pecDeduction.available), 200); }); void test('save/recover/recalculate preserves old annual results and all other fiscal books, plan and reports', () => { const c = pecCase(), before = JSON.parse(c.d.modelo_a_snapshot), plan = c.d.business_plan_snapshot; const legacy = structuredClone(c.b); delete legacy.pecPayments; c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, legacy, now)); const old = structuredClone(annualBook(c.d).result); c.b = { ...annualBook(c.d), pecPayments: c.b.pecPayments }; c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, c.b, now)); const back = annualBook(JSON.parse(JSON.stringify(c.d))); assert.deepEqual(back.history, [old]); assert.equal(value(back.result.model.payable), 708); assert.equal(annualCurrent(c.d, back), true); const after = JSON.parse(c.d.modelo_a_snapshot); delete after.irpcAnnual; assert.deepEqual(after, before); assert.equal(c.d.business_plan_snapshot, plan); const html = annualHtml(c.d); assert.match(html, /PEC — dedução limitada/); assert.match(html, /708.00/); assert.match(html, /sem reembolso PEC/); const dossier = createProjectDossierReport(c.d, { selectedSections: new Set(['finance']), }); assert.match(JSON.stringify(dossier), /PEC — dedução limitada/); const frozen = structuredClone(back.result); back.pecPayments.rows[0].amount = '100'; c.d.modelo_a_snapshot = saveAnnualBook(c.d, back); assert.equal(annualCurrent(c.d, back), false); assert.deepEqual(back.result, frozen); assert.match(annualReportRows(c.d).flat().join(' '), /Histórico/); assert.throws(() => annualHtml(c.d), /fontes actuais/); }); void test('malformed PEC extension never replaces legacy snapshots', () => { const c = pecCase(), original = c.d.modelo_a_snapshot; c.b.pecPayments.rows[0].previouslyUsed = null; assert.throws(() => saveAnnualBook(c.d, c.b), /não recuperáveis/); assert.equal(c.d.modelo_a_snapshot, original); });