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

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