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 frozen = () => JSON.parse( fs.readFileSync( new URL('./fixtures/irpc-l7-preserved.json', import.meta.url), 'utf8', ), ); const val = (v) => (v ? Number(v.numerator) / Number(v.denominator) : null); const q = (state) => ({ state, eligible: state === 'known' ? 'yes' : '', from: '2026-01-01', to: '2026-12-31', reference: 'QA condições/ausência documentadas no sujeito/projecto', periodReference: 'QA ano civil2026,CBF16 e Decreto72/2013art9', }); function setup(account = '60', start = '2026-07-15') { const d = frozen().acceleration, b = normalBook(d), p = JSON.parse(d.business_plan_snapshot); p.plan.assetDepreciation = account; d.business_plan_snapshot = JSON.stringify(p); Object.assign(b.depreciation.assets[0], { accounting: account, start, timing: 'months', }); b.special.acceleration.monthly = { version: 1, reference: 'QA opção de duodécimos desde a entrada emuso,datas/meses do mesmo activoL6', }; return { d, b }; } void test('accepted10 books remain current and reproduce exact original expense,cooperative and annualasset models;history is read unchanged', () => { for (const [name, d] of Object.entries(frozen())) { const b = normalBook(d), original = structuredClone(b.result); assert.equal(normalCurrent(d, b), true, name); assert.deepEqual(calculateNormal(d, b), original.model, name); assert.deepEqual(normalBook(d).result, original, name); } }); void test('explicit monthly11 reuses6 L6months and limits75quota to60accounted,adding only10deduction', () => { const { d, b } = setup(), original = JSON.stringify(b.depreciation), m = calculateNormal(d, b), s = m.special, a = s.acceleration.monthly.assets[0]; assert.equal(m.version, 'IRPC-normal-adjustments/11'); assert.equal(a.months, 6); assert.equal(val(a.normalRate), 10); assert.equal(val(a.acceleratedRate), 15); assert.equal(val(a.normalQuota), 50); assert.equal(val(a.acceleratedQuota), 75); assert.equal(val(a.normalDeduction), 50); assert.equal(val(a.acceleratedDeduction), 60); assert.equal(val(a.additionalDeduction), 10); assert.equal(val(m.taxableProfit), 6310); assert.equal(val(m.colecta), 2019.2); assert.equal(val(s.taxableProfit), 6300); assert.equal(val(s.taxableBase), 6300); assert.equal(val(s.colecta), 2016); assert.equal(s.finalBalance.payable, null); assert.equal(JSON.stringify(b.depreciation), original); }); void test('month timing comes from L6 including midmonth entry,January,October and December;never dailyproration', () => { for (const [start, account, months, normal, accelerated] of [ ['2026-01-01', '120', 12, 100, 150], ['2026-07-31', '60', 6, 50, 75], ['2026-10-20', '60', 3, 25, 37.5], ['2026-12-31', '20', 1, 100 / 12, 12.5], ]) { const { d, b } = setup(account, start), m = calculateNormal(d, b), a = m.special.acceleration.monthly.assets[0]; assert.equal(a.months, months); assert.equal(val(a.normalQuota), normal); assert.equal(val(a.acceleratedQuota), accelerated); assert.equal(a.months, m.depreciation.assets[0].months); } }); void test('December retains exact fractional difference and report never rounds fiscal amount', () => { const { d, b } = setup('20', '2026-12-15'), m = calculateNormal(d, b), a = m.special.acceleration.monthly.assets[0]; assert.deepEqual(a.normalQuota, fraction(25n, 3n)); assert.deepEqual(a.acceleratedQuota, fraction(25n, 2n)); assert.deepEqual(a.additionalDeduction, fraction(25n, 6n)); d.modelo_a_snapshot = saveNormalBook(d, recordNormal(d, b)); assert.match(normalHtml(d), /25\/6/); assert.match(normalHtml(d), /≈/); }); void test('accounted caps of0,40,60,100 remain binding and no additional quota is invented', () => { for (const [account, normal, accelerated, diff] of [ ['0', 0, 0, 0], ['40', 40, 40, 0], ['60', 50, 60, 10], ['100', 50, 75, 25], ]) { const { d, b } = setup(account), s = calculateNormal(d, b).special, a = s.acceleration.monthly.assets[0]; assert.equal(val(a.normalDeduction), normal); assert.equal(val(a.acceleratedDeduction), accelerated); assert.equal(val(s.acceleration.additionalDeduction), diff); } }); void test('new project building reuses land/residual basis and existing2percent table rate with monthlycap', () => { const { d, b } = setup('50'); Object.assign(b.depreciation.assets[0], { category: 'commercial_building', cost: '5000', land: '1000', rate: '2', }); b.special.acceleration.rows[0].kind = 'new_project_building'; const a = calculateNormal(d, b).special.acceleration.monthly.assets[0]; assert.equal(val(a.taxBasis), 4000); assert.equal(val(a.normalQuota), 40); assert.equal(val(a.acceleratedQuota), 60); assert.equal(val(a.acceleratedDeduction), 50); assert.equal(val(a.additionalDeduction), 10); }); void test('other existing assets stay on their normal quota;selected asset is neither copied nor deducted twice', () => { const { d, b } = setup('80'), other = structuredClone(b.depreciation.assets[0]); b.depreciation.assets[0].accounting = '60'; Object.assign(other, { id: 'other', assetKey: 'other', document: 'QA-other', description: 'Mobiliário foraCBF16', category: 'furniture', cost: '200', rate: '10', start: '2026-01-01', timing: 'annual', accounting: '20', }); b.depreciation.assets.push(other); const s = calculateNormal(d, b).special; assert.equal(s.acceleration.normal.assets.length, 2); assert.equal(s.acceleration.accelerated.assets.length, 2); assert.deepEqual( s.acceleration.normal.assets[1], s.acceleration.accelerated.assets[1], ); assert.equal(val(s.acceleration.additionalDeduction), 10); }); void test('missingmonthlysupport blocks only CBF16 totals;normalreference and storedinput are retained', () => { const { d, b } = setup(); b.special.acceleration.monthly.reference = ''; const m = calculateNormal(d, b); assert.equal(m.special.acceleration.status, 'indeterminate'); assert.equal(m.special.acceleration.additionalDeduction, null); assert.equal(val(m.colecta), 2019.2); assert.equal( m.special.acceleration.monthly.assets[0].acceleratedQuota, null, ); assert.match(m.special.acceleration.reason, /Falta suporte/); }); void test('negative condition,declaredabsence and missingqualification have distinct monthly outcomes', () => { const negative = setup(); negative.b.special.acceleration.rows[0].eligible = 'no'; let s = calculateNormal(negative.d, negative.b).special; assert.equal(s.acceleration.rows[0].status, 'not_applicable'); assert.equal(val(s.acceleration.additionalDeduction), 0); assert.equal(s.acceleration.monthly.assets[0].applied, false); assert.equal(s.acceleration.monthly.assets[0].acceleratedRate, null); for (const state of ['absent', '']) { const { d, b } = setup(); b.special.acceleration.rows = []; b.special.acceleration.qualification = q(state); b.special.acceleration.monthly.reference = ''; s = calculateNormal(d, b).special; assert.equal( s.acceleration.status, state === 'absent' ? 'absent' : 'indeterminate', ); assert.equal( val(s.acceleration.additionalDeduction), state === 'absent' ? 0 : null, ); } }); void test('newmonthlyscope excludes priorquotas,annualoption,exits and earlier/laterentry while retaining L6 reference', () => { for (const mutate of [ (a) => (a.previousAccounting = '1'), (a) => (a.timing = 'annual'), (a) => { a.exitState = 'known'; a.exitDate = '2026-12-01'; }, (a) => (a.start = '2025-07-01'), (a) => (a.start = '2027-01-01'), ]) { const { d, b } = setup(); mutate(b.depreciation.assets[0]); const m = calculateNormal(d, b); assert.equal(m.special.acceleration.additionalDeduction, null); assert.notEqual(m.taxableProfit, null); assert.equal(m.special.acceleration.status, 'indeterminate'); } }); void test('short fiscalperiod already supported in L6 is not silently included in this annual CBF16 increment', () => { const d = JSON.parse( fs.readFileSync( new URL( './fixtures/irpc-l6-values-preserved.json', import.meta.url, ), 'utf8', ), ), b = normalBook(d), model = setup().b.special; b.special = structuredClone(model); for (const part of [ b.special.expenses, b.special.cooperative, b.special.acceleration, ]) part.qualification = { ...part.qualification, from: '2026-07-01' }; Object.assign(b.depreciation.assets[0], { category: 'machine', cost: '1000', rate: '10', accounting: '60', timing: 'months', start: '2026-07-01', }); const m = calculateNormal(d, b); assert.notEqual(m.colecta, null); assert.equal(m.special.acceleration.additionalDeduction, null); assert.match(m.special.acceleration.reason, /exercício anual civil2026/); }); void test('monthlyexplicitoption never selects CBF from location;missingeligibility evidence remains pending', () => { const { d, b } = setup(); d.province_id = 'QA-ZEE-candidate'; b.special.acceleration.recognitionReference = ''; const m = calculateNormal(d, b); assert.equal(m.special.acceleration.additionalDeduction, null); assert.notEqual(m.colecta, null); assert.match(m.special.acceleration.reason, /reconhecimento10/); }); void test('version11 save-recover preserves10history,originalasset,plan and otherbooks;recalculate stays idempotent', () => { const { d, b } = setup(), old = structuredClone(b.result), asset = JSON.stringify(b.depreciation), plan = d.business_plan_snapshot; 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.at(-1), old); assert.equal( back.history.at(-1).model.version, 'IRPC-normal-adjustments/10', ); assert.equal(JSON.stringify(back.depreciation), asset); assert.equal(d.business_plan_snapshot, plan); assert.ok(JSON.parse(d.modelo_a_snapshot).irpcWithholdings); const html = normalHtml(d); assert.match(html, /duodécimosL6:6/); assert.match(html, /diferença adicional:10.00/); assert.match(html, /Saldo final a pagar\/recuperar:Indeterminado/); assert.match( JSON.stringify( createProjectDossierReport(d, { selectedSections: new Set(['finance']), }), ), /CBF16 por duodécimos/, ); d.modelo_a_snapshot = saveNormalBook(d, recordNormal(d, back)); back = normalBook(d); assert.equal( val(back.result.model.special.acceleration.additionalDeduction), 10, ); assert.equal(val(back.result.model.special.colecta), 2016); back.depreciation.assets[0].start = '2026-10-01'; assert.equal(normalCurrent(d, back), false); }); void test('malformed optionalmonthly schema cannot overwrite oldbooks', () => { for (const value of [ null, { version: 2, reference: 'QA' }, { version: 1, reference: 12 }, ]) { const { b } = setup(); b.special.acceleration.monthly = value; assert.throws(() => validateSpecial(b.special), /não recuperável/); } }); void test('newmonthlycases only prepare guardedprivateAPI inputs and reports', () => { const cases = { monthly: setup(), fraction: setup('20', '2026-12-15'), missing: setup(), negative: setup(), absent: setup(), }; cases.missing.b.special.acceleration.monthly.reference = ''; cases.negative.b.special.acceleration.rows[0].eligible = 'no'; cases.absent.b.special.acceleration.qualification = q('absent'); cases.absent.b.special.acceleration.rows = []; fs.mkdirSync('outputs/irpc-l7-months', { recursive: true }); for (const [name, { d, b }] of Object.entries(cases)) { d.modelo_a_snapshot = saveNormalBook( d, recordNormal(d, b, '2026-10-26T16:00:00+02:00'), ); fs.writeFileSync( 'outputs/irpc-l7-months/' + name + '.json', JSON.stringify(d), ); fs.writeFileSync( 'outputs/irpc-l7-months/' + name + '.html', normalHtml(d), ); } });