379 lines
14 KiB
JavaScript
379 lines
14 KiB
JavaScript
import {
|
|
normalBook,
|
|
recordNormal,
|
|
saveNormalBook,
|
|
} from '../../resources/js/lib/irpc-normal.ts';
|
|
import { carryCase } from './helpers/irpc-carry-case.mjs';
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { investmentCase } from './helpers/irpc-investment-case.mjs';
|
|
import { internationalCase } from './helpers/irpc-international-case.mjs';
|
|
import {
|
|
reuseAnnualAssessment,
|
|
prepareInvestmentCredit,
|
|
calculateAnnual,
|
|
recordAnnual,
|
|
saveAnnualBook,
|
|
annualBook,
|
|
annualCurrent,
|
|
annualHtml,
|
|
} from '../../resources/js/lib/irpc-annual-settlement.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
void test('CBF15 city5percent and other provinces10percent deduct once after international and before PEC/PPC', () => {
|
|
const c = investmentCase(),
|
|
before = c.d.modelo_a_snapshot,
|
|
m = calculateAnnual(c.d, c.b);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.deepEqual(m.investmentCredit.nominal, {
|
|
numerator: '500',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(m.investmentCredit.afterCredit, {
|
|
numerator: '900',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(m.arithmeticBalance, {
|
|
numerator: '200',
|
|
denominator: '1',
|
|
});
|
|
assert.equal(c.d.modelo_a_snapshot, before);
|
|
c.b.investmentCredit.location = 'other';
|
|
const other = calculateAnnual(c.d, c.b);
|
|
assert.deepEqual(other.investmentCredit.deducted, {
|
|
numerator: '1000',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(other.arithmeticBalance, {
|
|
numerator: '-300',
|
|
denominator: '1',
|
|
});
|
|
assert.equal(other.investmentCredit.officialBalancesConsumed, false);
|
|
});
|
|
void test('CBF15 caps at remaining collection and separates unused credit from unrecovered PEC and PPC', () => {
|
|
const c = investmentCase();
|
|
c.b.investmentCredit.location = 'other';
|
|
Object.assign(c.b.investmentCredit.assets[0], {
|
|
amount: '40000',
|
|
proofAmount: '40000',
|
|
});
|
|
const m = calculateAnnual(c.d, c.b);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.deepEqual(m.investmentCredit.deducted, {
|
|
numerator: '1400',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(m.investmentCredit.unused, {
|
|
numerator: '2600',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(m.pecDeduction.deducted, {
|
|
numerator: '0',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(m.recoverable, { numerator: '500', denominator: '1' });
|
|
assert.equal(m.investmentCredit.carryCreated, false);
|
|
assert.equal(m.refundReceived, false);
|
|
});
|
|
void test('CBF15 requires actual project,source and first-exercise conditions without treating projected investment as realised', () => {
|
|
for (const patch of [
|
|
{ location: '' },
|
|
{ locationReference: '' },
|
|
{ cbfRequirements: '' },
|
|
{ recognitionReference: '' },
|
|
{ genericOnly: '' },
|
|
{ soleProject: '' },
|
|
{ noOtherBenefits: 'no' },
|
|
{ assessmentKey: 'stale' },
|
|
{ startDate: '2027-01-01' },
|
|
]) {
|
|
const c = investmentCase();
|
|
Object.assign(c.b.investmentCredit, patch);
|
|
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
|
}
|
|
const c = investmentCase();
|
|
c.b.investmentCredit.assets[0].realised = '';
|
|
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
|
});
|
|
void test('documented excluded,used,foreign or unrealised assets keep their inputs but receive zero credit', () => {
|
|
for (const patch of [
|
|
{ excluded15_6: 'yes' },
|
|
{ newTangible: 'no' },
|
|
{ domesticUse: 'no' },
|
|
{ realised: 'no' },
|
|
]) {
|
|
const c = investmentCase();
|
|
Object.assign(c.b.investmentCredit.assets[0], patch);
|
|
const m = calculateAnnual(c.d, c.b);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.deepEqual(m.investmentCredit.nominal, {
|
|
numerator: '0',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(m.arithmeticBalance, {
|
|
numerator: '700',
|
|
denominator: '1',
|
|
});
|
|
assert.equal(m.investmentCredit.rows[0].input.amount, '10000');
|
|
}
|
|
});
|
|
void test('source identity cannot be duplicated by a new id/date; supported parcels share one invoice capacity', () => {
|
|
const c = investmentCase(),
|
|
r = c.b.investmentCredit.assets[0];
|
|
c.b.investmentCredit.assets.push({ ...r, id: 'copy', date: '2026-05-02' });
|
|
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
|
const p = investmentCase(),
|
|
a = p.b.investmentCredit.assets[0];
|
|
a.amount = '6000';
|
|
p.b.investmentCredit.assets.push({ ...a, id: 'line2', parcel: 'machine2' });
|
|
assert.equal(calculateAnnual(p.d, p.b).arithmeticBalance, null);
|
|
a.amount = '4000';
|
|
assert.deepEqual(calculateAnnual(p.d, p.b).pending, []);
|
|
});
|
|
void test('CBF15 detects another map using the same project or actual investment,including split invoice capacity', () => {
|
|
const c = investmentCase(),
|
|
other = structuredClone(c.b);
|
|
other.investmentCredit.projectReference = 'OTHER-SYNTHETIC-PROJECT';
|
|
other.investmentCredit.assets[0].parcel = 'OTHER-LINE';
|
|
other.investmentCredit.assets[0].amount = '6000';
|
|
c.b.investmentCredit.assets[0].amount = '6000';
|
|
const root = JSON.parse(c.d.modelo_a_snapshot);
|
|
root.irpcAnnual = { ...root.irpcAnnual, other };
|
|
const d = { ...c.d, modelo_a_snapshot: JSON.stringify(root) };
|
|
assert.ok(
|
|
calculateAnnual(d, c.b).pending.some((e) =>
|
|
e.includes('ultrapassa o documento efectivo'),
|
|
),
|
|
);
|
|
});
|
|
void test('actual previous credit use is distinct from calculating or saving the same scenario again', () => {
|
|
const c = investmentCase();
|
|
c.b.investmentCredit.previousUse.amount = '100';
|
|
const m = calculateAnnual(c.d, c.b);
|
|
assert.deepEqual(m.investmentCredit.deducted, {
|
|
numerator: '400',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(
|
|
calculateAnnual(c.d, c.b).investmentCredit,
|
|
m.investmentCredit,
|
|
);
|
|
assert.deepEqual(m.arithmeticBalance, {
|
|
numerator: '300',
|
|
denominator: '1',
|
|
});
|
|
});
|
|
void test('CBF15 calculation-save-recovery-HTML and dossier reports retain sources,history and legacy benefits summary', () => {
|
|
const c = investmentCase();
|
|
c.b.internationalCredits.benefits = {
|
|
state: 'known',
|
|
amount: '777',
|
|
reference:
|
|
'SYNTHETIC earlier deliberate summary retained,not reapplied',
|
|
};
|
|
const b = recordAnnual(c.d, c.b, '2026-11-10T19:00:00+02:00'),
|
|
d = JSON.parse(
|
|
JSON.stringify({
|
|
...c.d,
|
|
modelo_a_snapshot: saveAnnualBook(c.d, b),
|
|
}),
|
|
),
|
|
back = annualBook(d);
|
|
assert.equal(annualCurrent(d, back), true);
|
|
assert.deepEqual(
|
|
calculateAnnual(d, back).investmentCredit,
|
|
back.result.model.investmentCredit,
|
|
);
|
|
assert.equal(back.internationalCredits.benefits.amount, '777');
|
|
assert.deepEqual(back.result.model.arithmeticBalance, {
|
|
numerator: '200',
|
|
denominator: '1',
|
|
});
|
|
assert.match(annualHtml(d), /IRPC-CBF15\/3/);
|
|
assert.match(
|
|
JSON.stringify(
|
|
createProjectDossierReport(d, {
|
|
selectedSections: new Set(['finance']),
|
|
generatedAt: '2026-11-10T19:00:00+02:00',
|
|
}),
|
|
),
|
|
/IRPC-CBF15\/3/,
|
|
);
|
|
const old = internationalCase();
|
|
assert.equal(calculateAnnual(old.d, old.b).investmentCredit, undefined);
|
|
});
|
|
|
|
void test('absence of CBF15 cannot silently discard other positive benefits or unknown benefit inventory', () => {
|
|
const c = investmentCase();
|
|
c.b.investmentCredit.qualification.state = 'absent';
|
|
c.b.investmentCredit.qualification.eligible = '';
|
|
c.b.internationalCredits.benefits = {
|
|
state: 'known',
|
|
amount: '777',
|
|
reference: 'SYNTHETIC old unspecified positive benefit',
|
|
};
|
|
c.b.investmentCredit.noOtherBenefits = '';
|
|
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
|
c.b.investmentCredit.noOtherBenefits = 'yes';
|
|
assert.deepEqual(calculateAnnual(c.d, c.b).arithmeticBalance, {
|
|
numerator: '700',
|
|
denominator: '1',
|
|
});
|
|
assert.equal(c.b.internationalCredits.benefits.amount, '777');
|
|
});
|
|
|
|
void test('CBF15 uses a versioned L8 book while legacy books remain version1 and cannot silently carry the new feature', () => {
|
|
const c = investmentCase();
|
|
assert.equal(c.b.version, 2);
|
|
const old = internationalCase();
|
|
assert.equal(old.b.version, 1);
|
|
const invalid = structuredClone(c.b);
|
|
invalid.version = 1;
|
|
assert.throws(() => calculateAnnual(c.d, invalid));
|
|
});
|
|
|
|
void test('adding CBF15 retains the accepted legacy settlement result in history', () => {
|
|
const c = investmentCase(),
|
|
legacy = recordAnnual(
|
|
c.d,
|
|
internationalCase().b,
|
|
'2026-11-10T18:00:00+02:00',
|
|
),
|
|
before = structuredClone(legacy.result);
|
|
const book = {
|
|
...legacy,
|
|
version: 2,
|
|
investmentCredit: structuredClone(c.b.investmentCredit),
|
|
};
|
|
const next = recordAnnual(c.d, book, '2026-11-10T19:00:00+02:00'),
|
|
d = { ...c.d, modelo_a_snapshot: saveAnnualBook(c.d, next) };
|
|
assert.deepEqual(annualBook(d).history.at(-1), before);
|
|
assert.deepEqual(annualBook(d).result.model.arithmeticBalance, {
|
|
numerator: '200',
|
|
denominator: '1',
|
|
});
|
|
});
|
|
void test('first-year ongoing project uses documented investment-procedure start,not new-project exploitation start', () => {
|
|
const c = investmentCase();
|
|
Object.assign(c.b.investmentCredit, {
|
|
startKind: 'existing',
|
|
startDate: '2026-04-01',
|
|
startReference:
|
|
'SYNTHETIC procedures after project approval startedApril2026',
|
|
});
|
|
assert.deepEqual(calculateAnnual(c.d, c.b).pending, []);
|
|
c.b.investmentCredit.startDate = '2026-06-01';
|
|
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
|
});
|
|
|
|
void test('declared absence of CBF15 preserves the accepted positive prior-credit carry journey', () => {
|
|
const c = carryCase(),
|
|
before = calculateAnnual(c.d, c.b),
|
|
b = prepareInvestmentCredit(c.d, c.b);
|
|
Object.assign(b.investmentCredit, {
|
|
qualification: {
|
|
state: 'absent',
|
|
eligible: '',
|
|
from: '2026-01-01',
|
|
to: '2026-12-31',
|
|
reference: 'SYNTHETIC no CBF15 benefit',
|
|
periodReference: 'SYNTHETIC current fiscal period',
|
|
},
|
|
noOtherBenefits: 'yes',
|
|
noOtherBenefitsReference: 'SYNTHETIC no other own fiscal benefits',
|
|
});
|
|
const after = calculateAnnual(c.d, b);
|
|
assert.deepEqual(after.pending, []);
|
|
assert.deepEqual(after.arithmeticBalance, before.arithmeticBalance);
|
|
assert.deepEqual(after.creditCarry, before.creditCarry);
|
|
});
|
|
|
|
void test('documented residual project quota limits CBF15 without allocating income/costs by presumption', () => {
|
|
const c = investmentCase();
|
|
Object.assign(c.b.investmentCredit, {
|
|
soleProject: 'no',
|
|
projectQuota: {
|
|
amount: '200',
|
|
reference:
|
|
'SYNTHETIC project residual collecta200 after allocated economic/international credits',
|
|
},
|
|
});
|
|
const m = calculateAnnual(c.d, c.b);
|
|
assert.deepEqual(m.pending, []);
|
|
assert.deepEqual(m.investmentCredit.deducted, {
|
|
numerator: '200',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(m.arithmeticBalance, {
|
|
numerator: '500',
|
|
denominator: '1',
|
|
});
|
|
assert.deepEqual(m.investmentCredit.projectCeiling, {
|
|
numerator: '200',
|
|
denominator: '1',
|
|
});
|
|
assert.match(m.investmentCredit.source, /QuotaCBF15-project/);
|
|
c.b.investmentCredit.projectQuota.amount = '0';
|
|
assert.deepEqual(calculateAnnual(c.d, c.b).arithmeticBalance, {
|
|
numerator: '700',
|
|
denominator: '1',
|
|
});
|
|
});
|
|
void test('project quota requires support and cannot exceed actual collection remaining after credits', () => {
|
|
for (const quota of [
|
|
{ amount: '200', reference: '' },
|
|
{
|
|
amount: '1405',
|
|
reference: 'SYNTHETIC invalid residual quota above1400',
|
|
},
|
|
{ amount: '-1', reference: 'SYNTHETIC invalid' },
|
|
]) {
|
|
const c = investmentCase();
|
|
Object.assign(c.b.investmentCredit, {
|
|
soleProject: 'no',
|
|
projectQuota: quota,
|
|
});
|
|
assert.equal(calculateAnnual(c.d, c.b).arithmeticBalance, null);
|
|
}
|
|
});
|
|
void test('zero project residual quota preserves the accepted prior-credit journey without applying a competing benefit', () => {
|
|
const c = carryCase(),
|
|
before = calculateAnnual(c.d, c.b),
|
|
b = prepareInvestmentCredit(c.d, c.b),
|
|
template = investmentCase().b.investmentCredit;
|
|
b.investmentCredit = {
|
|
...template,
|
|
soleProject: 'no',
|
|
projectQuota: {
|
|
amount: '0',
|
|
reference:
|
|
'SYNTHETIC project residual collecta zero after attribution',
|
|
},
|
|
};
|
|
const after = calculateAnnual(c.d, b);
|
|
assert.deepEqual(after.pending, []);
|
|
assert.deepEqual(after.arithmeticBalance, before.arithmeticBalance);
|
|
});
|
|
|
|
void test('CBF15 does not assume compatibility with an existing positive deduction/benefit in the L6 base', () => {
|
|
const c = investmentCase(),
|
|
normal = normalBook(c.d);
|
|
normal.base.benefits = {
|
|
state: 'known',
|
|
amount: '100',
|
|
reference:
|
|
'SYNTHETIC positive base benefit requiring separate cumulation correspondence',
|
|
};
|
|
const d = {
|
|
...c.d,
|
|
modelo_a_snapshot: saveNormalBook(
|
|
c.d,
|
|
recordNormal(c.d, normal, '2026-11-10T19:10:00+02:00'),
|
|
),
|
|
},
|
|
b = reuseAnnualAssessment(d, c.b);
|
|
b.investmentCredit.assessmentKey = b.assessmentKey;
|
|
const m = calculateAnnual(d, b);
|
|
assert.equal(m.arithmeticBalance, null);
|
|
assert.ok(m.investmentCredit.pending.length > 0);
|
|
});
|