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

224 lines
8.9 KiB
JavaScript

import { pecCase } from './helpers/irpc-pec-case.mjs';
import test from 'node:test';
import assert from 'node:assert/strict';
import {
annualBook,
calculateAnnual,
recordAnnual,
saveAnnualBook,
annualCurrent,
annualReportRows,
annualHtml,
} from '../../resources/js/lib/irpc-annual-settlement.ts';
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
const value = (x) => (x ? Number(x.numerator) / Number(x.denominator) : null);
const now = '2026-10-27T15:00:00+02:00';
const model = ({ d, b }) => calculateAnnual(d, b);
const indeterminate = (c) => {
const m = model(c);
assert.equal(m.status, 'indeterminate', JSON.stringify(m.pending));
assert.equal(m.arithmeticBalance, null);
assert.equal(m.pecDeduction.deducted, null);
assert.equal(m.pecDeduction.remaining, null);
return m;
};
void test('PEC precedes PPC; 1408 - 200 - 500 = 708, no official consumption', () => {
const m = model(pecCase());
assert.deepEqual(m.pending, []);
assert.equal(value(m.pecDeduction.deducted), 200);
assert.equal(value(m.pecDeduction.afterPec), 1208);
assert.equal(value(m.payable), 708);
assert.equal(m.pecDeduction.carryCreated, false);
assert.equal(m.officialBalancesConsumed, false);
});
void test('existing received L5 proof is reused after PEC and cannot become another PEC receipt', () => {
const c = pecCase('200', '500', '300');
assert.equal(value(model(c).payable), 408);
Object.assign(c.b.pecPayments.rows[0], {
document: 'QA-RF-PEC-2026',
proofAmount: '300',
});
assert.match(indeterminate(c).pending.join(' '), /natureza/);
});
void test('PEC cap never generates refund; PPC can still generate recoverable balance', () => {
const a = model(pecCase('2000', '0'));
assert.equal(value(a.pecDeduction.deducted), 1408);
assert.equal(value(a.pecDeduction.remaining), 592);
assert.equal(value(a.arithmeticBalance), 0);
assert.equal(value(a.recoverable), 0);
const b = model(pecCase('2000', '500'));
assert.equal(value(b.recoverable), 500);
assert.equal(value(b.pecDeduction.remaining), 592);
assert.equal(value(b.pecDeduction.afterPec), 0);
});
void test('previous use explicit, precise decimals and available portion are respected', () => {
const c = pecCase('200.25', '0');
Object.assign(c.b.pecPayments.rows[0], {
proofAmount: '300.75',
previouslyUsed: '100.50',
});
assert.equal(value(model(c).payable), 1207.75);
c.b.pecPayments.rows[0].amount = '200.26';
indeterminate(c);
});
void test('absence, known zero and missing PEC are distinct; contradictory rows block', () => {
const c = pecCase();
c.b.pecPayments.rows = [];
c.b.pecPayments.state = 'absent';
assert.equal(value(model(c).payable), 908);
c.b.pecPayments.state = '';
indeterminate(c);
c.b.pecPayments.state = 'known';
indeterminate(c);
const z = pecCase('0');
assert.equal(value(model(z).payable), 908);
z.b.pecPayments.rows[0].previouslyUsed = '';
indeterminate(z);
z.b.pecPayments.state = 'absent';
indeterminate(z);
});
void test('payment proof, unused balance, entity, year and currency cannot be presumed', () => {
for (const change of [
{ effective: '' },
{ effective: 'no' },
{ document: '' },
{ reference: '' },
{ priorUseReference: '' },
{ previouslyUsed: '' },
{ previouslyUsed: '201' },
{ amount: '201' },
{ amount: '1.001' },
{ subjectNuit: '999999999' },
{ year: '2025' },
{ date: '2025-08-31' },
{ date: '2026-02-30' },
{ currency: 'USD' },
]) {
const c = pecCase();
Object.assign(c.b.pecPayments.rows[0], change);
indeterminate(c);
}
});
void test('same physical proof cannot be duplicated, re-dated or relabelled PPC', () => {
const a = pecCase();
a.b.pecPayments.rows.push({
...a.b.pecPayments.rows[0],
id: 'copy',
date: '2026-09-01',
});
assert.match(
indeterminate(a).pending.join(' '),
/repetido|saldo disponível/,
);
const b = pecCase();
Object.assign(b.b.pecPayments.rows[0], {
document: b.b.ppc.rows[0].document,
proofAmount: '500',
});
assert.match(
indeterminate(b).pending.join(' '),
/naturezas|natureza incompatível/,
);
});
void test('cross-context allocations respect prior use and changing external claims invalidates current result', () => {
const c = pecCase('100', '0'),
r = c.b.pecPayments.rows[0];
r.proofAmount = '500';
r.previouslyUsed = '100';
const other = structuredClone(c.b);
other.pecPayments.rows[0].amount = '300';
const s = JSON.parse(c.d.modelo_a_snapshot);
s.irpcAnnual = { 'opportunity:other': other };
c.d.modelo_a_snapshot = JSON.stringify(s);
c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, c.b, now));
const saved = annualBook(c.d);
assert.equal(annualCurrent(c.d, saved), true);
assert.equal(value(saved.result.model.payable), 1308);
const edit = JSON.parse(c.d.modelo_a_snapshot);
edit.irpcAnnual['opportunity:other'].pecPayments.rows[0].amount = '301';
c.d.modelo_a_snapshot = JSON.stringify(edit);
assert.equal(annualCurrent(c.d, saved), false);
assert.match(
indeterminate({ ...c, b: saved }).pending.join(' '),
/saldo disponível/,
);
edit.irpcAnnual['opportunity:other'].pecPayments.rows[0].amount = '100';
edit.irpcAnnual['opportunity:other'].pecPayments.rows[0].previouslyUsed =
'0';
c.d.modelo_a_snapshot = JSON.stringify(edit);
indeterminate({ ...c, b: saved });
});
void test('other contexts cannot change PEC titular, origin or payment nature', () => {
for (const change of [{ subjectNuit: '999999999' }, { year: '2025' }]) {
const c = pecCase('100', '0'),
other = structuredClone(c.b);
other.pecPayments.rows[0].proofAmount = '500';
Object.assign(other.pecPayments.rows[0], change);
const s = JSON.parse(c.d.modelo_a_snapshot);
s.irpcAnnual = { 'opportunity:other': other };
c.d.modelo_a_snapshot = JSON.stringify(s);
indeterminate(c);
}
const c = pecCase('100', '0'),
other = structuredClone(c.b);
other.ppc.rows = [
{ ...c.b.pecPayments.rows[0], amount: '50', proofAmount: '100' },
];
other.ppc.state = 'known';
other.pecPayments.rows = [];
other.pecPayments.state = 'absent';
const s = JSON.parse(c.d.modelo_a_snapshot);
s.irpcAnnual = { 'opportunity:other': other };
c.d.modelo_a_snapshot = JSON.stringify(s);
indeterminate(c);
});
void test('positive special credits remain scoped and cannot be silently skipped before PEC', () => {
const c = pecCase();
c.b.credits.amount = '10';
assert.match(indeterminate(c).pending.join(' '), /Créditos/);
assert.equal(value(model(c).pecDeduction.available), 200);
});
void test('save/recover/recalculate preserves old annual results and all other fiscal books, plan and reports', () => {
const c = pecCase(),
before = JSON.parse(c.d.modelo_a_snapshot),
plan = c.d.business_plan_snapshot;
const legacy = structuredClone(c.b);
delete legacy.pecPayments;
c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, legacy, now));
const old = structuredClone(annualBook(c.d).result);
c.b = { ...annualBook(c.d), pecPayments: c.b.pecPayments };
c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, c.b, now));
const back = annualBook(JSON.parse(JSON.stringify(c.d)));
assert.deepEqual(back.history, [old]);
assert.equal(value(back.result.model.payable), 708);
assert.equal(annualCurrent(c.d, back), true);
const after = JSON.parse(c.d.modelo_a_snapshot);
delete after.irpcAnnual;
assert.deepEqual(after, before);
assert.equal(c.d.business_plan_snapshot, plan);
const html = annualHtml(c.d);
assert.match(html, /PEC — dedução limitada/);
assert.match(html, /708.00/);
assert.match(html, /sem reembolso PEC/);
const dossier = createProjectDossierReport(c.d, {
selectedSections: new Set(['finance']),
});
assert.match(JSON.stringify(dossier), /PEC — dedução limitada/);
const frozen = structuredClone(back.result);
back.pecPayments.rows[0].amount = '100';
c.d.modelo_a_snapshot = saveAnnualBook(c.d, back);
assert.equal(annualCurrent(c.d, back), false);
assert.deepEqual(back.result, frozen);
assert.match(annualReportRows(c.d).flat().join(' '), /Histórico/);
assert.throws(() => annualHtml(c.d), /fontes actuais/);
});
void test('malformed PEC extension never replaces legacy snapshots', () => {
const c = pecCase(),
original = c.d.modelo_a_snapshot;
c.b.pecPayments.rows[0].previouslyUsed = null;
assert.throws(() => saveAnnualBook(c.d, c.b), /não recuperáveis/);
assert.equal(c.d.modelo_a_snapshot, original);
});