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

553 lines
19 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {
irpcBook,
newIrpcRow,
recordIrpc,
saveIrpcBook,
emptyIrpcOrdinaryFacts,
irpcCurrent,
irpcReportRows,
irpcPreparationHtml,
} from '../../resources/js/lib/irpc-withholdings.ts';
import { irpcFxSources, updateIrpcFx } from '../../resources/js/lib/irpc-fx.ts';
import {
emptyFxSelection,
selectFx,
} from '../../resources/js/lib/iva-fx-selection.ts';
import {
currencyEventBook,
newCurrencyEvent,
saveCurrencyEventBook,
} from '../../resources/js/lib/iva-currency-events.ts';
const accepted = JSON.parse(
fs.readFileSync(
new URL('./fixtures/irpc-l5-preserved.json', import.meta.url),
'utf8',
),
);
const draft = () => structuredClone(accepted);
const row = (id = 'normal') => ({
...newIrpcRow(id),
counterparty: 'SYNTHETIC IRPC counterparty',
documentRef: 'SYNTHETIC-' + id,
base: '100000',
kind: 'royalty',
recipientIrpc: 'yes',
payerIrpc: 'yes',
residence: 'nonresident',
permanentEstablishment: 'no',
mozambique: 'yes',
qualificationRef:
'SYNTHETIC classified non-digital income,IRPCpayer/recipient,Mozambique source;residence andEP explicitly recorded',
specialRelief: 'none',
eventsComplete: 'yes',
events: [
{
kind: 'cost',
date: '2026-10-02',
reference: 'SYNTHETIC applicable cost',
},
{ kind: 'payment', date: '2026-11-02', reference: 'SYNTHETIC payment' },
],
settlement: 'none',
settlementRef: 'SYNTHETIC no remittance',
ordinary: {
...emptyIrpcOrdinaryFacts(),
nonDigital: 'yes',
reference: 'SYNTHETIC exact category facts;CIRPC62/67',
},
});
const book = (r = row()) => ({ version: 1, rows: [r], payments: [] });
const calc = (r) => recordIrpc(draft(), book(r)).result.rows[0];
const fx = (patch = {}) => {
const s = {
...emptyFxSelection(),
nature: 'commercial',
submissionDate: '2026-10-20',
bulletinDate: '2026-10-20',
bulletinRef: 'SYNTHETIC fictional BM bulletin for QA',
source: 'SYNTHETIC archived QA exemplar',
issuer: 'Banco de Moçambique',
sourceCurrency: 'USD',
direction: 'mzn_per_unit',
mean: '64',
...patch,
};
return { ...s, applied: selectFx(s) };
};
void test('ordinary nonresident royalties equipment services and nonlisted capitals use20%,not digital10%', () => {
for (const kind of ['royalty', 'equipment', 'services', 'capital']) {
const r = row();
r.kind = kind;
if (kind === 'capital') r.ordinary.capitalType = 'unlisted_interest';
const v = calc(r);
assert.equal(v.status, 'calculated');
assert.equal(v.rate, 20);
assert.equal(v.tax.numerator, '20000');
assert.equal(v.nature, 'definitive');
}
});
void test('resident royalties and ordinary rent are on account;predial nonresident is also on account', () => {
for (const patch of [
{ kind: 'royalty', residence: 'resident' },
{ kind: 'rent', residence: 'resident' },
{ kind: 'rent', residence: 'nonresident' },
]) {
const r = { ...row(), ...patch };
r.ordinary.rentOwnPropertyManagement = 'no';
const v = calc(r);
assert.equal(v.status, 'calculated');
assert.equal(v.nature, 'on_account');
assert.equal(v.tax.numerator, '20000');
assert.equal('annualCredit' in v, false);
}
});
void test('identified dispensa branches and unknown facts block only their own income line', () => {
const d = draft(),
rs = [
{
...row('resident-services'),
residence: 'resident',
kind: 'services',
},
{ ...row('property'), kind: 'rent' },
{
...row('capital'),
residence: 'resident',
kind: 'capital',
ordinary: {
...row().ordinary,
capitalType: 'unlisted_interest',
},
},
row('independent'),
];
const out = recordIrpc(d, { version: 1, rows: rs, payments: [] }).result
.rows;
assert.ok(
out
.slice(0, 3)
.every((v) => v.status === 'indeterminate' && v.tax === null),
);
assert.equal(out[3].tax.numerator, '20000');
});
void test('special categories62(4)a-c retain10% with exact facts,not generic20% or genericinstallation10%', () => {
for (const kind of [
'telecom',
'international_transport',
'connected_installation',
'aircraft_maintenance_charter',
'rural_public_electrification',
'maritime_fishing_cabotage',
]) {
const r = { ...row(), kind };
r.ordinary.specialFacts = 'yes';
const v = calc(r);
assert.equal(v.rate, 10);
assert.equal(v.tax.numerator, '10000');
assert.match(v.reason, /62\(4\)/);
}
const unknown = { ...row(), kind: 'connected_installation' };
assert.equal(calc(unknown).tax, null);
const generic = { ...row(), kind: 'services' };
assert.equal(calc(generic).rate, 20);
});
void test('special62(4) entity condition is distinct from digital noEP/nonattributable branch', () => {
for (const patch of [
{ residence: 'resident' },
{ permanentEstablishment: 'yes', attributable: 'no' },
]) {
const r = { ...row(), kind: 'telecom', ...patch };
r.ordinary.specialFacts = 'yes';
assert.equal(calc(r).tax, null);
}
});
void test('listed debt and other BMsecurities remain distinct;liquidity repos are not assumed', () => {
for (const [capitalType, rate] of [
['listed_debt_mz', 20],
['listed_other_mz', 10],
]) {
const r = { ...row(), kind: 'capital' };
r.ordinary.capitalType = capitalType;
r.ordinary.specialFacts = 'yes';
assert.equal(calc(r).rate, rate);
}
const resident = { ...row(), kind: 'capital', residence: 'resident' };
resident.ordinary.capitalType = 'listed_debt_mz';
resident.ordinary.capitalSeatAndManagement = 'yes';
assert.equal(calc(resident).rate, 20);
assert.equal(calc(resident).nature, 'definitive');
resident.ordinary.capitalType = 'liquidity_repo';
assert.equal(calc(resident).rate, 20);
assert.equal(calc(resident).nature, 'definitive');
});
void test('expired2025 agricultural measures do not extend into2026 foreign services/interest', () => {
for (const kind of ['services', 'capital']) {
const r = {
...row(),
kind,
description:
'SYNTHETIC nationalagriculturalcompany / external agricultural financing',
};
r.ordinary.capitalType = 'unlisted_interest';
const v = calc(r);
assert.equal(v.rate, 20);
assert.match(v.reason, /31\/12\/2025/);
}
const r = row();
r.events[0].date = '2025-12-31';
assert.equal(calc(r).tax, null);
});
void test('applied existing BMmean converts base, preserves original and keeps event/date separate', () => {
const r = { ...row(), currency: 'USD', base: '1000', fxSelection: fx() };
const v = calc(r);
assert.equal(v.base.numerator, '64000');
assert.equal(v.tax.numerator, '12800');
assert.equal(v.period, '2026-10');
assert.equal(v.eventDate, '2026-10-02');
assert.equal(v.conversion.date, '2026-10-20');
assert.equal(v.conversion.originalAmount, '1000');
assert.equal(v.conversion.originalCurrency, 'USD');
assert.equal(r.base, '1000');
assert.equal(r.currency, 'USD');
});
void test('purchase/sale perspective,inverse quote and fiscalexception use deliveredselector', () => {
const opposite = fx({
nature: 'purchase',
relation: 'opposite',
bulletinPerspective: 'SYNTHETIC issuer',
buy: '63',
sell: '65',
});
let r = { ...row(), currency: 'USD', base: '100', fxSelection: opposite };
assert.equal(calc(r).base.numerator, '6500');
const inverse = fx({ direction: 'units_per_mzn', mean: '0.02' });
r.fxSelection = inverse;
assert.equal(calc(r).base.numerator, '5000');
r.fxSelection = fx({
mode: 'exception',
exceptionRef: 'SYNTHETIC identifiedfiscalprovision',
exceptionScope: 'SYNTHETIC retention scope',
exceptionDate: '2026-10-02',
exceptionRate: '66',
});
assert.equal(calc(r).base.numerator, '6600');
assert.equal(calc(r).conversion.column, 'exception');
});
void test('unapplied,stale,missingmean,wrongcurrency or undated FX remains indeterminate', () => {
const samples = [
undefined,
{ ...fx(), applied: undefined },
{ ...fx(), mean: '65' },
{ ...fx(), sourceCurrency: 'EUR' },
{ ...fx(), bulletinDate: '2026-10-19' },
];
for (const f of samples) {
const v = calc({
...row(),
currency: 'USD',
base: '1000',
fxSelection: f,
});
assert.equal(v.tax, null);
assert.match(v.reason, /conversão/);
}
});
void test('existing saved FX selections are linked,reused,and changed source marks only linked results stale', () => {
const d = draft(),
cb = currencyEventBook(d),
s = fx(),
f = {
...newCurrencyEvent('shared-fx', 'MZN'),
fxSelection: s,
sourceCurrency: 'USD',
fxDirection: s.direction,
fxRate: s.applied.rate,
fxDate: s.applied.date,
fxSource: s.applied.source,
description: 'SYNTHETIC existing FX',
};
d.modelo_a_snapshot = saveCurrencyEventBook(d, {
...cb,
rows: [...cb.rows, f],
});
const source = irpcFxSources(d).find((s) => s.id === f.id),
r = {
...row(),
currency: 'USD',
base: '1000',
fxSelection: source.selection,
fxLink: { id: source.id, fingerprint: source.fingerprint },
},
b = recordIrpc(d, book(r));
d.modelo_a_snapshot = saveIrpcBook(d, b);
assert.equal(irpcCurrent(d, b), true);
const current = currencyEventBook(d);
d.modelo_a_snapshot = saveCurrencyEventBook(d, {
...current,
rows: current.rows.map((v) =>
v.id === f.id
? { ...v, description: 'SYNTHETIC revisedsource' }
: v,
),
});
assert.equal(irpcCurrent(d, b), false);
assert.match(recordIrpc(d, b).result.rows[0].reason, /origem alterada/);
const m = recordIrpc(d, book(row('independentMZN')));
assert.equal(m.result.rows[0].tax.numerator, '20000');
});
void test('foreign withholding proof converts available amount once;allocations remainMZN', () => {
const r = {
...row(),
direction: 'received',
settlement: 'complete',
settlementRef: 'SYNTHETIC provedpartialwithholding',
allocations: [{ paymentId: 'p', amount: '640' }],
},
p = {
id: 'p',
kind: 'withholding_proof',
reference: 'SYNTHETIC-USDproof',
date: '2026-10-20',
currency: 'USD',
amount: '10',
fxSelection: fx(),
};
const d = draft(),
b = recordIrpc(d, { ...book(r), payments: [p] });
assert.equal(b.result.rows[0].settled.numerator, '640');
assert.equal(b.result.rows[0].outstanding.numerator, '19360');
assert.equal(b.result.paymentConversions.p.originalAmount, '10');
d.modelo_a_snapshot = saveIrpcBook(d, b);
assert.match(irpcReportRows(d)[0][3], /SYNTHETIC-USDproof/);
assert.match(irpcReportRows(d)[0][3], /Original: 10 USD/);
});
void test('foreign payment to beneficiary or purported ATremittance is never converted into taxproof', () => {
for (const kind of ['beneficiary', 'remittance']) {
const r = {
...row(),
settlement: 'complete',
allocations: [{ paymentId: 'p', amount: '640' }],
},
p = {
id: 'p',
kind,
reference: 'SYNTHETIC-wrongpayment',
date: '2026-10-20',
currency: 'USD',
amount: '10',
fxSelection: fx(),
};
assert.equal(
recordIrpc(draft(), { ...book(r), payments: [p] }).result.rows[0]
.status,
'indeterminate',
);
}
});
void test('converted shared proof cannot exceed availableMZN or change FX between contexts', () => {
const d = draft(),
p = {
id: 'p',
kind: 'withholding_proof',
reference: 'SYNTHETIC-sharedUSD',
date: '2026-10-20',
currency: 'USD',
amount: '10',
fxSelection: fx(),
},
r = {
...row('one'),
direction: 'received',
settlement: 'complete',
allocations: [{ paymentId: 'p', amount: '400' }],
};
d.modelo_a_snapshot = saveIrpcBook(
d,
recordIrpc(d, { ...book(r), payments: [p] }),
);
d.context_opportunity = 'other';
const other = {
...row('two'),
direction: 'received',
settlement: 'complete',
allocations: [{ paymentId: 'p', amount: '300' }],
};
assert.equal(
recordIrpc(d, { ...book(other), payments: [p] }).result.rows[0].status,
'indeterminate',
);
other.allocations[0].amount = '200';
assert.equal(
recordIrpc(d, { ...book(other), payments: [p] }).result.rows[0].status,
'calculated',
);
assert.equal(
recordIrpc(d, {
...book(other),
payments: [{ ...p, fxSelection: fx({ mean: '65' }) }],
}).result.rows[0].status,
'indeterminate',
);
});
void test('accepted v1MZN10% results remain current and bytepreserved without recomputation', () => {
const d = draft(),
b = irpcBook(d),
before = JSON.stringify(b);
assert.equal(irpcCurrent(d, b), true);
assert.equal(JSON.stringify(irpcBook(d)), before);
assert.deepEqual(
irpcBook({ ...d, modelo_a_snapshot: saveIrpcBook(d, b) }),
b,
);
});
void test('new normal/FX result preserves old maps and acceptedresult in history,report frozen after change', () => {
const d = draft(),
b = irpcBook(d),
old = structuredClone(b.result),
s = JSON.parse(d.modelo_a_snapshot);
const next = recordIrpc(d, {
...b,
rows: [
...b.rows,
{ ...row('fx'), currency: 'USD', base: '1000', fxSelection: fx() },
],
});
d.modelo_a_snapshot = saveIrpcBook(d, next);
for (const [k, v] of Object.entries(s))
if (k !== 'irpcWithholdings')
assert.deepEqual(JSON.parse(d.modelo_a_snapshot)[k], v);
assert.deepEqual(next.history.at(-1), old);
next.rows.at(-1).fxSelection.mean = '65';
assert.equal(irpcCurrent(d, next), false);
d.modelo_a_snapshot = saveIrpcBook(d, next);
const report = irpcReportRows(d).at(-1);
assert.match(report[2], /12800.00/);
assert.match(report[3], /Histórico/);
assert.match(report[3], /taxa: 64/);
});
void test('export keeps currencies,normal/special distinctions,payment refs and externalformalities separate', () => {
const d = draft(),
r = { ...row(), currency: 'USD', base: '1000', fxSelection: fx() };
d.modelo_a_snapshot = saveIrpcBook(d, recordIrpc(d, book(r)));
const h = irpcPreparationHtml(d);
assert.match(h, /Original: 1000 USD/);
assert.ok(!h.includes('selectionKey'));
assert.match(h, /20%/);
assert.match(h, /CER/);
assert.match(h, /sem submissão/);
assert.match(h, /2026-10/);
});
if (process.env.IRPC_GROUPED_FIXTURE_OUTPUT) {
const d = draft(),
old = irpcBook(d),
s = fx(),
f = {
...newCurrencyEvent('retention-fx', 'MZN'),
description: 'SYNTHETIC existing BMselection for retention',
fxSelection: s,
sourceCurrency: 'USD',
fxDirection: s.direction,
fxRate: s.applied.rate,
fxDate: s.applied.date,
fxSource: s.applied.source,
};
const cb = currencyEventBook(d);
d.modelo_a_snapshot = saveCurrencyEventBook(d, {
...cb,
rows: [...cb.rows, f],
});
const source = irpcFxSources(d).find((v) => v.id === f.id);
const foreign = {
...row('foreign-royalty'),
currency: 'USD',
base: '1000',
fxSelection: source.selection,
fxLink: { id: source.id, fingerprint: source.fingerprint },
settlement: 'complete',
settlementRef: 'SYNTHETIC IRPC remittance5000',
allocations: [{ paymentId: 'p3', amount: '5000' }],
},
special = {
...row('special-telecom'),
kind: 'telecom',
ordinary: { ...row().ordinary, specialFacts: 'yes' },
},
resident = {
...row('resident-royalty'),
residence: 'resident',
base: '50000',
},
received = {
...row('received-foreign-royalty'),
direction: 'received',
currency: 'USD',
base: '100',
fxSelection: s,
settlement: 'complete',
settlementRef: 'SYNTHETIC proof10USD converted640MZN',
allocations: [{ paymentId: 'p4', amount: '640' }],
};
const b = recordIrpc(d, {
...old,
rows: [...old.rows, foreign, special, resident, received],
payments: [
...old.payments,
{
id: 'p3',
kind: 'remittance',
reference: 'SYNTHETIC-IRPC-AT5000',
date: '2026-10-20',
amount: '5000',
currency: 'MZN',
},
{
id: 'p4',
kind: 'withholding_proof',
reference: 'SYNTHETIC-IRPC-USD10',
date: '2026-10-20',
amount: '10',
currency: 'USD',
fxSelection: s,
},
],
});
d.modelo_a_snapshot = saveIrpcBook(d, b);
fs.writeFileSync(
process.env.IRPC_GROUPED_FIXTURE_OUTPUT,
JSON.stringify(d, null, 2) + '\n',
);
}
void test('62(3) definitive capital needs exact seat/management facts and is not an annual credit', () => {
const r = { ...row(), residence: 'resident', kind: 'capital' };
r.ordinary.capitalType = 'listed_debt_mz';
assert.equal(calc(r).tax, null);
r.ordinary.capitalSeatAndManagement = 'yes';
assert.equal(calc(r).nature, 'definitive');
assert.equal(calc(r).rate, 20);
assert.equal('annualCredit' in calc(r), false);
});
void test('confirming unchanged FX preserves source link;deliberate input change detaches only that selection', () => {
const input = {
currency: 'USD',
fxSelection: fx(),
fxLink: { id: 'saved', fingerprint: 'original' },
};
const confirmed = {
...input.fxSelection,
applied: selectFx(input.fxSelection),
};
assert.deepEqual(updateIrpcFx(input, confirmed).fxLink, input.fxLink);
assert.equal(
updateIrpcFx(input, { ...input.fxSelection, mean: '65' }).fxLink,
undefined,
);
assert.equal(input.fxSelection.mean, '64');
assert.equal(input.fxLink.id, 'saved');
});