304 lines
10 KiB
JavaScript
304 lines
10 KiB
JavaScript
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
newIspcEligibilityInput,
|
|
recordIspcEligibility,
|
|
saveIspcEligibilityBook,
|
|
} from '../../resources/js/lib/ispc-eligibility.ts';
|
|
import {
|
|
newIspcQuarterInput,
|
|
ispcQuarterBook,
|
|
recordIspcQuarter,
|
|
saveIspcQuarterBook,
|
|
ispcQuarterCurrent,
|
|
ispcQuarterReportRows,
|
|
} from '../../resources/js/lib/ispc-quarter.ts';
|
|
const seed = JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/iva-l3e-preserved.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
function fixture(ep = {}, qp = {}) {
|
|
let d = {
|
|
...structuredClone(seed),
|
|
context_opportunity: 'opportunity:375',
|
|
project_title: 'SYNTHETIC QA — L4 ISPC',
|
|
fiscal_component: 'ispc',
|
|
currency: 'MZN',
|
|
person_type: 'collective',
|
|
fiscal_activity: 'SYNTHETIC QA canalização',
|
|
fiscal_establishments: '1',
|
|
};
|
|
const input = {
|
|
...newIspcEligibilityInput(d),
|
|
activities: ['services12'],
|
|
territory: 'yes',
|
|
basis: 'previous_year',
|
|
referenceYear: '2025',
|
|
annualReference: '1000000',
|
|
annualReferenceRef: 'SYNTHETIC QA acesso2025',
|
|
organizedRequired: 'no',
|
|
sharesInIspc: 'no',
|
|
sharesAnonymous: 'no',
|
|
otherActivities: 'no',
|
|
generalRegimeChosen: 'no',
|
|
unlistedServices: 'no',
|
|
...ep,
|
|
};
|
|
const e = recordIspcEligibility(
|
|
d,
|
|
{ version: 1, input },
|
|
'2026-10-25T12:00:00Z',
|
|
);
|
|
d.modelo_a_snapshot = saveIspcEligibilityBook(d, e);
|
|
const b = {
|
|
version: 1,
|
|
input: {
|
|
...newIspcQuarterInput(),
|
|
sales: ['100000', '', '', ''],
|
|
salesRefs: ['SYNTHETIC QA T1', '', '', ''],
|
|
coverage: 'all_taxpayer_activity',
|
|
...qp,
|
|
},
|
|
};
|
|
return { d, b };
|
|
}
|
|
const calc = (ep = {}, qp = {}) => {
|
|
const { d, b } = fixture(ep, qp);
|
|
return recordIspcQuarter(d, b, '2026-10-25T12:01:00Z').result;
|
|
};
|
|
const decimal = (x) => Number(x.numerator) / Number(x.denominator);
|
|
void test('quarterly services12 andliberal15;highest mixed rate applied to entire quarterly base;no costs or VAT deduction', () => {
|
|
assert.equal(decimal(calc().tax), 12000);
|
|
assert.equal(
|
|
decimal(calc({ activities: ['commerce', 'services12'] }).tax),
|
|
12000,
|
|
);
|
|
assert.equal(
|
|
decimal(
|
|
calc({
|
|
activities: ['services12', 'liberal15'],
|
|
liberalDaysSameEntity: '100',
|
|
liberalClientRef: 'QA',
|
|
}).tax,
|
|
),
|
|
15000,
|
|
);
|
|
assert.equal(calc().projected, true);
|
|
});
|
|
void test('annual goods tiers applied to quarter without splitting annual limits or using previous-year accessvolume', () => {
|
|
for (const [annual, rate] of [
|
|
['1000000', 3],
|
|
['1000000.000001', 4],
|
|
['2500000', 4],
|
|
['2500000.000001', 5],
|
|
['4000000', 5],
|
|
]) {
|
|
const r = calc(
|
|
{ activities: ['commerce'] },
|
|
{
|
|
annualBandVolume: annual,
|
|
annualBandOrigin: 'forecast',
|
|
annualBandRef: 'QAannual2026',
|
|
},
|
|
);
|
|
assert.equal(r.status, 'calculated', r.reason);
|
|
assert.equal(r.rate, rate);
|
|
assert.equal(decimal(r.tax), 1000 * rate);
|
|
}
|
|
assert.equal(calc({ activities: ['commerce'] }).status, 'indeterminate');
|
|
});
|
|
void test('20percent only newly crossed excess;prior quarters prevent repeating same excess;no threshold divided byfour', () => {
|
|
const q = {
|
|
quarter: '3',
|
|
sales: ['2000000', '1900000', '200000', ''],
|
|
salesRefs: ['QA1', 'QA2', 'QA3', ''],
|
|
};
|
|
const r = calc({}, q);
|
|
assert.equal(decimal(r.ordinary), 100000);
|
|
assert.equal(decimal(r.excess), 100000);
|
|
assert.equal(decimal(r.tax), 32000);
|
|
const next = calc(
|
|
{},
|
|
{
|
|
...q,
|
|
quarter: '4',
|
|
sales: ['2000000', '1900000', '200000', '100000'],
|
|
salesRefs: ['QA1', 'QA2', 'QA3', 'QA4'],
|
|
},
|
|
);
|
|
assert.equal(decimal(next.ordinary), 0);
|
|
assert.equal(decimal(next.tax), 20000);
|
|
});
|
|
void test('minimum500:below no payment,declaration stays;exact500 payable;explicitzero distinct from missing', () => {
|
|
let r = calc(
|
|
{
|
|
activities: ['liberal15'],
|
|
liberalDaysSameEntity: '100',
|
|
liberalClientRef: 'QA',
|
|
},
|
|
{ sales: ['3000', '', '', ''] },
|
|
);
|
|
assert.equal(decimal(r.tax), 450);
|
|
assert.equal(decimal(r.payment), 0);
|
|
assert.equal(r.declaration, true);
|
|
r = calc(
|
|
{ activities: ['commerce'] },
|
|
{
|
|
sales: ['16666.666667', '', '', ''],
|
|
annualBandVolume: '1000000',
|
|
annualBandOrigin: 'forecast',
|
|
annualBandRef: 'QA',
|
|
},
|
|
);
|
|
assert.ok(decimal(r.payment) > 500);
|
|
r = calc({}, { sales: ['0', '', '', ''] });
|
|
assert.equal(r.status, 'calculated');
|
|
assert.equal(decimal(r.tax), 0);
|
|
assert.equal(calc({}, { sales: ['', '', '', ''] }).status, 'indeterminate');
|
|
const exact = calc(
|
|
{ activities: ['commerce'] },
|
|
{
|
|
sales: ['12500', '', '', ''],
|
|
annualBandVolume: '2000000',
|
|
annualBandOrigin: 'forecast',
|
|
annualBandRef: 'QA',
|
|
},
|
|
);
|
|
assert.equal(decimal(exact.payment), 500);
|
|
});
|
|
void test('effective tax needs recorded option;notopted and incompatible are notapplicable;preparation remains scenario only', () => {
|
|
assert.equal(calc({ origin: 'effective' }).status, 'indeterminate');
|
|
assert.equal(calc({ option: 'not_opted' }).status, 'not_applicable');
|
|
assert.equal(calc({ organizedRequired: 'yes' }).status, 'not_applicable');
|
|
const r = calc({
|
|
origin: 'effective',
|
|
option: 'opted',
|
|
optionDate: '2026-01-01',
|
|
optionReference: 'QA opção',
|
|
});
|
|
assert.equal(r.status, 'calculated');
|
|
assert.equal(r.projected, false);
|
|
assert.equal(
|
|
calc({
|
|
origin: 'effective',
|
|
assessmentDate: '2026-10-25',
|
|
option: 'opted',
|
|
optionDate: '2026-10-01',
|
|
optionReference: 'QA opção',
|
|
}).status,
|
|
'indeterminate',
|
|
);
|
|
});
|
|
void test('missing prior quarter,scope,year,source andannual mismatch do not produce zero', () => {
|
|
for (const p of [
|
|
{ year: '2025' },
|
|
{ quarter: '5' },
|
|
{ coverage: '' },
|
|
{ salesRefs: ['', '', '', ''] },
|
|
{ quarter: '2', sales: ['', '1000', '', ''] },
|
|
])
|
|
assert.equal(calc({}, p).status, 'indeterminate');
|
|
assert.equal(
|
|
calc(
|
|
{ activities: ['commerce'] },
|
|
{
|
|
annualBandVolume: '99999',
|
|
annualBandOrigin: 'forecast',
|
|
annualBandRef: 'QA',
|
|
},
|
|
).status,
|
|
'indeterminate',
|
|
);
|
|
});
|
|
void test('closed-yearannual mustequalallfourquarters;quarter amount is not annual base', () => {
|
|
const q = {
|
|
quarter: '4',
|
|
sales: ['100000', '200000', '300000', '400000'],
|
|
salesRefs: ['QA1', 'QA2', 'QA3', 'QA4'],
|
|
annualBandVolume: '1000000',
|
|
annualBandOrigin: 'closed_year',
|
|
annualBandRef: 'QA2026',
|
|
};
|
|
assert.equal(decimal(calc({ activities: ['commerce'] }, q).tax), 12000);
|
|
assert.equal(
|
|
calc(
|
|
{ activities: ['commerce'] },
|
|
{ ...q, annualBandVolume: '1000001' },
|
|
).status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
decimal(calc({ activities: ['commerce'] }, { ...q, quarter: '1' }).tax),
|
|
3000,
|
|
);
|
|
});
|
|
void test('eligibility edits invalidate dependent calculation;save recovery freezesinput andretainsotherbooks/history', () => {
|
|
let { d, b } = fixture();
|
|
const old = JSON.parse(d.modelo_a_snapshot);
|
|
b = recordIspcQuarter(d, b);
|
|
d.modelo_a_snapshot = saveIspcQuarterBook(d, b);
|
|
assert.deepEqual(JSON.parse(d.modelo_a_snapshot).books, old.books);
|
|
assert.equal(ispcQuarterCurrent(d, ispcQuarterBook(d)), true);
|
|
const recovered = ispcQuarterBook(JSON.parse(JSON.stringify(d)));
|
|
recovered.input.sales[0] = '200000';
|
|
assert.equal(ispcQuarterCurrent(d, recovered), false);
|
|
assert.equal(decimal(recovered.result.tax), 12000);
|
|
const next = recordIspcQuarter(d, recovered);
|
|
assert.equal(decimal(next.result.tax), 24000);
|
|
assert.equal(next.history.length, 1);
|
|
assert.equal(decimal(next.history[0].tax), 12000);
|
|
const s = JSON.parse(d.modelo_a_snapshot);
|
|
s.ispcEligibility['opportunity:375'].input.activityDescription = 'edited';
|
|
d.modelo_a_snapshot = JSON.stringify(s);
|
|
assert.equal(recordIspcQuarter(d, next).result.status, 'indeterminate');
|
|
});
|
|
void test('invalidstoredmaps andambiguouscontext remainpreservederrors', () => {
|
|
let { d, b } = fixture();
|
|
const s = JSON.parse(d.modelo_a_snapshot);
|
|
s.ispcQuarters = [];
|
|
assert.throws(() =>
|
|
ispcQuarterBook({ ...d, modelo_a_snapshot: JSON.stringify(s) }),
|
|
);
|
|
assert.throws(() =>
|
|
saveIspcQuarterBook(d, { ...b, input: { ...b.input, sales: ['1'] } }),
|
|
);
|
|
});
|
|
void test('export completeeligibilityoptioncalculationfixture andreport from recovereddata', () => {
|
|
let { d, b } = fixture(
|
|
{
|
|
origin: 'effective',
|
|
assessmentDate: '2026-10-25',
|
|
option: 'opted',
|
|
optionDate: '2026-01-01',
|
|
optionReference: 'SYNTHETIC QA opção ISPC2026',
|
|
},
|
|
{
|
|
quarter: '2',
|
|
sales: ['100000', '200000', '', ''],
|
|
salesRefs: ['SYNTHETIC QA T1', 'SYNTHETIC QA T2', '', ''],
|
|
},
|
|
);
|
|
b = recordIspcQuarter(d, b, '2026-10-25T12:01:00Z');
|
|
d.modelo_a_snapshot = saveIspcQuarterBook(d, b);
|
|
const recovered = JSON.parse(JSON.stringify(d));
|
|
assert.equal(decimal(ispcQuarterBook(recovered).result.tax), 24000);
|
|
assert.equal(ispcQuarterBook(recovered).result.projected, false);
|
|
assert.match(JSON.stringify(ispcQuarterReportRows(recovered)), /24000.00/);
|
|
const report = createProjectDossierReport(recovered, {
|
|
selectedSections: new Set(['finance']),
|
|
version: 'QA ISPC',
|
|
generatedAt: '2026-10-25T12:02:00Z',
|
|
});
|
|
assert.match(JSON.stringify(report.sections), /24000.00/);
|
|
assert.match(JSON.stringify(report.sections), /opção ISPC2026/);
|
|
if (process.env.L4_ISPC_OUTPUT)
|
|
fs.writeFileSync(
|
|
process.env.L4_ISPC_OUTPUT,
|
|
JSON.stringify(recovered, null, 2),
|
|
);
|
|
});
|