Files
Investidor-V2/tests/frontend/irpc-special-months.test.mjs
T

339 lines
13 KiB
JavaScript

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),
);
}
});