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

227 lines
8.1 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 { serializeBusinessPlanSnapshot } from '../../resources/js/lib/business-financial-plan.ts';
import {
normalBook,
newNormalBook,
newNormalAdjustment,
normalFinancialSource,
calculateNormal,
recordNormal,
normalCurrent,
saveNormalBook,
normalReportRows,
normalHtml,
} from '../../resources/js/lib/irpc-normal.ts';
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
import { irpcBook } from '../../resources/js/lib/irpc-withholdings.ts';
const plan = () => ({
currency: 'MZN',
openingCash: '0',
products: [
{
label: 'Serviço',
monthlyUnits: '10',
unitPrice: '100',
unitVariableCost: '30',
},
],
monthlyCollections: '800',
monthlyVariableCostPayments: '250',
monthlyOperatingExpenses: '100',
monthlyOperatingPayments: '80',
monthlyPayrollExpense: '50',
monthlyPayrollPayments: '40',
assetInvestment: '0',
inventory: '0',
receivables: '0',
payables: '0',
equity: '0',
shareholderLoans: '0',
bankLoanProceeds: '0',
grants: '0',
assetDepreciation: '0',
depreciationLifeMonths: 12,
monthlyInterestExpense: '10',
monthlyInterestPayment: '5',
monthlyPrincipalRepayment: '60',
periods: 12,
});
const draft = () => ({
...JSON.parse(
fs.readFileSync(
new URL('./fixtures/irpc-mrfta-preserved.json', import.meta.url),
'utf8',
),
),
fiscal_component: 'irpc',
business_plan_snapshot: serializeBusinessPlanSnapshot(plan()),
});
const row = (id = 'a') => ({
...newNormalAdjustment(id),
sourceCode: 'operating_expenses',
document: 'QA aviso multa sintético',
line: id,
amount: '100',
rule: 'noncontractual_fine',
included: 'yes',
reference:
'QA pressuposto:multa não contratual incluída no custo operacional',
});
const book = () => ({
...newNormalBook(),
year: '2026',
mappingReference: 'QA plano meses1–12=Jan–Dez2026',
scope: 'resident_general',
scopeReference: 'QA residente geral sem regime especial',
rows: [
row(),
{
...row('b'),
sourceCode: 'interest_expense',
amount: '20',
rule: 'compensatory_interest',
document: 'QA liquidação juros sintética',
reference:
'QA pressuposto:juros compensatórios incluídos no custo financeiro',
},
],
});
void test('L6 reuses reconciled profit and expense sources,without cash repayment or received withholding becoming expense', () => {
const d = draft(),
s = normalFinancialSource(d),
m = calculateNormal(d, book());
assert.equal(s.accountingProfitBeforeTax, '6480.00');
assert.equal(m.accountingProfit.numerator, '6480');
assert.equal(m.additions.numerator, '120');
assert.equal(m.partialAdjustedResult.numerator, '6600');
assert.equal(m.taxableProfit, null);
assert.equal(m.taxableBase, null);
assert.equal(m.tax, null);
assert.equal(m.origin, 'projected');
assert.match(m.pending.join(' '), /L6 parcial/);
const p = plan();
p.monthlyPrincipalRepayment = '0';
p.monthlyInterestPayment = '0';
d.business_plan_snapshot = serializeBusinessPlanSnapshot(p);
assert.deepEqual(
calculateNormal(d, book()).partialAdjustedResult,
m.partialAdjustedResult,
);
});
void test('missing qualification affects its own adjustment;confirmed additions remain visible without a full adjusted total', () => {
const b = book();
b.rows[1].reference = '';
const m = calculateNormal(draft(), b);
assert.equal(m.rows[0].status, 'calculated');
assert.equal(m.rows[1].adjustment, null);
assert.equal(m.additions.numerator, '100');
assert.equal(m.partialAdjustedResult, null);
b.rows[1].rule = 'other';
assert.equal(calculateNormal(draft(), b).rows[1].status, 'outside_scope');
});
void test('duplicate document lines and oversubscribed source costs cannot double count', () => {
const b = book();
b.rows.push({ ...b.rows[0], id: 'duplicate' });
let m = calculateNormal(draft(), b);
assert.equal(m.rows[0].adjustment, null);
assert.equal(m.rows[2].adjustment, null);
b.rows = [
{ ...row('one'), amount: '700' },
{ ...row('two'), amount: '600' },
];
m = calculateNormal(draft(), b);
assert.ok(m.rows.every((r) => r.adjustment === null));
assert.match(m.pending.join(' '), /excedem/);
});
void test('already excluded expenses,revenue sources,negative and overprecision amounts do not become additions', () => {
for (const patch of [
{ included: 'no' },
{ sourceCode: 'revenue' },
{ amount: '-10' },
{ amount: '1.001' },
{ rule: '' },
]) {
const b = book();
b.rows = [{ ...row(), ...patch }];
assert.equal(calculateNormal(draft(), b).rows[0].adjustment, null);
}
});
void test('year,mapping,currency,horizon and regime stay explicit;no global annual assessment inferred', () => {
for (const patch of [
{ year: '' },
{ year: '2025' },
{ mappingReference: '' },
{ scope: 'other' },
{ scopeReference: '' },
])
assert.equal(
calculateNormal(draft(), { ...book(), ...patch })
.partialAdjustedResult,
null,
);
for (const p of [
{ ...plan(), currency: 'USD' },
{ ...plan(), periods: 6 },
]) {
const d = draft();
d.business_plan_snapshot = serializeBusinessPlanSnapshot(p);
assert.equal(calculateNormal(d, book()).partialAdjustedResult, null);
}
const d = draft();
d.business_plan_snapshot = '';
assert.equal(calculateNormal(d, book()).accountingProfit, null);
assert.equal(
calculateNormal(d, { ...book(), rows: [] }).partialAdjustedResult,
null,
);
});
void test('calculation save recovery report preserves installed books and invalidates history on financial or adjustment changes', () => {
const d = draft(),
before = JSON.parse(d.modelo_a_snapshot),
b = recordNormal(d, book(), '2026-10-25T20:00:00Z');
d.modelo_a_snapshot = saveNormalBook(d, b);
const recovered = JSON.parse(JSON.stringify(d)),
saved = normalBook(recovered);
assert.equal(normalCurrent(recovered, saved), true);
assert.deepEqual(
JSON.parse(d.modelo_a_snapshot).irpcWithholdings,
before.irpcWithholdings,
);
assert.ok(irpcBook(d));
assert.match(JSON.stringify(normalReportRows(recovered)), /6600/);
assert.match(
JSON.stringify(
createProjectDossierReport(recovered, {
selectedSections: new Set(['finance']),
}),
),
/6600/,
);
assert.match(normalHtml(recovered), /Não é demonstração contabilística/);
const p = plan();
p.monthlyOperatingExpenses = '110';
recovered.business_plan_snapshot = serializeBusinessPlanSnapshot(p);
assert.equal(normalCurrent(recovered, saved), false);
assert.throws(() => normalHtml(recovered));
const next = recordNormal(recovered, saved);
assert.deepEqual(next.history[0], b.result);
assert.equal(next.result.model.partialAdjustedResult.numerator, '6480');
fs.mkdirSync('outputs/irpc-l6', { recursive: true });
fs.writeFileSync('outputs/irpc-l6/calculated.json', JSON.stringify(d));
fs.writeFileSync('outputs/irpc-l6/preparation.html', normalHtml(d));
});
void test('opportunities remain separate and malformed persisted data is preserved with explicit error', () => {
const d = draft();
d.modelo_a_snapshot = saveNormalBook(d, recordNormal(d, book()));
d.context_opportunity = 'another';
assert.equal(normalBook(d).rows.length, 0);
const b = book();
b.rows[0].amount = 100;
assert.throws(() => saveNormalBook(d, b));
const old = d.modelo_a_snapshot;
assert.throws(() => saveNormalBook(d, { ...book(), rows: [row(), row()] }));
assert.equal(d.modelo_a_snapshot, old);
});