Files
Investidor-V2/tests/frontend/irpc-normal-base.test.mjs
T

267 lines
9.8 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 {
newNormalBaseInput,
newNormalLoss,
calculateNormalBase,
} from '../../resources/js/lib/irpc-normal-base.ts';
import { fraction } from '../../resources/js/lib/fiscal-rational.ts';
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
const seed = () =>
JSON.parse(
fs.readFileSync(
new URL('./fixtures/irpc-profit-preserved.json', import.meta.url),
'utf8',
),
);
const loss = (id, year, assessed, used, requested) => ({
...newNormalLoss(id),
year,
subjectNuit: '123456789',
assessed,
previouslyUsed: used,
requested,
reference: 'QA saldo fiscal corrigido,sujeito/exercício ' + year,
kind: 'ordinary',
annualPeriods: 'yes',
generalActivity: 'yes',
balanceConfirmed: 'yes',
});
const input = () => ({
...newNormalBaseInput(),
subjectNuit: '123456789',
identityReference: 'QA sujeito do lucro e dos saldos',
method: 'direct',
methodReference: 'QA declaração e contabilidade organizada',
lossesState: 'known',
lossesReference: 'QA inventário completo dos saldos fiscais do sujeito',
unchangedObject: 'yes',
objectReference: 'QA actividade e objecto mantidos ao termo2026',
losses: [
loss('old', '2021', '1000', '200', '800'),
loss('new', '2024', '3000', '500', '1500'),
],
benefits: {
state: 'known',
amount: '0',
reference: 'QA deduções/benefícios à base,valor conhecido zero',
},
});
void test('6700 is reused as profit;2300 documented loss use yields4400 taxable base,never tax payable', () => {
const d = seed(),
b = normalBook(d);
b.base = input();
const m = calculateNormal(d, b);
assert.equal(m.taxableProfit.numerator, '6700');
assert.equal(m.base.requestedLosses.numerator, '2300');
assert.equal(m.base.deductedLosses.numerator, '2300');
assert.equal(m.taxableBase.numerator, '4400');
assert.equal(m.tax, null);
assert.deepEqual(
m.base.losses.map((l) => l.available.numerator),
['800', '2500'],
);
assert.deepEqual(
m.base.losses.map((l) => l.remaining.numerator),
['0', '1000'],
);
assert.deepEqual(
m.base.losses.map((l) => l.lastOrdinaryYear),
[2026, 2029],
);
});
void test('supported absence of losses and benefits retains6700 base;missing benefit information does not mean zero', () => {
const i = {
...input(),
lossesState: 'absent',
losses: [],
unchangedObject: '',
objectReference: '',
benefits: {
state: 'absent',
amount: '',
reference: 'QA sem benefício aplicável',
},
};
let m = calculateNormalBase(i, fraction(6700n), '2026');
assert.equal(m.taxableBase.numerator, '6700');
i.benefits = { state: '', amount: '', reference: '' };
m = calculateNormalBase(i, fraction(6700n), '2026');
assert.equal(m.taxableBase, null);
assert.equal(m.taxableProfit.numerator, '6700');
});
void test('expired ordinary2020 loss cannot be deducted;2021 remains within last ordinary year2026', () => {
const i = input();
i.losses = [loss('expired', '2020', '500', '0', '0')];
i.unchangedObject = '';
i.objectReference = '';
let m = calculateNormalBase(i, fraction(6700n), '2026');
assert.equal(m.losses[0].status, 'excluded');
assert.equal(m.losses[0].lastOrdinaryYear, 2025);
assert.equal(m.taxableBase.numerator, '6700');
i.losses[0].requested = '1';
assert.equal(
calculateNormalBase(i, fraction(6700n), '2026').taxableBase,
null,
);
});
void test('losses cannot exceed available balance or profit;no implicit allocation order or percentage cap', () => {
const i = input();
i.losses[0].requested = '801';
assert.equal(
calculateNormalBase(i, fraction(6700n), '2026').taxableBase,
null,
);
i.losses = [
loss('a', '2021', '5000', '0', '5000'),
loss('b', '2024', '5000', '0', '4000'),
];
let m = calculateNormalBase(i, fraction(6700n), '2026');
assert.equal(m.taxableBase, null);
assert.match(m.pending.join(' '), /reparta explicitamente/);
i.losses[0].requested = '2700';
m = calculateNormalBase(i, fraction(6700n), '2026');
assert.equal(m.taxableBase.numerator, '0');
assert.equal(m.deductedLosses.numerator, '6700');
});
void test('same taxpayer,annual periods,ordinary origin,reconciled balance and activity facts are specific dependencies', () => {
for (const patch of [
{ subjectNuit: '987654321' },
{ annualPeriods: 'no' },
{ generalActivity: 'no' },
{ balanceConfirmed: '' },
{ kind: 'transfer' },
{ year: '2026' },
{ previouslyUsed: '1100' },
{ requested: '' },
]) {
const i = input();
Object.assign(i.losses[0], patch);
assert.equal(
calculateNormalBase(i, fraction(6700n), '2026').taxableBase,
null,
);
}
});
void test('changed object explicitly excludes deduction;duplicate annual balances and indirect methods remain pending', () => {
const i = input();
i.unchangedObject = 'no';
i.losses.forEach((l) => (l.requested = '0'));
let m = calculateNormalBase(i, fraction(6700n), '2026');
assert.equal(m.taxableBase.numerator, '6700');
assert.ok(m.losses.every((l) => l.status === 'excluded'));
i.unchangedObject = 'yes';
i.losses[1].year = '2021';
assert.equal(
calculateNormalBase(i, fraction(6700n), '2026').taxableBase,
null,
);
i.losses[1].year = '2024';
i.method = 'indirect';
assert.equal(
calculateNormalBase(i, fraction(6700n), '2026').taxableBase,
null,
);
});
void test('positive known benefits are preserved without free subtraction;absence contradictions remain explicit', () => {
const i = input();
i.benefits = {
state: 'known',
amount: '200',
reference: 'QA benefício positivo,regra por corresponder',
};
let m = calculateNormalBase(i, fraction(6700n), '2026');
assert.equal(m.input.benefits.amount, '200');
assert.equal(m.taxableBase, null);
i.benefits = {
state: 'absent',
amount: '200',
reference: 'QA ausência contraditória',
};
assert.equal(
calculateNormalBase(i, fraction(6700n), '2026').taxableBase,
null,
);
i.benefits = {
state: 'known',
amount: '0',
reference: 'QA zero conhecido',
};
i.lossesState = 'absent';
assert.equal(
calculateNormalBase(i, fraction(6700n), '2026').taxableBase,
null,
);
});
void test('unknown or negative current profit is preserved,not transformed to zero or current-year loss carryforward', () => {
for (const p of [null, fraction(-100n)]) {
const m = calculateNormalBase(input(), p, '2026');
assert.equal(m.taxableBase, null);
assert.deepEqual(m.taxableProfit, p);
}
});
void test('legacy current result and all books survive;new3 history,save/recovery/dossier and repeated simulation do not consume official balances', () => {
const d = seed(),
legacy = normalBook(d);
assert.equal(normalCurrent(d, legacy), true);
assert.equal(legacy.result.model.version, 'IRPC-normal-adjustments/2');
const b = { ...legacy, base: input() },
before = JSON.parse(d.modelo_a_snapshot),
saved = recordNormal(d, b, '2026-10-25T23:00:00Z');
assert.deepEqual(saved.history.at(-1), legacy.result);
assert.equal(saved.result.model.version, 'IRPC-normal-adjustments/3');
d.modelo_a_snapshot = saveNormalBook(d, saved);
assert.deepEqual(
JSON.parse(d.modelo_a_snapshot).irpcWithholdings,
before.irpcWithholdings,
);
const back = JSON.parse(JSON.stringify(d));
assert.equal(normalCurrent(back, normalBook(back)), true);
assert.match(normalHtml(back), /matéria colectável simulada:4400/);
assert.match(normalHtml(back), /imposto a pagar:Indeterminado/);
assert.match(
JSON.stringify(
createProjectDossierReport(back, {
selectedSections: new Set(['finance']),
}),
),
/4400/,
);
const repeated = recordNormal(back, normalBook(back));
assert.equal(repeated.result.model.taxableBase.numerator, '4400');
assert.equal(repeated.base.losses[1].previouslyUsed, '500');
assert.equal(repeated.base.losses[1].requested, '1500');
const changed = normalBook(back);
changed.base.losses[0].requested = '700';
back.modelo_a_snapshot = saveNormalBook(back, changed);
assert.equal(normalCurrent(back, normalBook(back)), false);
assert.throws(() => normalHtml(back));
fs.mkdirSync('outputs/irpc-l6-base', { recursive: true });
fs.writeFileSync('outputs/irpc-l6-base/calculated.json', JSON.stringify(d));
fs.writeFileSync('outputs/irpc-l6-base/preparation.html', normalHtml(d));
const missing = normalBook(d);
missing.base.benefits = { state: '', amount: '', reference: '' };
const md = structuredClone(d);
md.modelo_a_snapshot = saveNormalBook(md, recordNormal(md, missing));
fs.writeFileSync('outputs/irpc-l6-base/missing.json', JSON.stringify(md));
});
void test('malformed future input versions and duplicate IDs fail without resetting originals', () => {
const d = seed(),
b = normalBook(d);
b.base = input();
b.base.version = 2;
assert.throws(() => saveNormalBook(d, b));
b.base.version = 1;
b.base.losses[1].id = b.base.losses[0].id;
assert.throws(() => saveNormalBook(d, b));
});