251 lines
9.8 KiB
JavaScript
251 lines
9.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
normalBook,
|
|
normalFinancialSource,
|
|
recordNormal,
|
|
calculateNormal,
|
|
normalCurrent,
|
|
saveNormalBook,
|
|
normalHtml,
|
|
normalReportRows,
|
|
} from '../../resources/js/lib/irpc-normal.ts';
|
|
import {
|
|
newNormalProfitInput,
|
|
profitTopics,
|
|
} from '../../resources/js/lib/irpc-normal-profit.ts';
|
|
import {
|
|
restoreBusinessPlanSnapshot,
|
|
serializeBusinessPlanSnapshot,
|
|
} from '../../resources/js/lib/business-financial-plan.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
import {
|
|
irpcBook,
|
|
saveIrpcBook,
|
|
} from '../../resources/js/lib/irpc-withholdings.ts';
|
|
const seed = () =>
|
|
JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/irpc-normal-preserved.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
function example() {
|
|
const d = seed(),
|
|
b = normalBook(d),
|
|
profit = newNormalProfitInput();
|
|
b.rows[1].amount = '120';
|
|
b.rows[1].reference =
|
|
'QA todos120MZN de juros reconhecidos são compensatórios documentados;nenhuma taxa L5 calculada';
|
|
profit.completenessReference =
|
|
'QA plano cobre sujeito residente/exercício2026 completo,sem outros estabelecimentos,receitas,custos ou operações omitidos';
|
|
profit.lines = normalFinancialSource(d).accountingLines.map((l) => ({
|
|
code: l.code,
|
|
state: l.code === 'depreciation' ? 'absent' : 'known',
|
|
reference:
|
|
'QA qualificação ordinária/exercício/reconciliação de ' +
|
|
l.origin +
|
|
';restante custo indispensável sem outras exclusões',
|
|
}));
|
|
for (const [k] of profitTopics)
|
|
profit.topics[k] = {
|
|
state: 'absent',
|
|
amount: '',
|
|
reference: 'QA ausência verificada de ' + k,
|
|
};
|
|
profit.topics.incomeCorrections = {
|
|
state: 'known',
|
|
amount: '0',
|
|
reference:
|
|
'QA revisão de correcções ao rendimento:valor conhecido zero',
|
|
};
|
|
b.profit = profit;
|
|
return { d, b };
|
|
}
|
|
void test('documented financial result plus supported addbacks reaches scoped simulated taxable profit without tax/base', () => {
|
|
const { d, b } = example(),
|
|
m = calculateNormal(d, b);
|
|
assert.equal(m.accountingProfit.numerator, '6480');
|
|
assert.equal(m.additions.numerator, '220');
|
|
assert.equal(m.taxableProfit.numerator, '6700');
|
|
assert.equal(m.taxableBase, null);
|
|
assert.equal(m.tax, null);
|
|
assert.equal(m.profit.pending.length, 0);
|
|
assert.equal(
|
|
m.profit.lines.find((l) => l.code === 'interest_expense').remainder
|
|
.numerator,
|
|
'0',
|
|
);
|
|
assert.equal(
|
|
m.profit.lines.find((l) => l.code === 'operating_expenses').remainder
|
|
.numerator,
|
|
'1100',
|
|
);
|
|
});
|
|
void test('known zero,declared absence,and missing information keep distinct saved evidence and effects', () => {
|
|
const { d, b } = example();
|
|
b.profit.topics.incomeCorrections = {
|
|
state: 'known',
|
|
amount: '0',
|
|
reference: 'QA total revisto de correcções ao rendimento é zero',
|
|
};
|
|
let m = calculateNormal(d, b);
|
|
assert.equal(m.taxableProfit.numerator, '6700');
|
|
assert.equal(
|
|
m.profit.topics.find((t) => t.key === 'incomeCorrections').state,
|
|
'known',
|
|
);
|
|
assert.equal(
|
|
m.profit.topics.find((t) => t.key === 'positivePatrimonial').state,
|
|
'absent',
|
|
);
|
|
b.profit.topics.negativePatrimonial = {
|
|
state: '',
|
|
amount: '',
|
|
reference: '',
|
|
};
|
|
m = calculateNormal(d, b);
|
|
assert.equal(m.taxableProfit, null);
|
|
assert.equal(m.additions.numerator, '220');
|
|
assert.equal(
|
|
m.profit.topics.find((t) => t.key === 'negativePatrimonial').value,
|
|
null,
|
|
);
|
|
assert.match(m.profit.pending.join(' '), /sem zero presumido/);
|
|
});
|
|
void test('absence cannot override known nonzero financial values or silently discard an entered amount', () => {
|
|
const { d, b } = example();
|
|
b.profit.lines.find((l) => l.code === 'operating_expenses').state =
|
|
'absent';
|
|
assert.equal(calculateNormal(d, b).taxableProfit, null);
|
|
b.profit.lines.find((l) => l.code === 'operating_expenses').state = 'known';
|
|
b.profit.topics.positivePatrimonial.amount = '100';
|
|
assert.equal(calculateNormal(d, b).taxableProfit, null);
|
|
});
|
|
void test('known unimplemented variation,depreciation and remaining financial interest stay visible without presumed treatment', () => {
|
|
for (const patch of ['variation', 'depreciation', 'interest']) {
|
|
const { d, b } = example();
|
|
if (patch === 'variation')
|
|
b.profit.topics.positivePatrimonial = {
|
|
state: 'known',
|
|
amount: '500',
|
|
reference: 'QA variação500,regra específica por corresponder',
|
|
};
|
|
if (patch === 'depreciation') {
|
|
const p = restoreBusinessPlanSnapshot(d.business_plan_snapshot);
|
|
p.assetDepreciation = '120';
|
|
d.business_plan_snapshot = serializeBusinessPlanSnapshot(p);
|
|
b.profit.lines.find((l) => l.code === 'depreciation').state =
|
|
'known';
|
|
}
|
|
if (patch === 'interest') b.rows[1].amount = '20';
|
|
const m = calculateNormal(d, b);
|
|
assert.equal(m.taxableProfit, null);
|
|
assert.ok(m.profit.pending.length > 0);
|
|
if (patch === 'variation')
|
|
assert.equal(m.profit.topics[0].value.numerator, '500');
|
|
}
|
|
});
|
|
void test('complete line classifications and subject-year completeness are required,not a bare project subtotal', () => {
|
|
for (const patch of [
|
|
'line',
|
|
'reference',
|
|
'completeness',
|
|
'scope',
|
|
'year',
|
|
]) {
|
|
const { d, b } = example();
|
|
if (patch === 'line') b.profit.lines.pop();
|
|
if (patch === 'reference') b.profit.lines[0].reference = '';
|
|
if (patch === 'completeness') b.profit.completenessReference = '';
|
|
if (patch === 'scope') b.scope = 'other';
|
|
if (patch === 'year') b.year = '2025';
|
|
assert.equal(calculateNormal(d, b).taxableProfit, null);
|
|
}
|
|
});
|
|
void test('declared no addbacks can close only after full documented review,and fiscal losses are not clamped to zero', () => {
|
|
const { d, b } = example();
|
|
let p = restoreBusinessPlanSnapshot(d.business_plan_snapshot);
|
|
p.monthlyInterestExpense = '0';
|
|
d.business_plan_snapshot = serializeBusinessPlanSnapshot(p);
|
|
b.rows = [];
|
|
b.profit.lines.find((l) => l.code === 'interest_expense').state = 'absent';
|
|
assert.equal(calculateNormal(d, b).taxableProfit.numerator, '6600');
|
|
p.products[0].unitPrice = '1';
|
|
d.business_plan_snapshot = serializeBusinessPlanSnapshot(p);
|
|
assert.equal(calculateNormal(d, b).taxableProfit.numerator, '-5280');
|
|
});
|
|
void test('L5 uncertainty does not block independent known expense classification or annual profit', () => {
|
|
const { d, b } = example(),
|
|
s = JSON.parse(d.modelo_a_snapshot);
|
|
for (const v of Object.values(s.irpcWithholdings ?? {})) {
|
|
if (v.mrfta) {
|
|
v.mrfta.codes = [];
|
|
v.mrfta.interest.mode = '';
|
|
v.mrfta.interest.calculation = undefined;
|
|
}
|
|
}
|
|
d.modelo_a_snapshot = JSON.stringify(s);
|
|
assert.equal(calculateNormal(d, b).taxableProfit.numerator, '6700');
|
|
});
|
|
void test('old result remains current and unchanged;new closure history and stale-input guards survive roundtrip and dossier', () => {
|
|
const legacy = seed(),
|
|
old = structuredClone(normalBook(legacy));
|
|
assert.equal(normalCurrent(legacy, old), true);
|
|
assert.equal(old.result.model.version, 'IRPC-normal-adjustments/1');
|
|
const { d, b } = example();
|
|
const saved = recordNormal(d, b, '2026-10-25T22:00:00Z');
|
|
assert.deepEqual(saved.history.at(-1), old.result);
|
|
const before = JSON.parse(d.modelo_a_snapshot);
|
|
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), /lucro tributável simulado:6700/);
|
|
assert.match(
|
|
JSON.stringify(
|
|
createProjectDossierReport(back, {
|
|
selectedSections: new Set(['finance']),
|
|
}),
|
|
),
|
|
/6700/,
|
|
);
|
|
assert.match(JSON.stringify(normalReportRows(back)), /Ausência declarada/);
|
|
const l5 = irpcBook(back);
|
|
back.modelo_a_snapshot = saveIrpcBook(back, l5);
|
|
assert.deepEqual(normalBook(back), saved);
|
|
normalBook(back).profit.lines[0].reference = '';
|
|
const changed = normalBook(back);
|
|
changed.profit.lines[0].reference = '';
|
|
back.modelo_a_snapshot = saveNormalBook(back, changed);
|
|
assert.equal(normalCurrent(back, normalBook(back)), false);
|
|
assert.throws(() => normalHtml(back));
|
|
fs.mkdirSync('outputs/irpc-l6-profit', { recursive: true });
|
|
fs.writeFileSync(
|
|
'outputs/irpc-l6-profit/calculated.json',
|
|
JSON.stringify(d),
|
|
);
|
|
fs.writeFileSync('outputs/irpc-l6-profit/preparation.html', normalHtml(d));
|
|
const missing = normalBook(d);
|
|
missing.profit.topics.negativePatrimonial = {
|
|
state: '',
|
|
amount: '',
|
|
reference: '',
|
|
};
|
|
const md = structuredClone(d);
|
|
md.modelo_a_snapshot = saveNormalBook(md, recordNormal(md, missing));
|
|
fs.writeFileSync('outputs/irpc-l6-profit/missing.json', JSON.stringify(md));
|
|
});
|
|
void test('malformed added evidence and unsupported stored versions fail without resetting the book', () => {
|
|
const { d, b } = example();
|
|
b.profit.topics.positivePatrimonial.amount = 100;
|
|
assert.throws(() => saveNormalBook(d, b));
|
|
b.profit.topics.positivePatrimonial.amount = '';
|
|
b.profit.lines.push({ ...b.profit.lines[0] });
|
|
assert.throws(() => saveNormalBook(d, b));
|
|
});
|