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

302 lines
11 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {
newIrpcRow,
emptyIrpcOrdinaryFacts,
recordIrpc,
saveIrpcBook,
irpcBook,
irpcCurrent,
irpcReportRows,
} from '../../resources/js/lib/irpc-withholdings.ts';
import {
newMrftaInput,
prepareMrfta,
recordMrfta,
mrftaCurrent,
mrftaHtml,
} from '../../resources/js/lib/irpc-mrfta.ts';
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
const draft = () =>
JSON.parse(
fs.readFileSync(
new URL('./fixtures/irpc-grouped-preserved.json', import.meta.url),
'utf8',
),
);
const row = (id = 'principal') => ({
...newIrpcRow(id),
counterparty: 'QA contraparte',
documentRef: 'QA-' + id,
base: '100000',
kind: 'royalty',
recipientIrpc: 'yes',
payerIrpc: 'yes',
residence: 'nonresident',
permanentEstablishment: 'no',
mozambique: 'yes',
qualificationRef: 'QA contrato e residência',
specialRelief: 'none',
eventsComplete: 'yes',
events: [{ kind: 'cost', date: '2026-10-02', reference: 'QA custo' }],
settlement: 'complete',
settlementRef: 'QA reconciliação',
allocations: [{ paymentId: 'p', amount: '3000' }],
ordinary: {
...emptyIrpcOrdinaryFacts(),
nonDigital: 'yes',
reference: 'QA royalties não digitais',
},
});
const input = () => ({
...newMrftaInput(),
period: '2026-10',
declaration: 'initial',
timing: 'late',
nuit: '999999999',
name: 'QA sujeito passivo',
commercialName: 'QA',
taxArea: 'QA área fiscal',
address: 'QA domicílio',
contact: 'qa@example.invalid',
completeness: 'yes',
completenessRef: 'QA conferência do período e livros',
otherRubrics: 'none',
otherRubricsRef:
'QA ausência documentada de IRPS/tributação autónoma/outros valores',
codes: [
{
rowId: 'principal',
cer: '999',
reference: 'QA CER fictício, apenas teste',
},
],
interest: {
...newMrftaInput().interest,
mode: 'assessment',
period: '2026-10',
amount: '500',
date: '2026-11-25',
reference: 'QA liquidação de juros compensatórios',
},
});
function book(rows = [row()]) {
return {
...recordIrpc(draft(), {
version: 1,
rows,
payments: [
{
id: 'p',
kind: 'remittance',
reference: 'QA entrega IRPC',
date: '2026-11-15',
amount: '3000',
currency: 'MZN',
},
],
}),
mrfta: input(),
};
}
void test('MRFTA fills one period with principal, documented interest, payment and balance separately', () => {
const b = book(),
m = prepareMrfta(b, true);
assert.deepEqual(m.pending, []);
assert.equal(m.principal.numerator, '20000');
assert.equal(m.paid.numerator, '3000');
assert.equal(m.interest.numerator, '500');
assert.equal(m.total.numerator, '17500');
assert.equal(m.lines[0].rate, 20);
assert.equal(m.lines[0].cer, '999');
assert.match(m.interestReason, /transcritos/);
});
void test('unknown MAIBOR2026 never becomes zero or MIMO; independent principal remains calculated', () => {
const b = book();
b.mrfta.interest.mode = '';
const m = prepareMrfta(b, true);
assert.equal(m.interest, null);
assert.equal(m.total, null);
assert.equal(m.principal.numerator, '20000');
assert.equal(b.result.rows[0].status, 'calculated');
assert.match(m.pending.join(' '), /MAIBOR2026/);
});
void test('insufficient CER or contradictory rate per rubric blocks total without losing rows', () => {
const b = book();
b.mrfta.codes = [];
let m = prepareMrfta(b, true);
assert.equal(m.total, null);
assert.equal(m.lines.length, 1);
assert.match(m.pending.join(' '), /CER/);
const other = structuredClone(b.result.rows[0]);
other.id = 'other';
other.rate = 10;
other.tax = { numerator: '10000', denominator: '1' };
b.result.rows.push(other);
b.result.inputs.push({ ...row('other'), allocations: [] });
b.mrfta.codes = [
{ rowId: 'principal', cer: '999', reference: 'QA ref' },
{ rowId: 'other', cer: '999', reference: 'QA ref' },
];
m = prepareMrfta(b, true);
assert.equal(m.lines.length, 2);
assert.match(m.pending.join(' '), /taxas ou CER distintos/);
assert.equal(m.total, null);
});
void test('other months,suffered withholdings and documented dispensas stay outside payable rows', () => {
const b = book();
for (const [id, patch, original] of [
['other-month', { period: '2026-11' }, {}],
['suffered', {}, { direction: 'received' }],
[
'dispensed',
{
treatment: 'dispensed',
tax: { numerator: '0', denominator: '1' },
},
{},
],
]) {
b.result.rows.push({ ...b.result.rows[0], id, ...patch });
b.result.inputs.push({ ...row(id), ...original });
}
const m = prepareMrfta(b, true);
assert.equal(m.lines.length, 1);
assert.equal(m.excluded.length, 3);
assert.equal(m.principal.numerator, '20000');
assert.equal(m.total.numerator, '17500');
});
void test('unsupported digital and indeterminate cases retain explicit gaps without guessed rubric', () => {
const b = book();
b.result.inputs[0].kind = 'digital_services';
let m = prepareMrfta(b, true);
assert.equal(m.lines.length, 0);
assert.equal(m.total, null);
assert.match(m.pending.join(' '), /digital/);
b.result.inputs[0].kind = 'royalty';
b.result.rows[0].status = 'indeterminate';
b.result.rows[0].tax = null;
m = prepareMrfta(b, true);
assert.equal(m.total, null);
assert.match(m.pending.join(' '), /indeterminado/);
});
void test('timely remittance only yields zero interest with complete proof and explicit applicable deadline', () => {
const r = row();
r.base = '15000';
const b = book([r]);
b.mrfta.interest = {
...b.mrfta.interest,
mode: 'timely',
period: '2026-10',
dueDate: '2026-11-20',
dueReference: 'QA prazo documentado e calendário',
};
assert.equal(prepareMrfta(b, true).interest.numerator, '0');
assert.equal(prepareMrfta(b, true).total.numerator, '0');
b.mrfta.interest.dueDate = '2026-11-10';
assert.equal(prepareMrfta(b, true).interest, null);
b.mrfta.interest.dueDate = '2026-11-20';
b.result.rows[0].settled = null;
assert.equal(prepareMrfta(b, true).interest, null);
});
void test('assessment cannot leak into another period or accept negative amount and missing reference', () => {
const b = book();
for (const patch of [
{ period: '2026-11' },
{ amount: '-10' },
{ reference: '' },
{ date: '2026-02-30' },
]) {
const copy = structuredClone(b);
Object.assign(copy.mrfta.interest, patch);
assert.equal(prepareMrfta(copy, true).interest, null);
assert.equal(prepareMrfta(copy, true).total, null);
}
});
void test('MRFTA save,recover,history and dossier preserve existing tax maps and freeze evidence', () => {
const d = draft(),
prior = irpcBook(d),
original = JSON.parse(d.modelo_a_snapshot);
let b = book();
b.history = [prior.result];
b = recordMrfta(b, true);
d.modelo_a_snapshot = saveIrpcBook(d, b);
const recovered = JSON.parse(JSON.stringify(d)),
saved = irpcBook(recovered);
assert.deepEqual(saved, b);
assert.equal(irpcCurrent(recovered, saved), true);
assert.equal(mrftaCurrent(saved, true), true);
for (const [k, v] of Object.entries(original))
if (k !== 'irpcWithholdings')
assert.deepEqual(JSON.parse(recovered.modelo_a_snapshot)[k], v, k);
const report = irpcReportRows(recovered);
assert.match(JSON.stringify(report), /17500/);
assert.match(
JSON.stringify(
createProjectDossierReport(recovered, {
selectedSections: new Set(['finance']),
}),
),
/MRFTA/,
);
assert.match(mrftaHtml(saved, true), /Quadro7/);
assert.match(mrftaHtml(saved, true), /QA CER fictício/);
const before = structuredClone(saved.mrftaResult);
saved.mrfta.interest.amount = '600';
assert.equal(mrftaCurrent(saved, true), false);
assert.throws(() => mrftaHtml(saved, true));
const next = recordMrfta(saved, true);
assert.deepEqual(next.mrftaHistory.at(-1), before);
assert.equal(next.mrftaResult.model.total.numerator, '17600');
fs.mkdirSync('outputs/irpc-mrfta', { recursive: true });
fs.writeFileSync(
'outputs/irpc-mrfta/calculated.json',
JSON.stringify(recovered),
);
fs.writeFileSync('outputs/irpc-mrfta/preparation.html', mrftaHtml(b, true));
});
void test('malformed persisted MRFTA rejects recovery without replacing originals', () => {
const b = book();
b.mrfta.codes.push({ ...b.mrfta.codes[0] });
assert.throws(() => saveIrpcBook(draft(), b));
b.mrfta.codes = [];
b.mrfta.interest.amount = 5;
assert.throws(() => saveIrpcBook(draft(), b));
});
void test('export escapes identities,documents and assessment references', () => {
const b = book();
b.mrfta.name = '<script>QA</script>';
b.mrfta.interest.reference = '<img src=x onerror=QA>';
const html = mrftaHtml(recordMrfta(b, true), true);
assert.ok(!html.includes('<script>QA'));
assert.ok(html.includes('&lt;script&gt;'));
assert.ok(html.includes('&lt;img'));
});
void test('other tax rubrics cannot silently become zero and partial rows remain available', () => {
const b = book();
b.mrfta.otherRubrics = 'pending';
const m = prepareMrfta(b, true);
assert.equal(m.total, null);
assert.equal(m.principal.numerator, '20000');
assert.match(m.pending.join(' '), /IRPS,tributação autónoma/);
assert.throws(() => mrftaHtml(recordMrfta(b, false), false));
});
void test('same rubric rate and documented CER aggregate without losing document references', () => {
const other = { ...row('other'), allocations: [], settlement: 'none' },
b = book([row(), other]);
b.mrfta.codes.push({
rowId: 'other',
cer: '999',
reference: 'QA segundo documento de CER fictício',
});
const m = prepareMrfta(b, true);
assert.equal(m.lines.length, 1);
assert.equal(m.lines[0].base.numerator, '200000');
assert.equal(m.principal.numerator, '40000');
assert.equal(m.total.numerator, '37500');
assert.equal(m.lines[0].references.length, 2);
assert.equal(m.lines[0].documents.length, 2);
});