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

494 lines
18 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 {
normalBook,
normalCurrent,
calculateNormal,
recordNormal,
saveNormalBook,
normalHtml,
normalFinancialSource,
} from '../../resources/js/lib/irpc-normal.ts';
import {
newNormalFormPreparation,
reuseDirectFormAllocations,
normalFormRepresentation,
prepareNormalForms,
} from '../../resources/js/lib/irpc-normal-form-preparation.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-periods-fx-preserved.json',
import.meta.url,
),
'utf8',
),
),
val = (f) => (f ? Number(f.numerator) / Number(f.denominator) : null);
function setup() {
const d = seed(),
b = normalBook(d);
b.forms = {
...newNormalFormPreparation(),
edition: 'accepted_pdf_and_m20a1',
reference:
'QA PDFM22/Q10–12 e M20A.1/Q4;edições inventariadas distintas',
completenessReference:
'QA contas completas da oportunidade/sujeito no período;sem outros custos ou proveitos omitidos',
allocations: [
...reuseDirectFormAllocations(normalFinancialSource(d)),
{
id: 'revenue',
sourceCode: 'revenue',
account: '7.2',
amount: '6000',
reference:
'QA prestação de serviços documentada,sem vendas de bens',
},
{
id: 'opex',
sourceCode: 'operating_expenses',
account: '6.3',
amount: '600',
reference:
'QA fornecimentos e serviços de terceiros completos no período',
},
],
incomeTax: {
state: 'absent',
amount: '',
placement: '',
reference:
'QA ausência documentada de imposto contabilizado;não inferir gasto da colecta',
},
reducedRate: {
state: 'absent',
reference:
'QA sujeito/período sem parcela de imposto a taxa reduzida',
},
};
return { d, b };
}
const field = (m, code) =>
m.formPreparation.fields.find((f) => f.code === code);
void test('exact representation keeps signedfraction,approximation,zero,missing and parentheses distinct without fiscal rounding', () => {
for (const f of [
fraction(1n, 3n),
fraction(-1n, 300n),
fraction(123456789012345678901n, 100n),
]) {
const r = normalFormRepresentation(f, 'whole');
assert.equal(r.exact, f.numerator + '/' + f.denominator + ' MZN');
assert.equal(r.represented, null);
assert.equal(r.ready, false);
}
assert.match(normalFormRepresentation(fraction(1n, 3n)).display, /≈/);
assert.equal(normalFormRepresentation(fraction(0n)).represented, '0');
assert.equal(normalFormRepresentation(null).display, 'Indeterminado');
assert.equal(
normalFormRepresentation(fraction(-20n), 'whole', true).represented,
'(20)',
);
assert.match(
normalFormRepresentation(fraction(1n, 2n)).reason,
/exacto ao cêntimo/,
);
});
void test('optional9 prepares M20 classifications/netresult and M22 mappedadjustments while preserving all existing totals', () => {
const { d, b } = setup(),
before = normalBook(d).result.model,
m = calculateNormal(d, b);
assert.equal(m.version, 'IRPC-normal-adjustments/9');
for (const k of [
'accountingProfit',
'taxableProfit',
'taxableBase',
'colecta',
])
assert.deepEqual(m[k], before[k]);
assert.equal(val(m.formPreparation.netResult), 3120);
assert.equal(val(field(m, '200').value), 3120);
assert.equal(field(m, '200').transcriptionReady, false);
assert.equal(val(field(m, '213').value), 0);
assert.equal(val(field(m, '206').value), 10);
assert.equal(val(field(m, '238').value), 3130);
assert.equal(val(field(m, '258').value), 0);
assert.equal(m.formPreparation.reconciliation.matches, true);
assert.equal(field(m, '259').application, 'not_applicable');
assert.equal(field(m, '260').transcriptionReady, true);
assert.equal(val(field(m, '302').value), 1001.6);
assert.equal(field(m, '302').representation.ready, true);
assert.equal(field(m, '309–316').value, null);
assert.equal(m.assessment.finalBalance.payable, null);
});
void test('missingorcontradictory booktax blocks only netresult and its fieldreconciliation; knownnormalprofit/base/colecta preserved', () => {
for (const tax of [
{ state: '', amount: '', placement: '', reference: '' },
{
state: 'absent',
amount: '320',
placement: 'outside',
reference: 'QA',
},
{
state: 'known',
amount: '320',
placement: 'included',
reference: 'QA',
},
]) {
const { d, b } = setup();
b.forms.incomeTax = tax;
const m = calculateNormal(d, b);
assert.equal(m.formPreparation.netResult, null);
assert.equal(field(m, '200').value, null);
assert.equal(val(field(m, '260').value), 3130);
assert.equal(val(field(m, '300').value), 1001.6);
assert.equal(val(m.colecta), 1001.6);
}
});
void test('incomeTaxalreadyincluded reconciles account8.5 and mappedtax without subtracting it again', () => {
const { d, b } = setup();
b.forms.incomeTax = {
state: 'known',
amount: '320',
placement: 'included',
reference: 'QA imposto reconhecido no plano',
};
b.forms.allocations.find((a) => a.id === 'opex').amount = '280';
b.forms.allocations.push({
id: 'tax',
sourceCode: 'operating_expenses',
account: '8.5',
amount: '320',
reference: 'QA imposto já no custo de origem',
});
b.schedules.rows.push({
id: 'tax',
rule: 'tax_own',
amount: '320',
sourceCode: 'operating_expenses',
document: 'QA-tax',
line: '1',
reference: 'QA imposto sobre lucro já incluído',
fiscalYear: '',
conditions: 'yes',
});
Object.assign(b.profit.topics.expenseCorrections, {
state: 'known',
amount: '320',
reference: 'QA corresponde ao imposto contabilizado já no resultado',
});
const m = calculateNormal(d, b);
assert.equal(val(m.formPreparation.netResult), 3120);
assert.equal(val(field(m, '213').value), 0);
assert.equal(val(m.taxableProfit), 3450);
assert.equal(field(m, '238').value, null);
assert.equal(m.formPreparation.reconciliation.matches, false);
assert.ok(m.formPreparation.unmapped.some((e) => e.reason.includes('213')));
});
void test('incomplete revenueclassification does not erase directM20 fields or independentM22 calculations', () => {
const { d, b } = setup();
b.forms.allocations.find((a) => a.id === 'revenue').amount = '5999';
const m = calculateNormal(d, b);
assert.equal(
m.formPreparation.m20.find((f) => f.account === '7.2').value,
null,
);
assert.equal(
val(m.formPreparation.m20.find((f) => f.account === '6.5').value),
60,
);
assert.equal(m.formPreparation.netResult, null);
assert.equal(val(m.taxableProfit), 3130);
assert.equal(val(field(m, '300').value), 1001.6);
});
void test('fractional M20 allocations remain exact; no truncation or rounding changes source or net/taxable totals', () => {
const { d, b } = setup();
b.forms.allocations.find((a) => a.id === 'opex').amount = '300.50';
b.forms.allocations.push({
id: 'other',
sourceCode: 'operating_expenses',
account: '6.8',
amount: '299.50',
reference: 'QA gastos operacionais restantes documentados',
});
const m = calculateNormal(d, b),
f = m.formPreparation.m20.find((f) => f.account === '6.3');
assert.deepEqual(f.value, fraction(601n, 2n));
assert.equal(f.representation.ready, false);
assert.equal(f.representation.represented, null);
assert.equal(val(m.formPreparation.netResult), 3120);
assert.equal(val(m.colecta), 1001.6);
});
void test('lossuses positiveM22 lossfield and M20parentheses; no loss in profitfield and no negative taxablebase', () => {
const { d, b } = setup(),
p = JSON.parse(d.business_plan_snapshot);
p.plan.products[0].unitPrice = '20';
b.schedules.currentLossReference =
'QA perda fiscal corrente reconciliada no sujeito/período,sem reporte automático';
d.business_plan_snapshot = JSON.stringify(p);
b.forms.allocations.find((a) => a.id === 'revenue').amount = '1200';
const m = calculateNormal(d, b);
assert.equal(val(m.taxableProfit), -1670);
assert.equal(val(field(m, '259').value), 1670);
assert.equal(field(m, '260').application, 'not_applicable');
assert.equal(m.formPreparation.netRepresentation.represented, '(1680)');
assert.equal(val(m.taxableBase), 0);
assert.equal(val(m.colecta), 0);
});
void test('unmapped priorquota deduction keeps demonstrated effect and existing260/278/300; no guessedblankfield', () => {
const { d, b } = setup(),
p = JSON.parse(d.business_plan_snapshot);
p.plan.assetDepreciation = '48';
d.business_plan_snapshot = JSON.stringify(p);
Object.assign(b.depreciation.assets[0], {
accounting: '24',
start: '2025-01-01',
previousAccounting: '120',
previousFiscal: '80',
priorAvailable: '40',
});
b.forms.allocations.find((a) => a.sourceCode === 'depreciation').amount =
'24';
const m = calculateNormal(d, b);
assert.equal(val(m.depreciation.adjustment), -26);
assert.equal(val(m.colecta), 1001.6);
assert.ok(
m.formPreparation.unmapped.some((e) =>
e.origin.startsWith('Mapa de amortizações'),
),
);
assert.equal(field(m, '258').value, null);
assert.equal(field(m, '238').value, null);
assert.equal(val(field(m, '260').value), 3130);
assert.equal(
m.formPreparation.entries.find((e) =>
e.origin.startsWith('Mapa de amortizações'),
).code,
null,
);
});
void test('reducedrate unknown or knowninL7 blocks only302,notnormal300 and not finalL8 assumptions', () => {
for (const state of ['', 'known']) {
const { d, b } = setup();
b.forms.reducedRate.state = state;
const m = calculateNormal(d, b);
assert.equal(field(m, '302').value, null);
assert.equal(val(field(m, '300').value), 1001.6);
assert.equal(val(m.colecta), 1001.6);
assert.equal(m.assessment.finalBalance.recoverable, null);
}
});
void test('save/recover/report preserves8history and originalplan and never claims officialrounding or crosseditiontransfer', () => {
const { d, b } = setup(),
original = d.business_plan_snapshot,
old = structuredClone(b.result);
assert.equal(normalCurrent(d, normalBook(d)), true);
d.modelo_a_snapshot = saveNormalBook(
d,
recordNormal(d, b, '2026-10-26T10:00:00+02:00'),
);
const back = normalBook(JSON.parse(JSON.stringify(d)));
assert.equal(normalCurrent(d, back), true);
assert.deepEqual(back.history.at(-1), old);
assert.equal(d.business_plan_snapshot, original);
assert.match(normalHtml(d), /campo213/);
assert.match(normalHtml(d), /transporte automático M20/);
assert.match(normalHtml(d), /exacto ao cêntimo/);
assert.match(
JSON.stringify(
createProjectDossierReport(d, {
selectedSections: new Set(['finance']),
}),
),
/campo302/,
);
back.forms.allocations[0].reference += 'changed';
assert.equal(normalCurrent(d, back), false);
});
void test('fivenewcases write privateAPI proof inputs and reports without rerunning accepted functionalsuites', () => {
const cases = {
mapped: setup(),
fractional: setup(),
missing: setup(),
unmapped: setup(),
booktax: setup(),
};
cases.booktax.b.forms.incomeTax = {
state: 'known',
amount: '320',
placement: 'outside',
reference:
'QA gasto contabilístico exterior ao plano;campo próprio pendente',
};
cases.fractional.b.forms.allocations.find((a) => a.id === 'opex').amount =
'300.50';
cases.fractional.b.forms.allocations.push({
id: 'other',
sourceCode: 'operating_expenses',
account: '6.8',
amount: '299.50',
reference: 'QA parcela contabilística com centavos',
});
cases.missing.b.forms.incomeTax = {
state: '',
amount: '',
placement: '',
reference: '',
};
const u = cases.unmapped,
p = JSON.parse(u.d.business_plan_snapshot);
p.plan.assetDepreciation = '48';
u.d.business_plan_snapshot = JSON.stringify(p);
Object.assign(u.b.depreciation.assets[0], {
accounting: '24',
start: '2025-01-01',
previousAccounting: '120',
previousFiscal: '80',
priorAvailable: '40',
});
u.b.forms.allocations.find((a) => a.sourceCode === 'depreciation').amount =
'24';
fs.mkdirSync('outputs/irpc-l6-values-forms', { recursive: true });
for (const [name, { d, b }] of Object.entries(cases)) {
d.modelo_a_snapshot = saveNormalBook(
d,
recordNormal(d, b, '2026-10-26T10:00:00+02:00'),
);
fs.writeFileSync(
'outputs/irpc-l6-values-forms/' + name + '.json',
JSON.stringify(d),
);
fs.writeFileSync(
'outputs/irpc-l6-values-forms/' + name + '.html',
normalHtml(d),
);
}
});
void test('newlyidentified includedtax mismatch blocks only dependent213/reconciliation without revising demonstratedtax', () => {
const { d, b } = setup();
b.forms.incomeTax = {
state: 'known',
amount: '320',
placement: 'included',
reference: 'QA imposto reconhecido no plano',
};
b.forms.allocations.find((a) => a.id === 'opex').amount = '280';
b.forms.allocations.push({
id: 'tax',
sourceCode: 'operating_expenses',
account: '8.5',
amount: '320',
reference: 'QA imposto reconhecido no custo da origem',
});
const m = calculateNormal(d, b);
assert.equal(val(m.colecta), 1001.6);
assert.equal(val(m.formPreparation.netResult), 3120);
assert.equal(val(field(m, '213').value), 0);
assert.equal(field(m, '238').value, null);
assert.equal(val(field(m, '260').value), 3130);
assert.ok(m.formPreparation.pending.some((s) => s.includes('tax_own')));
});
void test('positive and negative asseteffects remain separate; netdepreciation is never all assignedto206', () => {
const { d, b } = setup(),
a = structuredClone(b.depreciation.assets[0]);
Object.assign(b.depreciation.assets[0], { cost: '60', accounting: '36' });
Object.assign(a, {
id: 'other',
assetKey: 'other',
document: 'QA-other-asset',
accounting: '24',
start: '2025-01-01',
previousAccounting: '120',
previousFiscal: '80',
priorAvailable: '40',
});
b.depreciation.assets.push(a);
const m = calculateNormal(d, b);
assert.equal(val(m.depreciation.adjustment), 4);
assert.equal(val(field(m, '206').value), 30);
assert.equal(val(m.taxableProfit), 3124);
assert.equal(m.formPreparation.unmapped.length, 1);
assert.equal(val(m.formPreparation.unmapped[0].value), -26);
assert.equal(field(m, '238').value, null);
});
void test('acceptedcostproof feeds independentM22 labels without applying printedpercentages again', () => {
const d = JSON.parse(
fs.readFileSync(
new URL(
'./fixtures/irpc-costs-preserved.json',
import.meta.url,
),
'utf8',
),
),
b = normalBook(d),
m = b.result.model,
{ b: input } = setup();
const mapped = prepareNormalForms(input.forms, m, normalFinancialSource(d));
for (const [rule, code] of [
['allowance', '219'],
['representation', '220'],
['advertising', '221'],
['credit', '209'],
]) {
const row = m.costs.rows.find((r) => r.rule === rule);
assert.ok(row);
const sum = m.costs.rows
.filter((r) => r.rule === rule)
.reduce((s, r) => s + val(r.adjustment), 0);
const value = mapped.fields.find((f) => f.code === code).value;
if (code !== '209') assert.equal(val(value), sum);
else
assert.equal(
val(value),
m.costs.rows
.filter((r) =>
['credit', 'stock', 'litigation'].includes(r.rule),
)
.reduce((s, r) => s + val(r.adjustment), 0),
);
}
assert.deepEqual(b.result.model, m);
});
void test('absence of booktax cannot erase previouslyidentified ownincomeTax cost; independentknowncolecta remains', () => {
const { d, b } = setup();
b.schedules.rows.push({
id: 'tax',
rule: 'tax_own',
amount: '320',
sourceCode: 'operating_expenses',
document: 'QA-tax',
line: '1',
reference: 'QA imposto já incluído',
fiscalYear: '',
conditions: 'yes',
});
Object.assign(b.profit.topics.expenseCorrections, {
state: 'known',
amount: '320',
reference: 'QA mapa do imposto próprio',
});
b.forms.incomeTax = {
state: 'absent',
amount: '',
placement: '',
reference: 'QA ausência a conferir',
};
const m = calculateNormal(d, b);
assert.equal(m.formPreparation.netResult, null);
assert.ok(
m.formPreparation.pending.some((s) =>
s.includes('contradiz imposto próprio'),
),
);
assert.equal(val(field(m, '300').value), 1104);
});