412 lines
15 KiB
JavaScript
412 lines
15 KiB
JavaScript
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import fs from 'node:fs';
|
||
import {
|
||
newRegularization,
|
||
evaluateRegularization,
|
||
recordRegularizations,
|
||
saveRegularizationBook,
|
||
regularizationBook,
|
||
regularizationFields,
|
||
regularizationsCurrent,
|
||
frozenAssetRows,
|
||
} from '../../resources/js/lib/iva-regularizations.ts';
|
||
import {
|
||
acquisitionBook,
|
||
recordAcquisitions,
|
||
saveAcquisitionBook,
|
||
acquisitionCurrent,
|
||
acquisitionReportRows,
|
||
readAcquisitionStore,
|
||
} from '../../resources/js/lib/iva-acquisitions.ts';
|
||
import {
|
||
recordSales,
|
||
saveSalesBook,
|
||
} from '../../resources/js/lib/iva-operations.ts';
|
||
const frozen = JSON.parse(
|
||
fs.readFileSync(
|
||
new URL('./fixtures/iva-l2e-preserved.json', import.meta.url),
|
||
'utf8',
|
||
),
|
||
);
|
||
export function regularizationFixture() {
|
||
const draft = structuredClone(frozen);
|
||
draft.context_opportunity = 'opportunity:370';
|
||
draft.project_title = 'SYNTHETIC QA — L2-E activos e regularizações';
|
||
const source = structuredClone(acquisitionBook(draft).result);
|
||
const annual = {
|
||
...newRegularization('annual-2026', 'annual', source),
|
||
numerator: '2',
|
||
denominator: '3',
|
||
reference: 'SYNTHETIC QA contas anuais2026',
|
||
reason: 'SYNTHETIC QA âmbito anual da entidade',
|
||
facts: {
|
||
scope: 'yes',
|
||
recorded: 'yes',
|
||
annual: 'yes',
|
||
exclusions: 'yes',
|
||
},
|
||
};
|
||
const correction = {
|
||
...newRegularization('credit-note', 'purchase_correction', source),
|
||
rowIds: ['actual'],
|
||
receivedDate: '2026-12-15',
|
||
correctedDeductible: '4',
|
||
reference: 'SYNTHETIC QA nota de créditoNC1',
|
||
reason: 'SYNTHETIC QA redução da aquisição',
|
||
facts: {
|
||
scope: 'yes',
|
||
recorded: 'yes',
|
||
supplierCorrection: 'yes',
|
||
rightUnchanged: 'yes',
|
||
},
|
||
};
|
||
return {
|
||
draft,
|
||
annual,
|
||
correction,
|
||
book: {
|
||
version: 1,
|
||
events: [annual, correction],
|
||
completePeriods: ['2026-12'],
|
||
},
|
||
};
|
||
}
|
||
void test('annual adjustment uses frozen C-limited input and actually deducted D,ceil final ratio and both signs', () => {
|
||
const { draft, annual } = regularizationFixture();
|
||
let r = evaluateRegularization(draft, annual);
|
||
assert.equal(r.status, 'determined');
|
||
assert.deepEqual(r.rawRatio, { numerator: '2', denominator: '3' });
|
||
assert.deepEqual(r.ratio, { numerator: '67', denominator: '100' });
|
||
assert.deepEqual(r.delta, { numerator: '132', denominator: '25' });
|
||
assert.match(r.reason, /campo12/);
|
||
r = evaluateRegularization(draft, {
|
||
...annual,
|
||
numerator: '1',
|
||
denominator: '2',
|
||
});
|
||
assert.deepEqual(r.delta, { numerator: '-4', denominator: '25' });
|
||
assert.match(r.reason, /campo13/);
|
||
for (const patch of [
|
||
{ period: '2026-11' },
|
||
{ denominator: '0' },
|
||
{ numerator: '4' },
|
||
{ reference: '' },
|
||
{ rowIds: ['actual'] },
|
||
{ rowIds: ['missing'] },
|
||
{ facts: { ...annual.facts, annual: '' } },
|
||
{ facts: { ...annual.facts, recorded: '' } },
|
||
])
|
||
assert.equal(
|
||
evaluateRegularization(draft, { ...annual, ...patch }).delta,
|
||
null,
|
||
JSON.stringify(patch),
|
||
);
|
||
const altered = structuredClone(draft);
|
||
const book = acquisitionBook(altered);
|
||
book.rows[1].d.numerator = '3';
|
||
altered.modelo_a_snapshot = saveAcquisitionBook(altered, book);
|
||
assert.deepEqual(
|
||
evaluateRegularization(altered, annual).delta,
|
||
{ numerator: '132', denominator: '25' },
|
||
'current edits never recalculate historical deduction',
|
||
);
|
||
const fabricated = structuredClone(annual);
|
||
fabricated.source.rows[1].deductibleExact.numerator = '0';
|
||
assert.equal(evaluateRegularization(draft, fabricated).delta, null);
|
||
});
|
||
void test('credit note keeps exact historical fraction,receipt deadline and source conditions;unsupported increase stays unknown', () => {
|
||
const { draft, correction } = regularizationFixture();
|
||
const r = evaluateRegularization(draft, correction);
|
||
assert.deepEqual(r.delta, { numerator: '-4', denominator: '3' });
|
||
assert.match(r.reason, /campo13/);
|
||
for (const patch of [
|
||
{ period: '2026-11' },
|
||
{ receivedDate: '2026-02-30' },
|
||
{ receivedDate: '2026-10-10' },
|
||
{ correctedDeductible: '6' },
|
||
{ correctedDeductible: '' },
|
||
{ facts: { ...correction.facts, supplierCorrection: '' } },
|
||
{ rowIds: ['actual', 'diesel'] },
|
||
])
|
||
assert.equal(
|
||
evaluateRegularization(draft, { ...correction, ...patch }).delta,
|
||
null,
|
||
JSON.stringify(patch),
|
||
);
|
||
assert.deepEqual(
|
||
evaluateRegularization(draft, {
|
||
...correction,
|
||
correctedDeductible: '0',
|
||
}).delta,
|
||
{ numerator: '-16', denominator: '3' },
|
||
);
|
||
});
|
||
void test('F uses exact 12/13 once,requires coverage and detects stale E without overwriting D history or other opportunities', () => {
|
||
let { draft, book } = regularizationFixture();
|
||
const before = JSON.parse(draft.modelo_a_snapshot);
|
||
book = recordRegularizations(draft, book);
|
||
draft.modelo_a_snapshot = saveRegularizationBook(draft, book);
|
||
const acquired = acquisitionBook(draft);
|
||
assert.equal(acquisitionCurrent(draft, acquired), false);
|
||
assert.throws(() => recordAcquisitions(draft, acquired), /ausência12\/13/);
|
||
acquired.periods[0].absent = acquired.periods[0].absent.filter(
|
||
(f) => !['12', '13'].includes(f),
|
||
);
|
||
const adjusted = recordAcquisitions(draft, acquired);
|
||
draft.modelo_a_snapshot = saveAcquisitionBook(draft, adjusted);
|
||
const p = adjusted.result.periods[0];
|
||
assert.deepEqual(
|
||
['12', '13', '15', '16', '17', '18'].map((f) => p.fields[f]),
|
||
['5.28', '1.333333', '29.44', '161.333333', '131.893333', '0.00'],
|
||
);
|
||
assert.deepEqual(p.exactFields['13'], { numerator: '4', denominator: '3' });
|
||
assert.ok(acquisitionCurrent(draft, adjusted));
|
||
assert.ok(regularizationsCurrent(draft, book));
|
||
assert.deepEqual(
|
||
adjusted.history.at(-1),
|
||
before.books['opportunity:370'].result,
|
||
);
|
||
const after = JSON.parse(draft.modelo_a_snapshot);
|
||
for (const [k, map] of Object.entries(before)) {
|
||
if (k === 'version') continue;
|
||
for (const [id, v] of Object.entries(map)) {
|
||
if (k === 'books' && id === 'opportunity:370') continue;
|
||
assert.deepEqual(after[k][id], v, k + id);
|
||
}
|
||
}
|
||
for (const id of ['opportunity:368', 'opportunity:369', 'opportunity:371'])
|
||
assert.ok(
|
||
acquisitionCurrent(
|
||
{ ...draft, context_opportunity: id },
|
||
after.books[id],
|
||
),
|
||
);
|
||
const incomplete = { ...book, completePeriods: [] };
|
||
assert.equal(
|
||
regularizationFields(
|
||
{
|
||
...draft,
|
||
modelo_a_snapshot: saveRegularizationBook(draft, incomplete),
|
||
},
|
||
'2026-12',
|
||
).fields['12'],
|
||
null,
|
||
);
|
||
const divergent = structuredClone(adjusted);
|
||
divergent.rows[1].d.numerator = '2';
|
||
assert.equal(
|
||
recordAcquisitions(draft, divergent).result.periods[0].fields['15'],
|
||
null,
|
||
'changed original deduction must be reconciled before F',
|
||
);
|
||
const changed = structuredClone(book);
|
||
changed.events[0].numerator = '1';
|
||
draft.modelo_a_snapshot = saveRegularizationBook(draft, changed);
|
||
assert.equal(acquisitionCurrent(draft, adjusted), false);
|
||
assert.equal(regularizationFields(draft, '2026-12').fields['13'], null);
|
||
assert.deepEqual(changed.result, book.result);
|
||
});
|
||
void test('F retains reduced-output uncertainty even when only E events remain in the current period book', () => {
|
||
let { draft, book } = regularizationFixture();
|
||
book = recordRegularizations(draft, book);
|
||
draft.modelo_a_snapshot = saveRegularizationBook(draft, book);
|
||
const acquisitions = {
|
||
...acquisitionBook(draft),
|
||
rows: [],
|
||
periods: [
|
||
{
|
||
period: '2026-12',
|
||
complete: true,
|
||
importsComplete: true,
|
||
absent: [],
|
||
},
|
||
],
|
||
};
|
||
const first = recordAcquisitions(draft, acquisitions);
|
||
draft.modelo_a_snapshot = saveAcquisitionBook(draft, first);
|
||
assert.equal(first.result.periods[0].fields['15'], '5.28');
|
||
const s = JSON.parse(draft.modelo_a_snapshot);
|
||
s.regulatedReducedBases['opportunity:370'] = structuredClone(
|
||
s.regulatedReducedBases['opportunity:371'],
|
||
);
|
||
draft.modelo_a_snapshot = JSON.stringify(s);
|
||
assert.equal(acquisitionCurrent(draft, first), false);
|
||
const result = recordAcquisitions(draft, first).result;
|
||
assert.equal(result.periods[0].fields['15'], '5.28');
|
||
for (const f of ['14', '16', '17', '18'])
|
||
assert.equal(result.periods[0].fields[f], null, f);
|
||
});
|
||
void test('annual adjustment composes C exclusion before D exactly once,including a projected investment asset', () => {
|
||
let { draft } = regularizationFixture();
|
||
let b = acquisitionBook(draft);
|
||
const diesel = structuredClone(b.rows[2]);
|
||
diesel.id = 'diesel-pro-rata';
|
||
diesel.field = '08';
|
||
diesel.documentRef = 'SYNTHETIC QA activo gasóleo';
|
||
diesel.d.method = 'pro_rata';
|
||
b = recordAcquisitions(draft, {
|
||
...b,
|
||
rows: [diesel],
|
||
result: undefined,
|
||
history: undefined,
|
||
});
|
||
draft.modelo_a_snapshot = saveAcquisitionBook(draft, b);
|
||
const e = {
|
||
...newRegularization('annual-diesel', 'annual', b.result),
|
||
numerator: '2',
|
||
denominator: '3',
|
||
reference: 'SYNTHETIC QA anual',
|
||
reason: 'SYNTHETIC QA âmbito',
|
||
facts: {
|
||
scope: 'yes',
|
||
recorded: 'yes',
|
||
annual: 'yes',
|
||
exclusions: 'yes',
|
||
},
|
||
};
|
||
assert.deepEqual(b.result.rows[0].allocationCalculation.before, {
|
||
numerator: '8',
|
||
denominator: '1',
|
||
});
|
||
assert.deepEqual(evaluateRegularization(draft, e).delta, {
|
||
numerator: '66',
|
||
denominator: '25',
|
||
});
|
||
});
|
||
void test('deduplication includes original document/line across revisions and rejects unsequenced annual plus credit note', () => {
|
||
const { draft, book, annual, correction } = regularizationFixture();
|
||
assert.throws(
|
||
() =>
|
||
recordRegularizations(draft, {
|
||
...book,
|
||
events: [annual, { ...annual, id: 'duplicate' }],
|
||
}),
|
||
/Mesma aquisição/,
|
||
);
|
||
assert.throws(
|
||
() =>
|
||
recordRegularizations(draft, {
|
||
...book,
|
||
events: [correction, { ...correction, id: 'other' }],
|
||
}),
|
||
/Mesma aquisição/,
|
||
);
|
||
assert.throws(
|
||
() =>
|
||
recordRegularizations(draft, {
|
||
...book,
|
||
events: [annual, { ...correction, rowIds: ['pro-rata'] }],
|
||
}),
|
||
/sequencial/,
|
||
);
|
||
});
|
||
void test('investment imports retain customs field11 while entering the frozen asset control register', () => {
|
||
const { draft } = regularizationFixture();
|
||
let b = acquisitionBook(draft);
|
||
b.rows[3].investmentAsset = true;
|
||
b = recordAcquisitions(draft, b);
|
||
draft.modelo_a_snapshot = saveAcquisitionBook(draft, b);
|
||
assert.equal(b.result.periods[0].fields['11'], '10.72');
|
||
const register = JSON.stringify(frozenAssetRows(draft));
|
||
assert.match(register, /import-due/);
|
||
assert.match(register, /268\/25/);
|
||
assert.equal(b.rows[3].field, '11');
|
||
});
|
||
void test('unrecoverable E source or fraction is visible and a missing calculated event never turns into zero', () => {
|
||
const { draft, book } = regularizationFixture();
|
||
const saved = recordRegularizations(draft, book);
|
||
const incomplete = structuredClone(saved);
|
||
incomplete.result.events = [];
|
||
const altered = {
|
||
...draft,
|
||
modelo_a_snapshot: saveRegularizationBook(draft, incomplete),
|
||
};
|
||
assert.equal(regularizationFields(altered, '2026-12').fields['12'], null);
|
||
for (const mutate of [
|
||
(b) => (b.events[0].source = {}),
|
||
(b) => (b.result.events[0].delta.denominator = '0'),
|
||
(b) => (b.events[0].facts.scope = 'maybe'),
|
||
]) {
|
||
const bad = structuredClone(saved);
|
||
mutate(bad);
|
||
assert.throws(
|
||
() => readAcquisitionStore(saveRegularizationBook(draft, bad)),
|
||
/Regulariza|regulariza/,
|
||
);
|
||
}
|
||
});
|
||
void test('asset register reuses frozen acquisition values and E save/recovery/report preserves source,history and prior maps', () => {
|
||
let { draft, book } = regularizationFixture();
|
||
book = recordRegularizations(draft, book);
|
||
const first = structuredClone(book.result);
|
||
book = recordRegularizations(draft, book);
|
||
assert.deepEqual(book.history, [first]);
|
||
draft.modelo_a_snapshot = saveRegularizationBook(draft, book);
|
||
let acquired = acquisitionBook(draft);
|
||
acquired.periods[0].absent = acquired.periods[0].absent.filter(
|
||
(f) => !['12', '13'].includes(f),
|
||
);
|
||
acquired = recordAcquisitions(draft, acquired);
|
||
draft.modelo_a_snapshot = saveAcquisitionBook(draft, acquired);
|
||
const assetDraft = { ...draft, context_opportunity: 'opportunity:372' };
|
||
const s = JSON.parse(draft.modelo_a_snapshot);
|
||
assetDraft.modelo_a_snapshot = saveSalesBook(
|
||
assetDraft,
|
||
recordSales(
|
||
assetDraft,
|
||
structuredClone(s.salesOperations['opportunity:370']),
|
||
),
|
||
);
|
||
const asset = structuredClone(acquired.rows[0]);
|
||
asset.id = 'asset-control';
|
||
asset.field = '08';
|
||
asset.description = 'SYNTHETIC QA activo de investimento';
|
||
asset.documentRef = 'SYNTHETIC QA activo1';
|
||
const assetBook = recordAcquisitions(assetDraft, {
|
||
currency: 'MZN',
|
||
taxpayerRegistered: 'yes',
|
||
rows: [asset],
|
||
periods: [
|
||
{
|
||
period: '2026-12',
|
||
complete: true,
|
||
importsComplete: true,
|
||
absent: ['12', '13'],
|
||
},
|
||
],
|
||
});
|
||
assetDraft.modelo_a_snapshot = saveAcquisitionBook(assetDraft, assetBook);
|
||
const report = JSON.stringify(frozenAssetRows(assetDraft));
|
||
for (const token of [
|
||
'activo1',
|
||
'2026-12-10',
|
||
'16/3',
|
||
'RCIVA27',
|
||
'IVA suportado16',
|
||
])
|
||
assert.ok(report.includes(token), token);
|
||
const recovered = JSON.parse(
|
||
JSON.stringify({
|
||
...assetDraft,
|
||
context_opportunity: 'opportunity:370',
|
||
}),
|
||
);
|
||
assert.deepEqual(regularizationBook(recovered), book);
|
||
const text = JSON.stringify(acquisitionReportRows(recovered));
|
||
for (const token of [
|
||
'Regularização annual-2026',
|
||
'5.28',
|
||
'4/3',
|
||
'131.893333',
|
||
'Pressuposto',
|
||
'22(4–8)',
|
||
])
|
||
assert.ok(text.includes(token), token);
|
||
if (process.env.IVA_L2E_CALCULATED)
|
||
fs.writeFileSync(
|
||
process.env.IVA_L2E_CALCULATED,
|
||
JSON.stringify(recovered),
|
||
);
|
||
});
|