433 lines
17 KiB
JavaScript
433 lines
17 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { economicCase } from './helpers/irpc-economic-case.mjs';
|
|
import { internationalCase } from './helpers/irpc-international-case.mjs';
|
|
import {
|
|
calculateAnnual,
|
|
prepareEconomicBook,
|
|
annualBook,
|
|
recordAnnual,
|
|
saveAnnualBook,
|
|
annualCurrent,
|
|
annualHtml,
|
|
annualReportRows,
|
|
} from '../../resources/js/lib/irpc-annual-settlement.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
import {
|
|
irpcBook,
|
|
recordIrpc,
|
|
saveIrpcBook,
|
|
} from '../../resources/js/lib/irpc-withholdings.ts';
|
|
const number = (v) => (v ? Number(v.numerator) / Number(v.denominator) : null);
|
|
const row = (c) => c.b.economicCredits.rows[0];
|
|
const foreign = (c) => c.b.internationalCredits.positions[0].payments[0];
|
|
const calc = (c) => calculateAnnual(c.d, c.b);
|
|
const pending = (c) => {
|
|
const m = calc(c);
|
|
assert.equal(m.status, 'indeterminate', JSON.stringify(m.pending));
|
|
assert.equal(m.arithmeticBalance, null);
|
|
assert.equal(m.economicCredit.deducted, null);
|
|
assert.equal(m.internationalCredit.deducted, null);
|
|
assert.equal(m.pecDeduction.deducted, null);
|
|
return m;
|
|
};
|
|
|
|
void test('economic60% of documented sourceIRPC20 yields12 before international8,PEC200 andPPC500:688', () => {
|
|
const m = calc(economicCase());
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(number(m.economicCredit.rows[0].nominal), 12);
|
|
assert.equal(number(m.economicCredit.deducted), 12);
|
|
assert.equal(number(m.economicCredit.afterCredit), 1396);
|
|
assert.equal(number(m.internationalCredit.deducted), 8);
|
|
assert.equal(number(m.internationalCredit.afterCredit), 1388);
|
|
assert.equal(number(m.payable), 688);
|
|
assert.equal(m.economicCredit.carryCreated, false);
|
|
assert.equal(m.economicCredit.officialBalancesConsumed, false);
|
|
assert.equal(m.submitted, false);
|
|
});
|
|
void test('documented proportional ceiling and prior actual use limit economic deduction,not a second tax rate on dividends', () => {
|
|
const c = economicCase();
|
|
row(c).nationalLimit = '5';
|
|
let m = calc(c);
|
|
assert.equal(number(m.economicCredit.rows[0].nominal), 12);
|
|
assert.equal(number(m.payable), 695);
|
|
row(c).previousUse.amount = '3';
|
|
m = calc(c);
|
|
assert.equal(number(m.economicCredit.deducted), 2);
|
|
assert.equal(number(m.payable), 698);
|
|
row(c).previousUse.amount = '6';
|
|
pending(c);
|
|
});
|
|
void test('article40 exclusion, payer exemption/foreign residence and special distributions do not become international credit', () => {
|
|
for (const patch of [
|
|
{ article40: '' },
|
|
{ article40: 'yes' },
|
|
{ article40Reference: '' },
|
|
{ payerResident: '' },
|
|
{ payerResident: 'no' },
|
|
{ recipientResident: 'no' },
|
|
{ payerIrpc: 'no' },
|
|
{ payerExempt: '' },
|
|
{ payerExempt: 'yes' },
|
|
{ kind: 'liquidation' },
|
|
{ kind: 'association' },
|
|
{ kind: '' },
|
|
{ qualificationReference: '' },
|
|
]) {
|
|
const c = economicCase();
|
|
Object.assign(row(c), patch);
|
|
const m = pending(c);
|
|
assert.equal(m.internationalCredit.positions.length, 1);
|
|
assert.equal(number(m.internationalCredit.positions[0].eligible), 8);
|
|
if (patch.article40 === 'yes')
|
|
assert.equal(m.economicCredit.rows[0].status, 'outside_scope');
|
|
}
|
|
});
|
|
void test('inclusion,credit gross-up and current assessment must be reconciled without silently changing L6', () => {
|
|
for (const patch of [
|
|
{ incomeIncluded: '' },
|
|
{ incomeIncluded: 'no' },
|
|
{ incomeReference: '' },
|
|
{ assessmentKey: '' },
|
|
{ assessmentKey: 'stale' },
|
|
{ grossUpIncluded: '' },
|
|
{ grossUpIncluded: '0' },
|
|
{ grossUpIncluded: '11.99' },
|
|
{ grossUpReference: '' },
|
|
{ nationalLimit: '' },
|
|
{ nationalLimit: '1409' },
|
|
{ nationalLimitReference: '' },
|
|
{ income: '0' },
|
|
]) {
|
|
const c = economicCase(),
|
|
before = c.d.modelo_a_snapshot;
|
|
Object.assign(row(c), patch);
|
|
pending(c);
|
|
assert.equal(c.d.modelo_a_snapshot, before);
|
|
}
|
|
});
|
|
void test('dividend/source tax identity,date,year,subject and actual allocated source amount require support', () => {
|
|
for (const patch of [
|
|
{ subjectNuit: '999999999' },
|
|
{ payerNuit: '' },
|
|
{ year: '2025' },
|
|
{ distributionDate: '2026-02-30' },
|
|
{ distributionDate: '2025-12-31' },
|
|
{ distributionDocument: '' },
|
|
{ distributionParcel: '' },
|
|
{ taxIssuer: '' },
|
|
{ taxDocument: '' },
|
|
{ taxParcel: '' },
|
|
{ parcelReference: '' },
|
|
{ currency: 'USD' },
|
|
{ proofAmount: '' },
|
|
{ taxAmount: '21' },
|
|
{ taxAmount: '' },
|
|
{ attributionReference: '' },
|
|
{ previouslyAttributed: '' },
|
|
{ previouslyAttributed: '1' },
|
|
{ priorAttributionReference: '' },
|
|
]) {
|
|
const c = economicCase();
|
|
Object.assign(row(c), patch);
|
|
pending(c);
|
|
}
|
|
});
|
|
void test('same distributed profit parcel cannot be re-used by changing source-tax document,id or date', () => {
|
|
const c = economicCase();
|
|
c.b.economicCredits.rows.push({
|
|
...structuredClone(row(c)),
|
|
id: 'other',
|
|
taxDocument: 'OTHER',
|
|
distributionDate: '2026-10-01',
|
|
distributionDocument: ' synthetic-dividend2026 ',
|
|
distributionParcel: ' DISTRIBUTION-1 ',
|
|
});
|
|
assert.match(pending(c).pending.join(' '), /Mesma parcela de lucros/);
|
|
});
|
|
void test('one source-tax parcel can support distinct documented distributions only within its actual available amount', () => {
|
|
const c = economicCase(),
|
|
other = {
|
|
...structuredClone(row(c)),
|
|
id: 'second',
|
|
distributionParcel: 'distribution-2',
|
|
};
|
|
c.b.economicCredits.rows.push(other);
|
|
assert.match(pending(c).pending.join(' '), /disponível/);
|
|
for (const r of c.b.economicCredits.rows) {
|
|
r.taxAmount = '10';
|
|
r.grossUpIncluded = '6';
|
|
}
|
|
let m = calc(c);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(number(m.economicCredit.deducted), 12);
|
|
row(c).previouslyAttributed = '1';
|
|
other.previouslyAttributed = '1';
|
|
pending(c);
|
|
row(c).taxAmount = '9';
|
|
other.taxAmount = '9';
|
|
row(c).grossUpIncluded = '5.4';
|
|
other.grossUpIncluded = '5.4';
|
|
m = calc(c);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(number(m.economicCredit.deducted), 10.8);
|
|
other.previouslyAttributed = '0';
|
|
assert.match(pending(c).pending.join(' '), /divergente/);
|
|
});
|
|
void test('economic and international use of the same actual tax parcel is rejected even with changed receipt date', () => {
|
|
const c = economicCase();
|
|
Object.assign(foreign(c), {
|
|
issuer: row(c).taxIssuer,
|
|
document: row(c).taxDocument,
|
|
parcel: ' UNDERLYING-TAX-1 ',
|
|
parcelReference: 'SYNTHETIC same parcel',
|
|
proofAmount: '20',
|
|
date: '2026-10-01',
|
|
});
|
|
assert.match(pending(c).pending.join(' '), /natureza|naturezas/);
|
|
});
|
|
void test('an old proof with no parcel overlaps the whole document;new names require supported partition on both sides', () => {
|
|
const c = economicCase();
|
|
Object.assign(foreign(c), {
|
|
issuer: row(c).taxIssuer,
|
|
document: row(c).taxDocument,
|
|
proofAmount: '20',
|
|
});
|
|
pending(c);
|
|
foreign(c).parcel = 'foreign-tax-line';
|
|
pending(c);
|
|
foreign(c).parcelReference =
|
|
'SYNTHETIC evidence of a distinct foreign-tax line in shared document';
|
|
const m = calc(c);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(number(m.payable), 688);
|
|
});
|
|
void test('economic source IRPC is not beneficiary withholding,PPC or PEC;distinct supported parcels may share a document', () => {
|
|
for (const kind of ['ppc', 'pec']) {
|
|
const c = economicCase(),
|
|
proof = kind === 'ppc' ? c.b.ppc.rows[0] : c.b.pecPayments.rows[0];
|
|
Object.assign(row(c), {
|
|
taxIssuer: proof.issuer,
|
|
taxDocument: proof.document,
|
|
proofAmount: proof.proofAmount,
|
|
});
|
|
pending(c);
|
|
Object.assign(proof, {
|
|
parcel: 'own-' + kind,
|
|
parcelReference: 'SYNTHETIC distinct payment parcel',
|
|
});
|
|
assert.deepEqual(calc(c).pending, []);
|
|
proof.parcel = row(c).taxParcel;
|
|
pending(c);
|
|
}
|
|
});
|
|
void test('underlying domestic IRPC and beneficiary withholding remain separate even when supported by the same paper', () => {
|
|
const c = internationalCase('10', '8', '200', '500', '300');
|
|
c.b = prepareEconomicBook(c.b);
|
|
c.b.economicCredits = economicCase().b.economicCredits;
|
|
const withholding = c.b.withholdings.rows[0],
|
|
proof = calc(c).claims.find((x) => x.kind === 'withholding');
|
|
Object.assign(row(c), {
|
|
taxIssuer: proof.issuer,
|
|
taxDocument: proof.document,
|
|
proofAmount: '300',
|
|
});
|
|
pending(c);
|
|
Object.assign(withholding, {
|
|
parcel: 'beneficiary-withholding',
|
|
parcelReference: 'SYNTHETIC separate recipient-tax parcel',
|
|
});
|
|
assert.equal(number(calc(c).payable), 388);
|
|
const other = structuredClone(c.b),
|
|
s = JSON.parse(c.d.modelo_a_snapshot);
|
|
other.economicCredits.rows = [];
|
|
other.economicCredits.state = 'absent';
|
|
other.internationalCredits.positions = [];
|
|
other.internationalCredits.state = 'absent';
|
|
other.ppc.rows = [];
|
|
other.ppc.state = 'absent';
|
|
other.pecPayments.rows = [];
|
|
other.pecPayments.state = 'absent';
|
|
other.withholdings.rows[0].parcel = 'relabelled-same-L5-allocation';
|
|
s.irpcAnnual = { 'opportunity:other': other };
|
|
s.irpcWithholdings['opportunity:other'] = structuredClone(
|
|
Object.values(s.irpcWithholdings)[0],
|
|
);
|
|
c.d.modelo_a_snapshot = JSON.stringify(s);
|
|
c.d.modelo_a_snapshot = saveIrpcBook(
|
|
c.d,
|
|
recordIrpc(c.d, irpcBook(c.d), '2026-10-28T14:00:00+02:00'),
|
|
);
|
|
assert.match(
|
|
pending(c).pending.join(' '),
|
|
/calculada e reconciliada emL5|afectação L5/,
|
|
);
|
|
});
|
|
void test('documented economic inclusion is not rejected merely because fiscal losses reduce total taxable base below dividends', () => {
|
|
const c = economicCase();
|
|
row(c).income = '5000';
|
|
const m = calc(c);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(number(m.payable), 688);
|
|
});
|
|
void test('cross-context source capacity and distributed-income duplication cannot be evaded with new ids/documents', () => {
|
|
const c = economicCase(),
|
|
other = structuredClone(c.b),
|
|
s = JSON.parse(c.d.modelo_a_snapshot);
|
|
other.internationalCredits.positions = [];
|
|
other.internationalCredits.state = 'absent';
|
|
other.ppc.rows = [];
|
|
other.ppc.state = 'absent';
|
|
other.pecPayments.rows = [];
|
|
other.pecPayments.state = 'absent';
|
|
other.economicCredits.rows[0].id = 'other-id';
|
|
other.economicCredits.rows[0].taxDocument = 'OTHER';
|
|
s.irpcAnnual = { 'opportunity:other': other };
|
|
c.d.modelo_a_snapshot = JSON.stringify(s);
|
|
assert.match(pending(c).pending.join(' '), /lucros económicos já/);
|
|
other.economicCredits.rows[0].distributionParcel = 'other-distribution';
|
|
other.economicCredits.rows[0].taxDocument = row(c).taxDocument;
|
|
c.d.modelo_a_snapshot = JSON.stringify(s);
|
|
pending(c);
|
|
other.economicCredits.rows[0].taxAmount = '10';
|
|
row(c).taxAmount = '10';
|
|
row(c).grossUpIncluded = '6';
|
|
c.d.modelo_a_snapshot = JSON.stringify(s);
|
|
assert.deepEqual(calc(c).pending, []);
|
|
});
|
|
void test('economic and accepted carry increments compose:1408-12-8-50-200-100-500=538', () => {
|
|
const m = calc(economicCase(true));
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(number(m.payable), 538);
|
|
assert.equal(number(m.creditCarry.afterInternational), 1338);
|
|
assert.equal(number(m.creditCarry.afterPec), 1038);
|
|
});
|
|
void test('economic priority leaves prior explicit allocations intact when remaining collecta becomes insufficient', () => {
|
|
const c = economicCase(true);
|
|
c.b.creditCarry.lots[0].eligible = '2000';
|
|
c.b.creditCarry.lots[0].selected = '1300';
|
|
c.b.creditCarry.currentPec = '0';
|
|
const before = structuredClone(c.b.creditCarry);
|
|
assert.match(pending(c).pending.join(' '), /ultrapassam a colecta/);
|
|
assert.deepEqual(c.b.creditCarry, before);
|
|
c.b.creditCarry.lots[0].selected = '1288';
|
|
const m = calc(c);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(number(m.recoverable), 500);
|
|
assert.equal(m.economicCredit.carryCreated, false);
|
|
});
|
|
void test('zero,absence and missing differ; earlier deliberate economic summary remains frozen and does not block detailed calculation', () => {
|
|
const c = economicCase();
|
|
c.b.internationalCredits.economic = {
|
|
state: 'known',
|
|
amount: '999',
|
|
reference: 'SYNTHETIC previous deliberate summary',
|
|
};
|
|
assert.equal(number(calc(c).payable), 688);
|
|
assert.equal(c.b.internationalCredits.economic.amount, '999');
|
|
c.b.economicCredits.state = 'absent';
|
|
pending(c);
|
|
c.b.economicCredits.rows = [];
|
|
assert.equal(number(calc(c).payable), 700);
|
|
c.b.economicCredits.state = '';
|
|
pending(c);
|
|
const z = economicCase();
|
|
Object.assign(row(z), {
|
|
taxAmount: '0',
|
|
proofAmount: '0',
|
|
grossUpIncluded: '0',
|
|
income: '0',
|
|
nationalLimit: '0',
|
|
});
|
|
assert.equal(number(calc(z).payable), 700);
|
|
});
|
|
void test('exact60% arithmetic and fractional quota never invent fiscal rounding', () => {
|
|
const c = economicCase();
|
|
Object.assign(row(c), {
|
|
taxAmount: '0.01',
|
|
proofAmount: '0.01',
|
|
grossUpIncluded: '0.006',
|
|
});
|
|
const m = calc(c);
|
|
assert.deepEqual(m.economicCredit.deducted, {
|
|
numerator: '3',
|
|
denominator: '500',
|
|
});
|
|
assert.deepEqual(m.payable, { numerator: '349997', denominator: '500' });
|
|
row(c).nationalLimit = '1/1000';
|
|
assert.deepEqual(calc(c).economicCredit.deducted, {
|
|
numerator: '1',
|
|
denominator: '1000',
|
|
});
|
|
});
|
|
void test('save/recover/history,HTML and dossier preserve current L6,original snapshots and accepted carry results', () => {
|
|
const c = economicCase(true),
|
|
prior = structuredClone(c.b);
|
|
delete prior.economicCredits;
|
|
c.d.modelo_a_snapshot = saveAnnualBook(
|
|
c.d,
|
|
recordAnnual(c.d, prior, '2026-10-28T12:00:00+02:00'),
|
|
);
|
|
const old = annualBook(c.d),
|
|
frozen = structuredClone(old.result),
|
|
base = JSON.parse(c.d.modelo_a_snapshot),
|
|
plan = c.d.business_plan_snapshot;
|
|
const next = prepareEconomicBook(old);
|
|
next.economicCredits = c.b.economicCredits;
|
|
c.d.modelo_a_snapshot = saveAnnualBook(
|
|
c.d,
|
|
recordAnnual(c.d, next, '2026-10-28T14:00:00+02:00'),
|
|
);
|
|
const back = annualBook(JSON.parse(JSON.stringify(c.d)));
|
|
assert.equal(annualCurrent(c.d, back), true);
|
|
assert.deepEqual(back.history, [frozen]);
|
|
assert.equal(number(back.result.model.payable), 538);
|
|
const after = JSON.parse(c.d.modelo_a_snapshot);
|
|
delete after.irpcAnnual;
|
|
delete base.irpcAnnual;
|
|
assert.deepEqual(after, base);
|
|
assert.equal(c.d.business_plan_snapshot, plan);
|
|
assert.match(annualHtml(c.d), /Crédito económico — antes do internacional/);
|
|
assert.match(annualHtml(c.d), /538.00/);
|
|
assert.match(
|
|
JSON.stringify(
|
|
createProjectDossierReport(c.d, {
|
|
selectedSections: new Set(['finance']),
|
|
}),
|
|
),
|
|
/Crédito económico/,
|
|
);
|
|
back.economicCredits.rows[0].nationalLimit = '5';
|
|
c.d.modelo_a_snapshot = saveAnnualBook(c.d, back);
|
|
assert.equal(annualCurrent(c.d, back), false);
|
|
assert.deepEqual(back.result, annualBook(c.d).result);
|
|
assert.throws(() => annualHtml(c.d), /fontes actuais/);
|
|
assert.match(annualReportRows(c.d).flat().join(' '), /Histórico/);
|
|
});
|
|
void test('malformed optional evidence and parcels are rejected without resetting snapshots', () => {
|
|
for (const mutate of [
|
|
(c) => (c.b.economicCredits.version = 2),
|
|
(c) => (row(c).previousUse = null),
|
|
(c) => (row(c).article40 = null),
|
|
(c) => (foreign(c).parcel = 1),
|
|
]) {
|
|
const c = economicCase(),
|
|
before = c.d.modelo_a_snapshot;
|
|
mutate(c);
|
|
assert.throws(() => saveAnnualBook(c.d, c.b), /recuperável/);
|
|
assert.equal(c.d.modelo_a_snapshot, before);
|
|
}
|
|
const c = internationalCase();
|
|
const once = prepareEconomicBook(c.b);
|
|
assert.equal(prepareEconomicBook(once), once);
|
|
});
|
|
void test('a partially identified document parcel remains recoverable and indeterminate rather than acquiring absent fields', () => {
|
|
const c = economicCase();
|
|
foreign(c).parcel = 'foreign-tax-line';
|
|
const model = pending(c);
|
|
c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, c.b));
|
|
const back = annualBook(JSON.parse(JSON.stringify(c.d)));
|
|
assert.deepEqual(calculateAnnual(c.d, back), back.result.model);
|
|
assert.equal(back.result.model.status, model.status);
|
|
});
|