570 lines
21 KiB
JavaScript
570 lines
21 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
normalBook,
|
|
normalCurrent,
|
|
saveNormalBook,
|
|
} from '../../resources/js/lib/irpc-normal.ts';
|
|
import {
|
|
irpcBook,
|
|
newIrpcRow,
|
|
saveIrpcBook,
|
|
recordIrpc,
|
|
} from '../../resources/js/lib/irpc-withholdings.ts';
|
|
import {
|
|
annualBook,
|
|
newAnnualBook,
|
|
newAnnualPpc,
|
|
saveAnnualBook,
|
|
reuseAnnualAssessment,
|
|
annualAssessment,
|
|
annualWithholdingOptions,
|
|
reuseAnnualWithholding,
|
|
calculateAnnual,
|
|
recordAnnual,
|
|
annualCurrent,
|
|
annualReportRows,
|
|
annualHtml,
|
|
} from '../../resources/js/lib/irpc-annual-settlement.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
const read = (n) =>
|
|
JSON.parse(
|
|
fs.readFileSync(new URL('./fixtures/' + n, import.meta.url), 'utf8'),
|
|
);
|
|
const val = (v) => (v ? Number(v.numerator) / Number(v.denominator) : null);
|
|
const now = '2026-10-26T18:00:00+02:00';
|
|
const original = () => read('irpc-assessment-preserved.json');
|
|
function acceptedL7(expense = false) {
|
|
const d = read('irpc-l7-preserved.json')[
|
|
expense ? 'expense' : 'acceleration'
|
|
];
|
|
if (!expense) {
|
|
const f = read('irpc-l7-continuation-preserved.json');
|
|
d.business_plan_snapshot = f.plan;
|
|
d.modelo_a_snapshot = saveNormalBook(d, f.book);
|
|
}
|
|
return d;
|
|
}
|
|
function setup(rf = '300', ppc = '500') {
|
|
const d = original(),
|
|
l5 = irpcBook(d);
|
|
if (rf !== null) {
|
|
const row = {
|
|
...newIrpcRow('L8-received'),
|
|
direction: 'received',
|
|
origin: 'effective',
|
|
counterparty: 'QA pagador A',
|
|
documentRef: 'QA rendimento próprio 2026',
|
|
base: String(Number(rf) * 5),
|
|
currency: 'MZN',
|
|
kind: 'royalty',
|
|
recipientIrpc: 'yes',
|
|
payerIrpc: 'yes',
|
|
residence: 'resident',
|
|
mozambique: 'yes',
|
|
qualificationRef:
|
|
'QA royalties próprios incluídos no resultado anual',
|
|
specialRelief: 'none',
|
|
eventsComplete: 'yes',
|
|
events: [
|
|
{
|
|
kind: 'payment',
|
|
date: '2026-10-02',
|
|
reference: 'QA rendimento e primeiro evento 2026',
|
|
},
|
|
],
|
|
settlement: 'complete',
|
|
settlementRef: 'QA retenção efectivamente sofrida e reconciliada',
|
|
allocations: [{ paymentId: 'RF-L8', amount: rf }],
|
|
ordinary: {
|
|
nonDigital: 'yes',
|
|
capitalType: '',
|
|
rentOwnPropertyManagement: '',
|
|
specialFacts: 'no',
|
|
capitalSeatAndManagement: '',
|
|
reference: 'QA royalties não digitais de residente',
|
|
},
|
|
};
|
|
l5.rows.push(row);
|
|
l5.payments.push({
|
|
id: 'RF-L8',
|
|
kind: 'withholding_proof',
|
|
reference: 'QA-RF-2026-1',
|
|
date: '2026-10-02',
|
|
amount: rf,
|
|
currency: 'MZN',
|
|
});
|
|
d.modelo_a_snapshot = saveIrpcBook(d, recordIrpc(d, l5, now));
|
|
}
|
|
let b = reuseAnnualAssessment(d, newAnnualBook());
|
|
b.reference =
|
|
'QA entidade123456789,exercício2026,declaração integral dos abatimentos';
|
|
b.assessmentReference = 'QA colecta válida já demonstrada1408';
|
|
b.withholdings = {
|
|
state: rf === null ? 'absent' : 'known',
|
|
reference:
|
|
'QA inventário completo das retenções efectivamente sofridas por este sujeito no exercício',
|
|
rows: [],
|
|
};
|
|
b.ppc = {
|
|
state: ppc === null ? 'absent' : 'known',
|
|
reference: 'QA inventário completo dos PPC efectuados no exercício',
|
|
rows: [],
|
|
};
|
|
b.pec = { state: 'absent', amount: '', reference: 'QA ausência de PEC' };
|
|
b.credits = {
|
|
state: 'known',
|
|
amount: '0',
|
|
reference: 'QA créditos com regras próprias conhecidos zero',
|
|
};
|
|
if (rf !== null) {
|
|
b = reuseAnnualWithholding(d, b, 'L8-received', 'RF-L8', 'claim-rf');
|
|
Object.assign(b.withholdings.rows[0], {
|
|
incomeIncluded: 'yes',
|
|
reference:
|
|
'QA próprio beneficiário NUIT123456789;rendimento incluído na base anual2026',
|
|
});
|
|
}
|
|
if (ppc !== null)
|
|
b.ppc.rows.push({
|
|
...newAnnualPpc('claim-ppc'),
|
|
document: 'QA-PPC-2026-1',
|
|
issuer: 'QA AT',
|
|
date: '2026-06-30',
|
|
subjectNuit: b.subjectNuit,
|
|
year: b.year,
|
|
proofAmount: ppc,
|
|
amount: ppc,
|
|
effective: 'yes',
|
|
reference:
|
|
'QA recibo de PPC efectivo e afectação ao sujeito/exercício',
|
|
});
|
|
return { d, b };
|
|
}
|
|
const model = (d, b) => calculateAnnual(d, b);
|
|
const pending = (d, b) => {
|
|
const m = model(d, b);
|
|
assert.equal(m.status, 'indeterminate', m.pending.join(' | '));
|
|
assert.equal(m.arithmeticBalance, null);
|
|
assert.equal(m.payable, null);
|
|
assert.equal(m.recoverable, null);
|
|
return m;
|
|
};
|
|
|
|
void test('optional L8 leaves accepted assessment, other books and report rows untouched', () => {
|
|
const d = original(),
|
|
before = JSON.stringify(d),
|
|
b = normalBook(d);
|
|
assert.equal(normalCurrent(d, b), true);
|
|
assert.equal(val(annualAssessment(d).colecta), 1408);
|
|
assert.equal(annualBook(d), undefined);
|
|
assert.deepEqual(annualReportRows(d), []);
|
|
assert.equal(JSON.stringify(d), before);
|
|
const old = acceptedL7();
|
|
assert.deepEqual(annualReportRows(old), []);
|
|
assert.equal(
|
|
normalBook(old).result.model.version,
|
|
'IRPC-normal-adjustments/13',
|
|
);
|
|
});
|
|
void test('1408 minus actual received300 and provenPPC500 yields608 payable without consuming official balances', () => {
|
|
const { d, b } = setup(),
|
|
before = d.modelo_a_snapshot,
|
|
m = model(d, b);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(m.status, 'payable');
|
|
assert.equal(val(m.knownWithholdings), 300);
|
|
assert.equal(val(m.knownPpc), 500);
|
|
assert.equal(val(m.payable), 608);
|
|
assert.equal(val(m.recoverable), 0);
|
|
assert.equal(m.officialBalancesConsumed, false);
|
|
assert.equal(m.submitted, false);
|
|
assert.equal(d.modelo_a_snapshot, before);
|
|
});
|
|
void test('1408 minus1000 and600 yields192 recoverable;408 and1000 balances exactly', () => {
|
|
for (const [rf, ppc, status, balance] of [
|
|
['1000', '600', 'recoverable', -192],
|
|
['408', '1000', 'balanced', 0],
|
|
]) {
|
|
const { d, b } = setup(rf, ppc),
|
|
m = model(d, b);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(m.status, status);
|
|
assert.equal(val(m.arithmeticBalance), balance);
|
|
if (typeof balance !== 'number')
|
|
throw new Error('Invalid balance fixture');
|
|
assert.equal(val(m.recoverable), balance < 0 ? -balance : 0);
|
|
}
|
|
});
|
|
void test('declared absence yields1408;known zero has its own proof and missing never becomes zero', () => {
|
|
const { d, b } = setup(null, null);
|
|
assert.equal(val(model(d, b).payable), 1408);
|
|
b.credits.state = '';
|
|
assert.match(pending(d, b).pending.join(' '), /Créditos/);
|
|
b.credits = { state: 'absent', amount: '', reference: 'QA ausência' };
|
|
b.ppc.state = '';
|
|
assert.match(pending(d, b).pending.join(' '), /PPC/);
|
|
const zero = setup('0', '0');
|
|
assert.equal(val(model(zero.d, zero.b).payable), 1408);
|
|
assert.equal(model(zero.d, zero.b).input.withholdings.state, 'known');
|
|
});
|
|
void test('effective received L5 proof is required, not a projection,remittance or definitive withholding', () => {
|
|
for (const patch of [
|
|
{ origin: 'projected' },
|
|
{ direction: 'paid' },
|
|
{ residence: 'nonresident', permanentEstablishment: 'no' },
|
|
]) {
|
|
const { d, b } = setup(),
|
|
l5 = irpcBook(d);
|
|
Object.assign(
|
|
l5.rows.find((r) => r.id === 'L8-received'),
|
|
patch,
|
|
);
|
|
d.modelo_a_snapshot = saveIrpcBook(d, recordIrpc(d, l5, now));
|
|
const option = annualWithholdingOptions(d).find(
|
|
(o) => o.rowId === 'L8-received',
|
|
);
|
|
assert.equal(option.ready, false);
|
|
pending(d, b);
|
|
}
|
|
const { d, b } = setup(),
|
|
l5 = irpcBook(d);
|
|
l5.payments.find((p) => p.id === 'RF-L8').kind = 'remittance';
|
|
d.modelo_a_snapshot = saveIrpcBook(d, recordIrpc(d, l5, now));
|
|
pending(d, b);
|
|
});
|
|
void test('beneficiary,income inclusion,proof date,entity,exercise and actualPPC are mandatory', () => {
|
|
for (const change of [
|
|
(b) => (b.withholdings.rows[0].incomeIncluded = ''),
|
|
(b) => (b.withholdings.rows[0].reference = ''),
|
|
(b) => (b.withholdings.rows[0].subjectNuit = '999999999'),
|
|
(b) => (b.year = '2025'),
|
|
(b) => (b.ppc.rows[0].subjectNuit = '999999999'),
|
|
(b) => (b.ppc.rows[0].year = '2025'),
|
|
(b) => (b.ppc.rows[0].currency = 'USD'),
|
|
(b) => (b.ppc.rows[0].date = '2026-02-30'),
|
|
(b) => (b.ppc.rows[0].date = '2027-01-01'),
|
|
(b) => (b.ppc.rows[0].effective = 'no'),
|
|
(b) => (b.ppc.rows[0].reference = ''),
|
|
(b) => (b.reference = ''),
|
|
]) {
|
|
const { d, b } = setup();
|
|
change(b);
|
|
pending(d, b);
|
|
}
|
|
});
|
|
void test('selected current source and reusing collection never overwrite deliberate entity/year edits', () => {
|
|
const { d, b } = setup();
|
|
b.subjectNuit = '999999999';
|
|
b.year = '2025';
|
|
const next = reuseAnnualAssessment(d, b);
|
|
assert.equal(next.subjectNuit, '999999999');
|
|
assert.equal(next.year, '2025');
|
|
pending(d, next);
|
|
const other = setup();
|
|
other.b.assessmentKey = 'old-source';
|
|
pending(other.d, other.b);
|
|
});
|
|
void test('absence contradiction and incomplete group inventories are indeterminate,not assumed totals', () => {
|
|
for (const change of [
|
|
(b) => {
|
|
b.withholdings = {
|
|
state: 'absent',
|
|
reference: 'QA ausência contraditória',
|
|
rows: [],
|
|
};
|
|
},
|
|
(b) => (b.ppc.rows = []),
|
|
(b) => (b.ppc.state = 'absent'),
|
|
(b) => (b.withholdings.reference = ''),
|
|
]) {
|
|
const { d, b } = setup();
|
|
change(b);
|
|
pending(d, b);
|
|
}
|
|
});
|
|
void test('duplicate pair,duplicatePPC,overclaim and cross-kind physical proof cannot be deducted twice', () => {
|
|
for (const change of [
|
|
(b) =>
|
|
b.withholdings.rows.push({
|
|
...b.withholdings.rows[0],
|
|
id: 'duplicate',
|
|
}),
|
|
(b) => b.ppc.rows.push({ ...b.ppc.rows[0], id: 'duplicate' }),
|
|
(b) => (b.withholdings.rows[0].amount = '301'),
|
|
(b) => (b.ppc.rows[0].amount = '501'),
|
|
(b) =>
|
|
Object.assign(b.ppc.rows[0], {
|
|
document: ' QA-RF-2026-1 ',
|
|
issuer: 'QA pagador A',
|
|
date: '2026-10-02',
|
|
amount: '100',
|
|
proofAmount: '300',
|
|
}),
|
|
]) {
|
|
const { d, b } = setup();
|
|
change(b);
|
|
pending(d, b);
|
|
}
|
|
const { d, b } = setup();
|
|
assert.throws(
|
|
() => reuseAnnualWithholding(d, b, 'L8-received', 'RF-L8', 'again'),
|
|
/já seleccionada/,
|
|
);
|
|
});
|
|
void test('partial proof allocations allowed only within its available amount across context maps', () => {
|
|
const { d, b } = setup(null, '500');
|
|
b.ppc.rows[0].amount = '300';
|
|
const other = structuredClone(b);
|
|
other.ppc.rows[0].id = 'other';
|
|
other.ppc.rows[0].amount = '200';
|
|
const s = JSON.parse(d.modelo_a_snapshot);
|
|
s.irpcAnnual = { 'opportunity:other': other };
|
|
d.modelo_a_snapshot = JSON.stringify(s);
|
|
assert.equal(val(model(d, b).payable), 1108);
|
|
other.ppc.rows[0].amount = '201';
|
|
d.modelo_a_snapshot = JSON.stringify(s);
|
|
pending(d, b);
|
|
other.ppc.rows[0].amount = '100';
|
|
other.ppc.rows[0].subjectNuit = '999999999';
|
|
d.modelo_a_snapshot = JSON.stringify(s);
|
|
pending(d, b);
|
|
other.ppc.rows[0].subjectNuit = b.subjectNuit;
|
|
other.ppc.rows[0].year = '2025';
|
|
d.modelo_a_snapshot = JSON.stringify(s);
|
|
pending(d, b);
|
|
});
|
|
void test('positive PEC or specialcredit stays separated with null final balances,explicitzero permitted', () => {
|
|
for (const key of ['pec', 'credits']) {
|
|
const { d, b } = setup();
|
|
b[key] = {
|
|
state: 'known',
|
|
amount: '10',
|
|
reference: 'QA saldo positivo exige regra própria',
|
|
};
|
|
const m = pending(d, b);
|
|
assert.match(m.pending.join(' '), /fora deste recorte/);
|
|
assert.equal(val(m.knownPpc), 500);
|
|
b[key].amount = '0';
|
|
assert.equal(model(d, b).status, 'payable');
|
|
b[key] = {
|
|
state: 'absent',
|
|
amount: '0',
|
|
reference: 'QA ausência contradiz montante',
|
|
};
|
|
pending(d, b);
|
|
}
|
|
});
|
|
void test('changing receipt date cannot evade identity checks;copied L5 allocation cannot consume larger proof twice', () => {
|
|
const p = setup();
|
|
p.b.ppc.rows.push({
|
|
...p.b.ppc.rows[0],
|
|
id: 'other-date',
|
|
date: '2026-07-01',
|
|
amount: '100',
|
|
});
|
|
pending(p.d, p.b);
|
|
const { d, b } = setup();
|
|
const l5 = irpcBook(d);
|
|
l5.payments.find((p) => p.id === 'RF-L8').amount = '1000';
|
|
d.modelo_a_snapshot = saveIrpcBook(d, recordIrpc(d, l5, now));
|
|
b.withholdings.rows = [];
|
|
const fresh = reuseAnnualWithholding(d, b, 'L8-received', 'RF-L8', 'rf');
|
|
Object.assign(fresh.withholdings.rows[0], {
|
|
amount: '200',
|
|
incomeIncluded: 'yes',
|
|
reference: 'QA próprio beneficiário e rendimento incluído',
|
|
});
|
|
const other = structuredClone(fresh);
|
|
other.withholdings.rows[0].amount = '101';
|
|
other.ppc.rows = [];
|
|
other.ppc.state = 'absent';
|
|
const s = JSON.parse(d.modelo_a_snapshot);
|
|
s.irpcAnnual = { 'opportunity:other': other };
|
|
s.irpcWithholdings['opportunity:other'] = structuredClone(l5);
|
|
d.modelo_a_snapshot = JSON.stringify(s);
|
|
d.modelo_a_snapshot = saveIrpcBook(d, recordIrpc(d, irpcBook(d), now));
|
|
fresh.withholdings.rows[0].sourceKey = annualWithholdingOptions(d).find(
|
|
(o) => o.rowId === 'L8-received',
|
|
).key;
|
|
other.withholdings.rows[0].sourceKey = fresh.withholdings.rows[0].sourceKey;
|
|
const current = JSON.parse(d.modelo_a_snapshot);
|
|
current.irpcAnnual['opportunity:other'] = other;
|
|
d.modelo_a_snapshot = JSON.stringify(current);
|
|
assert.match(pending(d, fresh).pending.join(' '), /Mesma afectação L5/);
|
|
other.withholdings.rows[0].amount = '100';
|
|
d.modelo_a_snapshot = JSON.stringify(current);
|
|
assert.equal(model(d, fresh).status, 'payable');
|
|
});
|
|
void test('annual accepted L7 accelerated assessment is reused once rather than summed with L6', () => {
|
|
const d = read('irpc-l7-preserved.json').acceleration,
|
|
b = reuseAnnualAssessment(d, newAnnualBook());
|
|
b.reference = 'QA entidade/exercício';
|
|
b.assessmentReference = 'QA colecta L7';
|
|
for (const key of ['withholdings', 'ppc', 'pec', 'credits']) {
|
|
b[key].state = 'absent';
|
|
b[key].reference = 'QA ausência declarada';
|
|
}
|
|
const m = model(d, b);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.equal(m.assessment.kind, 'special');
|
|
assert.equal(val(m.payable), 1996.8);
|
|
assert.notEqual(
|
|
val(m.payable),
|
|
val(normalBook(d).result.model.colecta) + 1996.8,
|
|
);
|
|
});
|
|
void test('100 minimum is applied symmetrically;99.99 kept exact without invented rounding or carry', () => {
|
|
for (const [ppc, status, balance] of [
|
|
['1308.01', 'below_minimum', 99.99],
|
|
['1308', 'payable', 100],
|
|
['1507.99', 'below_minimum', -99.99],
|
|
['1508', 'recoverable', -100],
|
|
]) {
|
|
const { d, b } = setup(null, ppc),
|
|
m = model(d, b);
|
|
assert.equal(m.status, status);
|
|
assert.equal(val(m.arithmeticBalance), balance);
|
|
if (status === 'below_minimum') {
|
|
assert.deepEqual(m.belowMinimum, {
|
|
numerator: '9999',
|
|
denominator: '100',
|
|
});
|
|
assert.equal(val(m.payable), 0);
|
|
assert.equal(val(m.recoverable), 0);
|
|
}
|
|
}
|
|
const { d, b } = setup();
|
|
b.ppc.rows[0].amount = '0.001';
|
|
pending(d, b);
|
|
});
|
|
void test('existing L7 source cannot be bypassed by choosing L6 and positiveautonomous/shortperiod remain scoped', () => {
|
|
for (const expense of [false, true]) {
|
|
const d = acceptedL7(expense);
|
|
let b = reuseAnnualAssessment(d, newAnnualBook());
|
|
b.reference = 'QA fecho';
|
|
b.assessmentReference = 'QA colecta';
|
|
for (const key of ['withholdings', 'ppc', 'pec', 'credits']) {
|
|
b[key].state = 'absent';
|
|
b[key].reference = 'QA sem movimentos';
|
|
}
|
|
assert.equal(b.assessment, 'special');
|
|
let m = pending(d, b);
|
|
if (!expense) assert.match(m.pending.join(' '), /períodos especiais/);
|
|
if (expense) assert.match(m.pending.join(' '), /Tributação autónoma/);
|
|
b.assessment = 'normal';
|
|
assert.match(pending(d, b).pending.join(' '), /não corrente/);
|
|
}
|
|
});
|
|
void test('record/recover/recalculate appends history unchanged,preserves all old books/plan,no double deduction', () => {
|
|
const { d, b } = setup(),
|
|
before = JSON.parse(d.modelo_a_snapshot),
|
|
plan = d.business_plan_snapshot;
|
|
d.modelo_a_snapshot = saveAnnualBook(d, recordAnnual(d, b, now));
|
|
const once = annualBook(d),
|
|
old = structuredClone(once.result);
|
|
assert.equal(annualCurrent(d, once), true);
|
|
d.modelo_a_snapshot = saveAnnualBook(d, recordAnnual(d, once, now));
|
|
const back = annualBook(JSON.parse(JSON.stringify(d)));
|
|
assert.deepEqual(back.history, [old]);
|
|
assert.equal(val(back.result.model.payable), 608);
|
|
assert.equal(d.business_plan_snapshot, plan);
|
|
const snapshot = JSON.parse(d.modelo_a_snapshot);
|
|
delete snapshot.irpcAnnual;
|
|
assert.deepEqual(snapshot, before);
|
|
b.ppc.rows[0].amount = '400';
|
|
assert.equal(annualCurrent(d, b), false);
|
|
});
|
|
void test('changed L5 or assessment invalidates current L8 while retaining historical report and references', () => {
|
|
const { d, b } = setup();
|
|
d.modelo_a_snapshot = saveAnnualBook(d, recordAnnual(d, b, now));
|
|
const back = annualBook(d),
|
|
old = structuredClone(back.result);
|
|
back.reference = 'NEW reference';
|
|
d.modelo_a_snapshot = saveAnnualBook(d, back);
|
|
assert.equal(annualCurrent(d, back), false);
|
|
assert.match(annualReportRows(d).flat().join(' '), /Histórico/);
|
|
assert.doesNotMatch(annualReportRows(d).flat().join(' '), /NEW reference/);
|
|
assert.throws(() => annualHtml(d), /fontes actuais/);
|
|
assert.deepEqual(back.result, old);
|
|
const n = normalBook(d);
|
|
n.base.identityReference += ' changed';
|
|
d.modelo_a_snapshot = saveNormalBook(d, n);
|
|
assert.equal(annualCurrent(d, back), false);
|
|
pending(d, back);
|
|
const changed = setup();
|
|
changed.d.modelo_a_snapshot = saveAnnualBook(
|
|
changed.d,
|
|
recordAnnual(changed.d, changed.b, now),
|
|
);
|
|
const saved = annualBook(changed.d),
|
|
prior = structuredClone(saved.result);
|
|
const l5 = irpcBook(changed.d);
|
|
l5.payments.find((p) => p.id === 'RF-L8').reference += ' corrected';
|
|
changed.d.modelo_a_snapshot = saveIrpcBook(
|
|
changed.d,
|
|
recordIrpc(changed.d, l5, now),
|
|
);
|
|
assert.equal(annualCurrent(changed.d, saved), false);
|
|
pending(changed.d, saved);
|
|
assert.deepEqual(saved.result, prior);
|
|
});
|
|
void test('malformed optionalbook never resets accepted books;report explicitly preserves missing states', () => {
|
|
const { d, b } = setup();
|
|
b.credits.state = '';
|
|
d.modelo_a_snapshot = saveAnnualBook(d, recordAnnual(d, b, now));
|
|
assert.match(annualHtml(d), /Indeterminado/);
|
|
assert.match(annualHtml(d), /Informação em falta/);
|
|
assert.match(
|
|
JSON.stringify(
|
|
createProjectDossierReport(d, {
|
|
selectedSections: new Set(['finance']),
|
|
}),
|
|
),
|
|
/Saldo anual simulado/,
|
|
);
|
|
const old = d.modelo_a_snapshot;
|
|
b.ppc.rows.push({ ...b.ppc.rows[0] });
|
|
assert.throws(() => saveAnnualBook(d, b), /identificador repetido/);
|
|
assert.equal(d.modelo_a_snapshot, old);
|
|
});
|
|
void test('prepare only L8 delta cases for guarded API and report recovery', () => {
|
|
const cases = {
|
|
payable: setup(),
|
|
recoverable: setup('1000', '600'),
|
|
balanced: setup('408', '1000'),
|
|
absent: setup(null, null),
|
|
missing: setup(),
|
|
duplicate: setup(),
|
|
pec: setup(),
|
|
};
|
|
cases.missing.b.ppc.rows[0].effective = '';
|
|
cases.duplicate.b.ppc.rows.push({
|
|
...cases.duplicate.b.ppc.rows[0],
|
|
id: 'duplicate',
|
|
});
|
|
cases.pec.b.pec = {
|
|
state: 'known',
|
|
amount: '10',
|
|
reference: 'QA PEC separado',
|
|
};
|
|
fs.mkdirSync('outputs/irpc-l8-settlement', { recursive: true });
|
|
for (const [name, { d, b }] of Object.entries(cases)) {
|
|
d.modelo_a_snapshot = saveAnnualBook(d, recordAnnual(d, b, now));
|
|
fs.writeFileSync(
|
|
'outputs/irpc-l8-settlement/' + name + '.json',
|
|
JSON.stringify(d),
|
|
);
|
|
fs.writeFileSync(
|
|
'outputs/irpc-l8-settlement/' + name + '.html',
|
|
annualHtml(d),
|
|
);
|
|
}
|
|
const { d } = setup();
|
|
fs.writeFileSync(
|
|
'outputs/irpc-l8-settlement/initial.json',
|
|
JSON.stringify(d),
|
|
);
|
|
});
|