import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import { normalBook, normalCurrent, calculateNormal, recordNormal, saveNormalBook, normalHtml, } from '../../resources/js/lib/irpc-normal.ts'; import { validateSpecial } from '../../resources/js/lib/irpc-special.ts'; import { fraction } from '../../resources/js/lib/fiscal-rational.ts'; import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts'; const read = (name) => JSON.parse( fs.readFileSync(new URL('./fixtures/' + name, import.meta.url), 'utf8'), ); const val = (v) => (v ? Number(v.numerator) / Number(v.denominator) : null); function accepted() { const d = read('irpc-l7-preserved.json').acceleration, f = read('irpc-l7-start-preserved.json'); d.business_plan_snapshot = f.plan; d.modelo_a_snapshot = saveNormalBook(d, f.book); return { d, b: normalBook(d) }; } function setup(period = 'cessation', prior = true) { const { d, b } = accepted(), months = period === 'annual' ? 12 : 6, p = JSON.parse(d.business_plan_snapshot); p.plan.assetDepreciation = '120'; d.business_plan_snapshot = JSON.stringify(p); if (period === 'annual') delete b.periodsFx; else Object.assign(b.periodsFx, { kind: 'cessation', from: '2026-01-01', to: '2026-06-30', reference: 'QA cessação documentada30Junho2026;meses completos sem transmissão presumida', }); b.schedules.interest.amount = String(months * 20); Object.assign(b.depreciation.assets[0], { start: prior ? '2025-01-01' : '2026-01-15', timing: 'months', accounting: String(months * 10), previousAccounting: prior ? '150' : '0', previousFiscal: prior ? '100' : '0', priorAvailable: prior ? '50' : '0', previousReference: prior ? 'QA quotas contabilísticas150,fiscais100,parcela50 temporariamente tributada;sem encargos permanentemente excluídos' : 'QA ausência declarada de quotas anteriores;activo entrou em2026', exitState: 'absent', exitDate: '', }); for (const part of [ b.special.expenses, b.special.cooperative, b.special.acceleration, ]) Object.assign(part.qualification, { from: '2026-01-01', to: period === 'annual' ? '2026-12-31' : '2026-06-30', periodReference: period === 'annual' ? 'QA ano civil2026' : b.periodsFx.reference, }); delete b.special.acceleration.monthly.startPeriod; b.special.acceleration.monthly.continuation = { version: 1, period, reference: 'QA CBF16 vigente no sujeito/projecto e período2026 declarado;sem cumulação', historyReference: 'QA activo novo2025/2026,método constante e duodécimos desde entrada;quotas anteriores reconciliadas no mapaL6,não recalculadas', monthlyFromStart: 'yes', }; return { d, b }; } void test('accepted12 result,key and fullhistory remain exact without continuation', () => { const { d, b } = accepted(); assert.equal(normalCurrent(d, b), true); assert.deepEqual(calculateNormal(d, b), b.result.model); assert.deepEqual(normalBook(d).history, b.history); }); void test('cessation reuses6 months without assuming assetexit;50 normal,75 accelerated capped60,additional10', () => { const { d, b } = setup('cessation', false), asset = JSON.stringify(b.depreciation), period = JSON.stringify(b.periodsFx), m = calculateNormal(d, b), s = m.special, a = s.acceleration.monthly.assets[0]; assert.equal(m.version, 'IRPC-normal-adjustments/13'); assert.equal(a.months, 6); assert.equal(a.exit.state, 'absent'); assert.equal(val(a.normalQuota), 50); assert.equal(val(a.acceleratedQuota), 75); assert.equal(val(a.acceleratedDeduction), 60); assert.equal(val(a.additionalDeduction), 10); assert.equal(val(m.taxableProfit), 3130); assert.equal(val(m.colecta), 1001.6); assert.equal(val(s.taxableProfit), 3120); assert.equal(val(s.taxableBase), 3120); assert.equal(val(s.colecta), 998.4); assert.equal(JSON.stringify(b.depreciation), asset); assert.equal(JSON.stringify(b.periodsFx), period); assert.equal(s.finalBalance.payable, null); }); void test('cessation with priorbook150,fiscal100,available50 recovers15 once and adds only25 to normaldeduction', () => { const { d, b } = setup(), m = calculateNormal(d, b), s = m.special, a = s.acceleration.monthly.assets[0]; assert.equal(a.months, 6); assert.equal(val(a.normalDeduction), 50); assert.equal(val(a.acceleratedDeduction), 75); assert.equal(val(a.prior.normalUsed), 0); assert.equal(val(a.prior.acceleratedUsed), 15); assert.equal(val(a.prior.normalRemaining), 50); assert.equal(val(a.prior.acceleratedRemaining), 35); assert.equal(val(a.prior.normalFiscalRemaining), 850); assert.equal(val(a.prior.acceleratedFiscalRemaining), 825); assert.equal(val(s.acceleration.additionalDeduction), 25); assert.equal(val(s.taxableProfit), 3105); assert.equal(val(s.taxableBase), 3105); assert.equal(val(s.colecta), 993.6); assert.equal(b.depreciation.assets[0].priorAvailable, '50'); assert.equal(b.depreciation.assets[0].previousFiscal, '100'); }); void test('annual2026 continuation preserves2025 actualbalances and records30priorused within150 deduction,additional50', () => { const { d, b } = setup('annual'), m = calculateNormal(d, b), s = m.special, a = s.acceleration.monthly.assets[0]; assert.equal(a.months, 12); assert.equal(val(a.normalQuota), 100); assert.equal(val(a.acceleratedQuota), 150); assert.equal(val(a.prior.acceleratedUsed), 30); assert.equal(val(a.prior.acceleratedRemaining), 20); assert.equal(val(s.acceleration.additionalDeduction), 50); assert.equal(val(m.taxableProfit), 6260); assert.equal(val(s.taxableProfit), 6210); assert.equal(val(s.colecta), 1987.2); assert.deepEqual( s.acceleration.normal.input, s.acceleration.accelerated.input, ); }); void test('documented assetexit excludes eventmonth and keeps exact fractional quotas;cessation date does not edit exit', () => { const { d, b } = setup(); Object.assign(b.depreciation.assets[0], { exitState: 'known', exitDate: '2026-06-20', }); const a = calculateNormal(d, b).special.acceleration.monthly.assets[0]; assert.equal(a.months, 5); assert.deepEqual(a.normalQuota, fraction(125n, 3n)); assert.deepEqual(a.acceleratedQuota, fraction(125n, 2n)); assert.deepEqual(a.additionalDeduction, fraction(125n, 6n)); assert.deepEqual(a.prior.acceleratedUsed, fraction(5n, 2n)); assert.deepEqual(a.prior.acceleratedRemaining, fraction(95n, 2n)); }); void test('remaining fiscalbalance and accounted/priorcaps bind both calculations;available amount is never a second deduction', () => { const { d, b } = setup(); Object.assign(b.depreciation.assets[0], { previousAccounting: '1000', previousFiscal: '980', priorAvailable: '20', }); let a = calculateNormal(d, b).special.acceleration.monthly.assets[0]; assert.equal(val(a.normalDeduction), 20); assert.equal(val(a.acceleratedDeduction), 20); assert.equal(val(a.additionalDeduction), 0); assert.equal(val(a.prior.acceleratedFiscalRemaining), 0); const second = setup(), p = JSON.parse(second.d.business_plan_snapshot); p.plan.assetDepreciation = '0'; second.d.business_plan_snapshot = JSON.stringify(p); second.b.depreciation.assets[0].accounting = '0'; a = calculateNormal(second.d, second.b).special.acceleration.monthly .assets[0]; assert.equal(val(a.normalDeduction), 50); assert.equal(val(a.acceleratedDeduction), 50); assert.equal(val(a.additionalDeduction), 0); assert.equal(val(a.prior.acceleratedRemaining), 0); }); void test('missing support or missing priorbalance blocks only dependent CBF16;absence and negative remain explicit', () => { for (const key of ['reference', 'historyReference', 'monthlyFromStart']) { const { d, b } = setup(); b.special.acceleration.monthly.continuation[key] = ''; const m = calculateNormal(d, b); assert.equal(m.special.acceleration.additionalDeduction, null); assert.notEqual(m.colecta, null); } const missing = setup(); missing.b.depreciation.assets[0].previousFiscal = ''; const pending = calculateNormal(missing.d, missing.b); assert.equal(pending.special.acceleration.additionalDeduction, null); assert.match(pending.depreciation.pending.join(' '), /em falta/); assert.equal(missing.b.depreciation.assets[0].previousFiscal, ''); for (const state of ['negative', 'absent']) { const { d, b } = setup(); if (state === 'negative') b.special.acceleration.rows[0].eligible = 'no'; else { b.special.acceleration.rows = []; Object.assign(b.special.acceleration.qualification, { state: 'absent', eligible: '', }); } const s = calculateNormal(d, b).special; assert.equal(val(s.acceleration.additionalDeduction), 0); assert.equal( s.acceleration.status, state === 'absent' ? 'absent' : 'calculated', ); if (state === 'negative') assert.equal(s.acceleration.rows[0].status, 'not_applicable'); } }); void test('otherstartyears,nonmonthlyhistory,transition,cumulation and prioramounts for currentyearentry stay out of scope', () => { for (const mutate of [ (b) => (b.depreciation.assets[0].start = '2024-01-01'), (b) => (b.special.acceleration.monthly.continuation.monthlyFromStart = 'no'), (b) => (b.periodsFx.kind = 'transition'), (b) => (b.special.acceleration.noOtherBenefits = 'no'), (b) => (b.depreciation.assets[0].start = '2026-01-01'), ]) { const { d, b } = setup(); mutate(b); const m = calculateNormal(d, b); assert.equal(m.special.acceleration.additionalDeduction, null); assert.notEqual(m.colecta, null); } }); void test('save-recover13 preserves12result and allhistorical balances;recalc idempotent and reports reconciliation without officialconsumption', () => { const { d, b } = setup(), history = structuredClone(b.history), old = structuredClone(b.result), asset = JSON.stringify(b.depreciation), plan = d.business_plan_snapshot, others = JSON.parse(d.modelo_a_snapshot).irpcWithholdings; d.modelo_a_snapshot = saveNormalBook(d, recordNormal(d, b)); let back = normalBook(JSON.parse(JSON.stringify(d))); assert.equal(normalCurrent(d, back), true); assert.deepEqual(back.history, [...history, old]); assert.equal(old.model.version, 'IRPC-normal-adjustments/12'); assert.equal(JSON.stringify(back.depreciation), asset); assert.equal(d.business_plan_snapshot, plan); assert.deepEqual(JSON.parse(d.modelo_a_snapshot).irpcWithholdings, others); const html = normalHtml(d); assert.match(html, /2026-01-01–2026-06-30/); assert.match(html, /recuperada CBF16:15.00/); assert.match(html, /saldo anterior aritmético CBF16:35.00/); assert.match(html, /sem consumo de saldos oficiais/); assert.match(html, /colecta sobre a base:993.60/); assert.match(html, /Saldo final a pagar\/recuperar:Indeterminado/); assert.match( JSON.stringify( createProjectDossierReport(d, { selectedSections: new Set(['finance']), }), ), /quotas contabilísticas anteriores/, ); d.modelo_a_snapshot = saveNormalBook(d, recordNormal(d, back)); back = normalBook(d); assert.equal( val(back.result.model.special.acceleration.additionalDeduction), 25, ); assert.equal(back.depreciation.assets[0].priorAvailable, '50'); back.depreciation.assets[0].previousFiscal = '110'; assert.equal(normalCurrent(d, back), false); }); void test('invalid or overlapping optionalpreparation cannot replace preservedbooks', () => { for (const c of [ null, { version: 2, period: 'annual', reference: 'QA', historyReference: 'QA', monthlyFromStart: 'yes', }, { version: 1, period: 'other', reference: 'QA', historyReference: 'QA', monthlyFromStart: 'yes', }, ]) { const { b } = setup(); b.special.acceleration.monthly.continuation = c; assert.throws(() => validateSpecial(b.special), /não recuperável/); } const { b } = setup(); b.special.acceleration.monthly.startPeriod = { version: 1, reference: 'QA', }; assert.throws(() => validateSpecial(b.special), /incompatível/); }); void test('prepare only compatible new guardedAPI cases with missing,negative and absence variants', () => { const cases = { cessation: setup('cessation', false), prior: setup(), annual: setup('annual'), exit: setup(), missing: setup(), negative: setup(), absent: setup(), }; Object.assign(cases.exit.b.depreciation.assets[0], { exitState: 'known', exitDate: '2026-06-20', }); cases.missing.b.special.acceleration.monthly.continuation.historyReference = ''; cases.negative.b.special.acceleration.rows[0].eligible = 'no'; cases.absent.b.special.acceleration.rows = []; Object.assign(cases.absent.b.special.acceleration.qualification, { state: 'absent', eligible: '', }); fs.mkdirSync('outputs/irpc-l7-cessation-prior', { recursive: true }); for (const [name, { d, b }] of Object.entries(cases)) { d.modelo_a_snapshot = saveNormalBook( d, recordNormal(d, b, '2026-10-26T18:00:00+02:00'), ); fs.writeFileSync( 'outputs/irpc-l7-cessation-prior/' + name + '.json', JSON.stringify(d), ); fs.writeFileSync( 'outputs/irpc-l7-cessation-prior/' + name + '.html', normalHtml(d), ); } });