287 lines
11 KiB
JavaScript
287 lines
11 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 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),
|
||
);
|
||
}
|
||
});
|