142 lines
15 KiB
JavaScript
142 lines
15 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;};
|
|
|
|
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);
|
|
});
|
|
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);
|
|
});
|
|
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' || patch.kind === 'liquidation' || patch.kind === 'association') assert.equal(m.economicCredit.rows[0].status, 'outside_scope');
|
|
}
|
|
});
|
|
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);
|
|
}
|
|
});
|
|
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);
|
|
}
|
|
});
|
|
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/);
|
|
});
|
|
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/);
|
|
});
|
|
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/);
|
|
});
|
|
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);
|
|
});
|
|
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);
|
|
}
|
|
});
|
|
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/);
|
|
});
|
|
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);
|
|
});
|
|
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, []);
|
|
});
|
|
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);
|
|
});
|
|
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);
|
|
});
|
|
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);
|
|
});
|
|
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'});
|
|
});
|
|
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/);
|
|
});
|
|
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);
|
|
});
|
|
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);
|
|
});
|