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

287 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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-monthly-preserved.json');
d.business_plan_snapshot = f.plan;
d.modelo_a_snapshot = saveNormalBook(d, f.book);
return { d, b: normalBook(d) };
}
function setup(month = 7) {
const { d, b } = accepted(),
p = JSON.parse(d.business_plan_snapshot);
p.plan.assetDepreciation = '120';
d.business_plan_snapshot = JSON.stringify(p);
const old = normalBook(read('irpc-l6-values-preserved.json'));
b.periodsFx = structuredClone(old.periodsFx);
b.periodsFx.from = '2026-' + String(month).padStart(2, '0') + '-01';
b.periodsFx.reference =
'QA início documentado em' +
b.periodsFx.from +
';meses completos até31/12';
delete b.forms;
const months = 13 - month;
b.schedules.interest.amount = String(months * 20);
Object.assign(b.depreciation.assets[0], {
accounting: String(months * 10),
start: b.periodsFx.from.slice(0, 8) + '15',
timing: 'months',
});
for (const part of [
b.special.expenses,
b.special.cooperative,
b.special.acceleration,
])
Object.assign(part.qualification, {
from: b.periodsFx.from,
periodReference: b.periodsFx.reference,
});
b.special.acceleration.monthly.startPeriod = {
version: 1,
reference:
'QA início de actividade residente2026,CBF16 e períodoL6 reconciliados',
};
return { d, b };
}
void test('accepted11 result and currentkey remain exact without startPeriod;10 histories still readable', () => {
const { d, b } = accepted();
assert.equal(normalCurrent(d, b), true);
assert.deepEqual(calculateNormal(d, b), b.result.model);
const old = read('irpc-l7-preserved.json').acceleration;
assert.equal(normalCurrent(old, normalBook(old)), true);
});
void test('resident July start reuses6 actualmonths,50 normalquota,75 accelerated capped60;only10 additional', () => {
const { d, b } = setup(),
asset = JSON.stringify(b.depreciation),
plan = d.business_plan_snapshot,
m = calculateNormal(d, b),
s = m.special,
a = s.acceleration.monthly.assets[0];
assert.equal(m.version, 'IRPC-normal-adjustments/12');
assert.equal(m.periodsFx.short, true);
assert.equal(a.months, 6);
assert.equal(val(a.normalQuota), 50);
assert.equal(val(a.acceleratedQuota), 75);
assert.equal(val(a.acceleratedDeduction), 60);
assert.equal(val(s.acceleration.additionalDeduction), 10);
assert.equal(val(m.taxableProfit), 3130);
assert.equal(val(m.taxableBase), 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(s.finalBalance.payable, null);
assert.equal(
s.acceleration.accelerated.assets.length,
b.depreciation.assets.length,
);
assert.equal(JSON.stringify(b.depreciation), asset);
assert.equal(d.business_plan_snapshot, plan);
assert.match(s.acceleration.source, /início de actividade/);
assert.doesNotMatch(s.acceleration.source, /primeiro exercício anual/);
});
void test('October and December use3 and1 months without annualisation and retain exact fiscalfraction', () => {
for (const [month, months, diff] of [
[10, 3, fraction(5n)],
[12, 1, fraction(5n, 3n)],
]) {
if (typeof month !== 'number' || typeof months !== 'number')
throw new Error('Invalid monthly fixture');
const { d, b } = setup(month),
m = calculateNormal(d, b),
a = m.special.acceleration.monthly.assets[0];
assert.equal(m.periodsFx.months, months);
assert.equal(a.months, months);
assert.deepEqual(a.additionalDeduction, diff);
assert.deepEqual(a.normalQuota, fraction(BigInt(months) * 25n, 3n));
d.modelo_a_snapshot = saveNormalBook(d, recordNormal(d, b));
assert.match(normalHtml(d), new RegExp('duodécimosL6:' + months));
if (month === 12) assert.match(normalHtml(d), /5\/3/);
}
});
void test('missing start evidence blocks only L7;absent and negative remain distinct withnormal totals', () => {
const missing = setup();
missing.b.special.acceleration.monthly.startPeriod.reference = '';
let m = calculateNormal(missing.d, missing.b);
assert.equal(m.special.acceleration.status, 'indeterminate');
assert.equal(m.special.acceleration.additionalDeduction, null);
assert.equal(val(m.colecta), 1001.6);
assert.match(
m.special.acceleration.reason,
/Falta suporte do período curto/,
);
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: '',
});
}
m = calculateNormal(d, b);
assert.equal(val(m.special.acceleration.additionalDeduction), 0);
assert.equal(
m.special.acceleration.status,
state === 'absent' ? 'absent' : 'calculated',
);
assert.equal(val(m.special.colecta), 1001.6);
if (state === 'negative')
assert.equal(
m.special.acceleration.rows[0].status,
'not_applicable',
);
}
});
void test('cessation,otherperiodkind,nonresident,previousyearentry,priorquotas and exits remain outside newrecorte', () => {
for (const mutate of [
(b) => (b.periodsFx.kind = 'same_year'),
(b) => {
b.periodsFx.kind = 'cessation';
b.periodsFx.from = '2026-01-01';
b.periodsFx.to = '2026-06-30';
},
(b) => (b.scope = 'ep_general'),
(b) => (b.depreciation.assets[0].start = '2026-06-01'),
(b) => (b.depreciation.assets[0].previousAccounting = '1'),
(b) => {
b.depreciation.assets[0].exitState = 'known';
b.depreciation.assets[0].exitDate = '2026-12-01';
},
]) {
const { d, b } = setup();
mutate(b);
const m = calculateNormal(d, b);
assert.equal(m.special.acceleration.additionalDeduction, null);
assert.equal(m.special.acceleration.status, 'indeterminate');
assert.notEqual(m.taxableProfit, null);
}
});
void test('short fiscalperiod remains unsupported in11 unless explicitly prepared;cancel returns same11 decision', () => {
const { d, b } = setup(),
period = JSON.stringify(b.periodsFx),
asset = JSON.stringify(b.depreciation);
delete b.special.acceleration.monthly.startPeriod;
const m = calculateNormal(d, b);
assert.equal(m.version, 'IRPC-normal-adjustments/11');
assert.equal(m.special.acceleration.additionalDeduction, null);
assert.match(m.special.acceleration.reason, /exercício anual civil2026/);
assert.equal(JSON.stringify(b.periodsFx), period);
assert.equal(JSON.stringify(b.depreciation), asset);
});
void test('save-recover12 keeps11result verbatim,originalassets,otherbooks and fractional report;recalc is idempotent', () => {
const { d, b } = setup(),
old = structuredClone(b.result),
history = structuredClone(b.history),
asset = JSON.stringify(b.depreciation),
plan = d.business_plan_snapshot,
others = JSON.parse(d.modelo_a_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, [...history, old]);
assert.deepEqual(back.history.at(-1), old);
assert.equal(
back.history.at(-1).model.version,
'IRPC-normal-adjustments/11',
);
assert.equal(JSON.stringify(back.depreciation), asset);
assert.equal(d.business_plan_snapshot, plan);
assert.deepEqual(
JSON.parse(d.modelo_a_snapshot).irpcWithholdings,
others.irpcWithholdings,
);
const html = normalHtml(d);
assert.match(html, /2026-07-01–2026-12-31/);
assert.match(html, /dedução adicional conciliada:10.00/);
assert.match(html, /colecta sobre a base:998.40/);
assert.match(html, /Saldo final a pagar\/recuperar:Indeterminado/);
assert.match(
JSON.stringify(
createProjectDossierReport(d, {
selectedSections: new Set(['finance']),
}),
),
/início de actividade/,
);
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), 998.4);
back.periodsFx.from = '2026-10-01';
assert.equal(normalCurrent(d, back), false);
});
void test('invalid startPeriod child cannot replace originalbooks', () => {
for (const child of [
null,
{ version: 2, reference: 'QA' },
{ version: 1, reference: 3 },
]) {
const { b } = setup();
b.special.acceleration.monthly.startPeriod = child;
assert.throws(() => validateSpecial(b.special), /não recuperável/);
}
});
void test('prepare only new guardedAPI cases and reports', () => {
const cases = {
start: setup(),
fraction: setup(12),
missing: setup(),
negative: setup(),
absent: setup(),
};
cases.missing.b.special.acceleration.monthly.startPeriod.reference = '';
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-start-period', { recursive: true });
for (const [name, { d, b }] of Object.entries(cases)) {
d.modelo_a_snapshot = saveNormalBook(
d,
recordNormal(d, b, '2026-10-26T17:00:00+02:00'),
);
fs.writeFileSync(
'outputs/irpc-l7-start-period/' + name + '.json',
JSON.stringify(d),
);
fs.writeFileSync(
'outputs/irpc-l7-start-period/' + name + '.html',
normalHtml(d),
);
}
});