227 lines
8.1 KiB
JavaScript
227 lines
8.1 KiB
JavaScript
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);
|
||
});
|