import test from 'node:test'; import assert from 'node:assert/strict'; import { carryCase } from './helpers/irpc-carry-case.mjs'; import { internationalCase } from './helpers/irpc-international-case.mjs'; import { newCarryUse, calculateCarryLot, } from '../../resources/js/lib/irpc-credit-carry.ts'; import { calculateAnnual, prepareCreditCarry, recordAnnual, annualBook, annualCurrent, saveAnnualBook, annualHtml, annualReportRows, } from '../../resources/js/lib/irpc-annual-settlement.ts'; import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts'; const number = (v) => (v ? Number(v.numerator) / Number(v.denominator) : null); const calc = (c) => calculateAnnual(c.d, c.b); const lot = (c, i = 0) => c.b.creditCarry.lots[i]; const pending = (c) => { const m = calc(c); assert.equal(m.status, 'indeterminate', JSON.stringify(m.pending)); assert.equal(m.arithmeticBalance, null); assert.equal(m.internationalCredit.deducted, null); assert.equal(m.pecDeduction.deducted, null); return m; }; void test('2021 international and2023 PEC remain eligible in2026; origin deductions/uses count once:1408-8-50-200-100-500=550', () => { const m = calc(carryCase()); assert.deepEqual(m.pending, []); assert.equal(number(m.payable), 550); assert.equal(number(m.creditCarry.lots[0].opening), 100); assert.equal(number(m.creditCarry.lots[0].used), 30); assert.equal(number(m.creditCarry.lots[0].available), 70); assert.equal(number(m.creditCarry.lots[0].remaining), 20); assert.equal(m.creditCarry.lots[0].lastYear, 2026); assert.equal(m.creditCarry.lots[1].lastYear, 2026); assert.equal(number(m.creditCarry.lots[1].remaining), 50); assert.equal(m.creditCarry.carryCreated, false); assert.equal(m.creditCarry.officialBalancesConsumed, false); }); void test('five subsequent exercises differs from PEC three; expired source retained with zero selection', () => { for (const [i, year] of [ [0, '2020'], [1, '2022'], ]) { const c = carryCase(); lot(c, i).year = year; if (i === 0) lot(c).uses[0].year = '2023'; assert.match(pending(c).pending.join(' '), /Prazo ordinário terminado/); lot(c, i).selected = '0'; const m = calc(c); assert.deepEqual(m.pending, []); assert.equal(m.creditCarry.lots[i].expired, true); assert.equal(number(m.creditCarry.lots[i].available), 0); assert.ok(number(m.creditCarry.lots[i].remaining) > 0); } }); void test('future/current origin, nonordinary periods, currency and wrong subject do not create an eligible carry', () => { for (const patch of [ { year: '2026' }, { year: '2027' }, { year: '' }, { ordinary: '' }, { ordinary: 'no' }, { currency: 'USD' }, { subjectNuit: '999999999' }, { country: 'MZ' }, { country: 'ZZ' }, { kind: '' }, ]) { const c = carryCase(); Object.assign(lot(c), patch); pending(c); } const c = carryCase(); lot(c, 1).country = 'ZA'; pending(c); }); void test('origin eligibility, insufficient collecta and applicable rule require concrete evidence', () => { for (const patch of [ { originDocument: '' }, { originReference: '' }, { ruleReference: '' }, { insufficient: '' }, { insufficient: 'no' }, { eligible: '' }, { originDeducted: '' }, { originDeducted: '201' }, { selected: '' }, { selected: '70.01' }, ]) { const c = carryCase(); Object.assign(lot(c), patch); pending(c); } }); void test('unknown uses are not zero; every actual use must fit its origin/window and documentary identity', () => { for (const patch of [ { usesState: '' }, { usesReference: '' }, { usesState: 'absent' }, { uses: [] }, { uses: [ { ...newCarryUse('u'), year: '2021', amount: '1', document: 'D', reference: 'R', }, ], }, ]) { const c = carryCase(); Object.assign(lot(c), patch); pending(c); } for (const patch of [ { year: '2027' }, { amount: '101' }, { document: '' }, { reference: '' }, { amount: '' }, ]) { const c = carryCase(); Object.assign(lot(c).uses[0], patch); pending(c); } const c = carryCase(); lot(c).uses.push({ ...lot(c).uses[0], id: 'copy', year: '2024', document: ' synthetic USE2023 ', }); assert.match(pending(c).pending.join(' '), /repetido/); }); void test('actual current-year use is subtracted before the proposed new deduction', () => { const c = carryCase(); lot(c).uses.push({ id: 'current-actual', year: '2026', amount: '10', document: 'SYNTHETIC current actual use', reference: 'SYNTHETIC actual assessment, not this simulation', }); const m = calc(c); assert.equal(number(m.creditCarry.lots[0].available), 60); assert.equal(number(m.creditCarry.lots[0].remaining), 10); assert.equal(number(m.payable), 550); }); void test('original country/nature/year cannot be duplicated by changing document or id', () => { const c = carryCase(); c.b.creditCarry.lots.push({ ...structuredClone(lot(c)), id: 'other', originDocument: 'OTHER', uses: [], usesState: 'absent', }); assert.match(pending(c).pending.join(' '), /mesma natureza/); }); void test('cross-context duplicate origin blocks and external edits invalidate saved current result', () => { const c = carryCase(); c.d.modelo_a_snapshot = saveAnnualBook(c.d, recordAnnual(c.d, c.b)); const b = annualBook(c.d); assert.equal(annualCurrent(c.d, b), true); const s = JSON.parse(c.d.modelo_a_snapshot), other = structuredClone(c.b); other.creditCarry.lots = [ { ...structuredClone(lot(c)), id: 'copied-origin', originDocument: 'OTHER', uses: [], usesState: 'absent', }, ]; s.irpcAnnual['opportunity:other'] = other; c.d.modelo_a_snapshot = JSON.stringify(s); assert.equal(annualCurrent(c.d, b), false); assert.match(pending(c).pending.join(' '), /noutro mapa/); }); void test('explicit allocation can favour older balances without automatic reduction or FIFO', () => { const c = carryCase(); Object.assign(c.b.creditCarry, { currentInternational: '0', currentPec: '100', }); const m = calc(c); assert.equal(number(m.payable), 658); assert.equal(number(m.internationalCredit.deducted), 0); assert.equal(number(m.internationalCredit.notSelected), 8); assert.equal(m.internationalCredit.insufficientCollecta, null); assert.equal(number(m.pecDeduction.deducted), 100); assert.equal(number(m.pecDeduction.remaining), 100); }); void test('current selection cannot exceed known availability or total collecta; planned choices are kept on failure', () => { for (const patch of [ { currentInternational: '9' }, { currentPec: '201' }, { currentInternational: '' }, { currentPec: '' }, { allocationReference: '' }, { assessmentKey: 'stale' }, ]) { const c = carryCase(); Object.assign(c.b.creditCarry, patch); pending(c); } const c = carryCase(); lot(c).eligible = '5000'; lot(c).selected = '1400'; const before = structuredClone(c.b); assert.match(pending(c).pending.join(' '), /ultrapassam a colecta/); assert.deepEqual(c.b, before); }); void test('no credit or PEC reimbursement is generated by old balances; PPC refund stays separate', () => { const c = carryCase(); Object.assign(c.b.creditCarry, { currentInternational: '8', currentPec: '0', }); lot(c).eligible = '2000'; lot(c).selected = '1300'; lot(c, 1).selected = '100'; const m = calc(c); assert.equal(number(m.recoverable), 500); assert.equal(number(m.pecDeduction.deducted), 0); assert.equal(m.creditCarry.carryCreated, false); assert.equal(number(m.creditCarry.lots[0].remaining), 570); }); void test('exact foreign-credit fractions are preserved; decimal presentation is never fiscal rounding', () => { const c = carryCase(); Object.assign(lot(c), { eligible: '2/3', originDeducted: '1/3', selected: '1/3', usesState: 'absent', uses: [], }); Object.assign(c.b.creditCarry, { currentInternational: '1/3' }); const m = calc(c); assert.deepEqual(m.pending, []); assert.deepEqual(m.creditCarry.lots[0].available, { numerator: '1', denominator: '3', }); assert.deepEqual(m.payable, { numerator: '1822', denominator: '3' }); lot(c).selected = '1/0'; pending(c); }); void test('missing/absent/known-zero remain distinct; absence does not demand unused allocation fields', () => { const c = internationalCase(), baseline = calc(c); c.b = prepareCreditCarry(c.b); pending(c); c.b.creditCarry.state = 'absent'; c.b.creditCarry.reference = 'SYNTHETIC QA documented absence'; let m = calc(c); assert.equal(number(m.payable), number(baseline.payable)); assert.deepEqual(m.internationalCredit, baseline.internationalCredit); c.b.creditCarry.lots.push({ ...carryCase().b.creditCarry.lots[0] }); pending(c); const z = carryCase(); Object.assign(lot(z), { eligible: '0', originDeducted: '0', selected: '0', usesState: 'absent', uses: [], }); m = calc(z); assert.deepEqual(m.pending, []); assert.equal(number(m.creditCarry.lots[0].available), 0); }); void test('optional preparation, recording, JSON recovery, HTML and dossier preserve accepted books and frozen history', () => { const c = internationalCase(); c.d.modelo_a_snapshot = saveAnnualBook( c.d, recordAnnual(c.d, c.b, '2026-10-27T17:00:00+02:00'), ); const old = annualBook(c.d), frozen = structuredClone(old.result), existing = JSON.parse(c.d.modelo_a_snapshot), plan = c.d.business_plan_snapshot; const next = prepareCreditCarry(old); next.creditCarry = carryCase().b.creditCarry; c.d.modelo_a_snapshot = saveAnnualBook( c.d, recordAnnual(c.d, next, '2026-10-28T12:00:00+02:00'), ); const recovered = annualBook(JSON.parse(JSON.stringify(c.d))); assert.deepEqual(recovered.history, [frozen]); assert.equal(number(recovered.result.model.payable), 550); assert.equal(annualCurrent(c.d, recovered), true); assert.equal(c.d.business_plan_snapshot, plan); const after = JSON.parse(c.d.modelo_a_snapshot); delete after.irpcAnnual; delete existing.irpcAnnual; assert.deepEqual(after, existing); assert.match( annualHtml(c.d), /Saldo anterior — Crédito internacional ZA — 2021/, ); assert.match(annualHtml(c.d), /550.00/); assert.match(annualHtml(c.d), /2026/); assert.match( JSON.stringify( createProjectDossierReport(c.d, { selectedSections: new Set(['finance']), }), ), /Afectação escolhida entre exercícios/, ); recovered.creditCarry.lots[0].selected = '49'; c.d.modelo_a_snapshot = saveAnnualBook(c.d, recovered); assert.equal(annualCurrent(c.d, recovered), false); assert.deepEqual(recovered.result.model, annualBook(c.d).result.model); assert.throws(() => annualHtml(c.d), /fontes actuais/); assert.match(annualReportRows(c.d).flat().join(' '), /Histórico/); }); void test('malformed detail and duplicate internal ids cannot overwrite original snapshots', () => { for (const mutate of [ (c) => (c.b.creditCarry.lots[0].uses = null), (c) => (c.b.creditCarry.version = 2), (c) => (c.b.creditCarry.lots[0].uses[0].amount = null), (c) => (c.b.creditCarry.lots[1].id = c.b.creditCarry.lots[0].id), ]) { const c = carryCase(), before = c.d.modelo_a_snapshot; mutate(c); assert.throws(() => saveAnnualBook(c.d, c.b), /recuper|repetido/); assert.equal(c.d.modelo_a_snapshot, before); } }); void test('standalone expired PEC keeps origin and is not silently converted to international five-year window', () => { const c = carryCase(), r = { ...lot(c, 1), year: '2022', selected: '0' }; const m = calculateCarryLot(r, c.b.subjectNuit, c.b.year); assert.equal(m.expired, true); assert.equal(m.lastYear, 2025); assert.equal(number(m.remaining), 150); });