Files
Investidor-V2/tests/frontend/iva-beverage-allocation.test.mjs
T

449 lines
16 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { fixture } from './fixtures/iva-beverage-fixture.mjs';
import {
recordBeverageRestitution,
saveBeverageRestitutionBook,
beverageRestitutionBook,
beverageRestitutionSources,
beverageRestitutionCurrent,
beverageRestitutionReportRows,
} from '../../resources/js/lib/iva-beverage-restitution.ts';
import { beverageDeductionEvidence } from '../../resources/js/lib/iva-beverage-allocation.ts';
import {
acquisitionBook,
recordAcquisitions,
saveAcquisitionBook,
} from '../../resources/js/lib/iva-acquisitions.ts';
import { newAcquisitionAllocation } from '../../resources/js/lib/iva-acquisition-allocation.ts';
import { fiscalCalculationGaps } from '../../resources/js/lib/iva-calculation-gaps.ts';
import {
l3FormPreparations,
l3FormPreparationHtml,
l3FormalitiesDependencies,
} from '../../resources/js/lib/l3-form-preparation.ts';
const n = (v) => v && `${v.numerator}/${v.denominator}`;
const allocation = (from, to, patch = {}) => ({
from,
to,
reference: 'SYNTHETIC QA repartição documento',
deductionFrom: '',
deductionTo: '',
deductionReference: '',
...patch,
});
function pending(row, id, from, to) {
return {
...structuredClone(row),
id,
decision: 'pending',
decisionReference: '',
decisionDate: '',
decisionIssuer: '',
approvedAmount: '',
receipts: [],
allocation: allocation(from, to),
};
}
function partialPrior(numerator = '3', denominator = '8', expected = '6/1') {
const f = fixture(),
a = acquisitionBook(f.d);
a.rows[0].origin = 'effective';
a.rows[0].allocation = 'mixed';
a.rows[0].bc.beveragePath = 'deduction';
a.rows[0].bc.facts.onlyDeduction = 'yes';
a.rows[0].d = {
...newAcquisitionAllocation('2026-12'),
method: 'actual',
numerator,
denominator,
unit: 'unidades físicas documentadas',
scopeRef: 'SYNTHETIC QA parte com direito',
source: 'SYNTHETIC QA critério e registos',
communicationRef: 'SYNTHETIC QA comunicação22(2)',
noExcludedPayments: 'yes',
facts: {
scopeConfirmed: 'yes',
rightBasis: 'yes',
actualScope: 'yes',
communication: 'yes',
conditionsMet: 'yes',
},
};
let actual = recordAcquisitions(f.d, a);
assert.equal(
n(actual.result.rows[0].deductibleExact),
expected,
actual.result.rows[0].reason,
);
f.d.modelo_a_snapshot = saveAcquisitionBook(f.d, actual);
actual.rows[0].bc.beveragePath = 'refund';
actual.rows[0].allocation = 'taxable_only';
delete actual.rows[0].d;
actual = recordAcquisitions(f.d, actual);
f.d.modelo_a_snapshot = saveAcquisitionBook(f.d, actual);
f.b.rows[0].source = beverageRestitutionSources(f.d)[0];
return f;
}
void test('disjoint portions6+10 share one documentary VAT16 without overlap;save/recovery/report preserve prior maps', () => {
const { d, b } = fixture(),
before = JSON.parse(d.modelo_a_snapshot);
b.rows = [
pending(b.rows[0], 'portion6', '0', '6'),
pending(b.rows[0], 'portion10', '6', '16'),
];
const book = recordBeverageRestitution(d, b);
assert.deepEqual(
book.result.rows.map((r) => [r.status, n(r.requested)]),
[
['determined', '6/1'],
['determined', '10/1'],
],
);
d.modelo_a_snapshot = saveBeverageRestitutionBook(d, book);
assert.deepEqual(beverageRestitutionBook(d), book);
for (const [k, v] of Object.entries(before))
assert.deepEqual(JSON.parse(d.modelo_a_snapshot)[k], v, k);
assert.ok(beverageRestitutionCurrent(d, book));
assert.match(
beverageRestitutionReportRows(d).flat().join(' '),
/IVA documental:16.00;parcelaIVA:6.00/,
);
});
void test('overlap,negative/zero/outside bounds,missing support and duplicate legacy whole line are rejected', () => {
const { d, b } = fixture();
for (const pair of [
['0', '0'],
['-1', '6'],
['6', '5'],
['0', '16.000001'],
]) {
const book = { ...b, rows: [pending(b.rows[0], 'bad', ...pair)] };
assert.equal(
recordBeverageRestitution(d, book).result.rows[0].requested,
null,
);
}
let book = {
...b,
rows: [
pending(b.rows[0], 'p1', '0', '10'),
pending(b.rows[0], 'p2', '9', '16'),
],
};
assert.ok(
recordBeverageRestitution(d, book).result.rows.every(
(r) => !r.requested,
),
);
book.rows = [
pending(b.rows[0], 'p1', '0', '6'),
structuredClone(b.rows[0]),
];
assert.ok(
recordBeverageRestitution(d, book).result.rows.every(
(r) => !r.requested,
),
);
book.rows = [pending(b.rows[0], 'p1', '0', '6')];
book.rows[0].allocation.reference = '';
assert.equal(
recordBeverageRestitution(d, book).result.rows[0].requested,
null,
);
});
void test('effective deduction6 must be explicitly reconciled to its own interval;E10 uses only the disjoint remainder', () => {
const { d, b } = partialPrior();
assert.equal(
n(beverageDeductionEvidence(d, b.rows[0].source).amount),
'6/1',
);
b.rows = [pending(b.rows[0], 'remainder', '6', '16')];
assert.equal(
recordBeverageRestitution(d, b).result.rows[0].requested,
null,
);
Object.assign(b.rows[0].allocation, {
deductionFrom: '0',
deductionTo: '6',
deductionReference: 'SYNTHETIC QA correspondência da dedução original',
});
let book = recordBeverageRestitution(d, b);
assert.equal(n(book.result.rows[0].requested), '10/1');
assert.equal(book.result.rows[0].status, 'determined');
assert.ok(
acquisitionBook(d).history.some((c) =>
c.rows.some((r) => n(r.deductibleExact) === '6/1'),
),
);
for (const patch of [
{ deductionTo: '5' },
{ deductionFrom: '4', deductionTo: '10' },
{ deductionReference: '' },
]) {
const changed = structuredClone(b);
Object.assign(changed.rows[0].allocation, patch);
assert.equal(
recordBeverageRestitution(d, changed).result.rows[0].requested,
null,
);
}
});
void test('a rounded tiny deduction,divergent historical amounts and editing away a committed portion never releaseVAT', () => {
const { d, b } = partialPrior(),
a = acquisitionBook(d),
old = structuredClone(
a.history.find((c) =>
c.rows.some((r) => n(r.deductibleExact) === '6/1'),
),
);
old.rows.find((r) => r.id === 'beverage-purchase').deductibleExact = {
numerator: '1',
denominator: '100000000',
};
old.rows.find((r) => r.id === 'beverage-purchase').deductible = '0.00';
a.history.push(old);
d.modelo_a_snapshot = saveAcquisitionBook(d, a);
b.rows = [pending(b.rows[0], 'x', '0', '16')];
assert.equal(
recordBeverageRestitution(d, b).result.rows[0].requested,
null,
);
assert.match(
beverageDeductionEvidence(d, b.rows[0].source).reason,
/divergentes/,
);
const f = fixture();
f.b.rows = [pending(f.b.rows[0], 'committed', '0', '6')];
let book = recordBeverageRestitution(f.d, f.b);
f.d.modelo_a_snapshot = saveBeverageRestitutionBook(f.d, book);
book = recordBeverageRestitution(f.d, { ...book, rows: [] });
f.d.modelo_a_snapshot = saveBeverageRestitutionBook(f.d, book);
const overlap = pending(f.b.rows[0], 'new', '5', '16');
assert.equal(
recordBeverageRestitution(f.d, { ...book, rows: [overlap] }).result
.rows[0].requested,
null,
);
const edited = pending(f.b.rows[0], 'committed', '0', '7');
assert.equal(
recordBeverageRestitution(f.d, { ...book, rows: [edited] }).result
.rows[0].requested,
null,
);
});
void test('gap inventory identifies supplyVAT independently from purchase16 and includes implementation limits of margin/travel/events', () => {
const { d } = fixture(),
gaps = fiscalCalculationGaps(d);
const supply = gaps.find(
(g) =>
g.book === 'salesOperations' && g.reason.includes('Bebidas em13'),
);
assert.ok(supply);
assert.match(supply.resolution, /IVA da aquisição separado/);
const store = JSON.parse(d.modelo_a_snapshot);
store.regulatedSecondhands = {
...store.regulatedSecondhands,
[d.context_opportunity]: {
rows: [{ id: 'm', period: '2026-12' }],
result: {
inputs: [{ id: 'm', period: '2026-12' }],
rows: [
{
id: 'm',
status: 'determined',
baseAmount: '100',
vat: null,
reason: 'Base da margem calculada;IVA não calculado.',
},
],
},
},
};
d.modelo_a_snapshot = JSON.stringify(store);
const margin = fiscalCalculationGaps(d).find(
(g) => g.book === 'regulatedSecondhands' && g.kind === 'implementation',
);
assert.ok(margin);
assert.match(margin.resolution, /liquidação/);
assert.ok(margin.required.length);
assert.ok(
fiscalCalculationGaps({ ...d, context_opportunity: 'other-context' })
.length === 0,
);
});
void test('formal preparation maps only identified L3 fields;E keeps16/0/12/7/5 outside27;HTML escapes documents and lists specific dependencies', () => {
const { d, b } = fixture();
b.rows[0].entityRef = '<script>alert(1)</script>';
d.modelo_a_snapshot = saveBeverageRestitutionBook(
d,
recordBeverageRestitution(d, b),
);
const forms = l3FormPreparations(d),
e = forms.find((p) => p.branch === 'E');
assert.deepEqual(
e.fields.map((f) => f.value),
['16.00', '16.00', '0.00', '12.00', '7.00', '5.00'],
);
assert.ok(e.fields.every((f) => f.field === 'Dossier'));
const html = l3FormPreparationHtml(e);
assert.ok(!html.includes('<script>'));
assert.ok(html.includes('&lt;script&gt;'));
assert.match(html, /modelo oficial específico ainda não identificado/);
assert.match(html, /Condição de retoma/);
assert.equal(l3FormalitiesDependencies.length, 4);
assert.ok(
forms
.filter((p) => p.branch === 'A/B')
.every((p) =>
p.fields.every((f) => !['05', '06'].includes(f.field)),
),
);
});
void test('export reconciled6deducted/10requested plus gap inventory and preparations for isolated persistence and realUI', async () => {
const { d, b } = partialPrior();
b.rows = [pending(b.rows[0], 'remainder', '6', '16')];
Object.assign(b.rows[0].allocation, {
deductionFrom: '0',
deductionTo: '6',
deductionReference: 'SYNTHETIC QA correspondência da dedução original',
});
let book = recordBeverageRestitution(d, b);
book = recordBeverageRestitution(d, book);
d.modelo_a_snapshot = saveBeverageRestitutionBook(d, book);
assert.equal(n(book.result.rows[0].requested), '10/1');
const fs = await import('node:fs');
if (process.env.IVA_E_PARTITION_OUTPUT) {
fs.writeFileSync(process.env.IVA_E_PARTITION_OUTPUT, JSON.stringify(d));
fs.writeFileSync(
process.env.IVA_E_PARTITION_OUTPUT.replace(
'calculated.json',
'gaps.json',
),
JSON.stringify(fiscalCalculationGaps(d), null, 2),
);
fs.writeFileSync(
process.env.IVA_E_PARTITION_OUTPUT.replace(
'calculated.json',
'preparations.json',
),
JSON.stringify(l3FormPreparations(d), null, 2),
);
}
});
void test('a failed recalculation does not commit its rejected boundaries;correcting support or decimal presentation keeps the accepted reservation', () => {
const { d, b } = fixture();
b.rows = [pending(b.rows[0], 'stable', '0', '6')];
let book = recordBeverageRestitution(d, b);
d.modelo_a_snapshot = saveBeverageRestitutionBook(d, book);
const bad = structuredClone(book);
bad.rows[0].allocation.to = '17';
book = recordBeverageRestitution(d, bad);
assert.equal(book.result.rows[0].requested, null);
d.modelo_a_snapshot = saveBeverageRestitutionBook(d, book);
book.rows[0].allocation.to = '6.0';
book.rows[0].allocation.reference = 'Updated documented support';
book = recordBeverageRestitution(d, book);
assert.equal(n(book.result.rows[0].requested), '6/1');
assert.equal(book.result.rows[0].status, 'determined');
});
void test('malformed applicable books are visible errors rather than an empty list;frozen gap labels do not adopt deliberate new edits', () => {
const { d } = fixture(),
store = JSON.parse(d.modelo_a_snapshot);
store.salesOperations[d.context_opportunity].rows[0].description =
'Deliberate edited description';
d.modelo_a_snapshot = JSON.stringify(store);
const gaps = fiscalCalculationGaps(d);
assert.ok(
gaps.some((g) => g.book === 'salesOperations' && g.kind === 'stale'),
);
assert.ok(
gaps
.filter((g) => g.book === 'salesOperations' && g.kind !== 'stale')
.every(
(g) => !g.calculation.includes('Deliberate edited description'),
),
);
store.books[d.context_opportunity] = 'malformed';
assert.throws(
() =>
fiscalCalculationGaps({
...d,
modelo_a_snapshot: JSON.stringify(store),
}),
/não recuperável/,
);
});
void test('identified AB C D fields use their actual frozen books;general request600000 never becomes approval550000 or receipt450000', () => {
const { d } = fixture();
const forms = l3FormPreparations({
...d,
context_opportunity: 'opportunity:373',
}),
march = forms.find((p) => p.branch === 'A/B' && p.period === '2026-03'),
sub = forms.find((p) => p.branch === 'C');
assert.ok(march);
assert.equal(march.fields.find((f) => f.field === '24').value, '72.00');
assert.ok(sub);
assert.equal(sub.fields.find((f) => f.field === '24').value, '61.00');
const refund = l3FormPreparations({
...d,
context_opportunity: 'opportunity:374',
}).find((p) => p.branch === 'D');
assert.ok(refund);
assert.deepEqual(
refund.fields.map((f) => [f.field, f.value]),
[
['25', '800000.00'],
['26', '200000.00'],
['27', '600000.00'],
],
);
assert.match(
refund.notes.join(' '),
/reconhecido550000.00;recebido450000.00;por receber100000.00/,
);
assert.ok(march.fields.every((f) => !['05', '06'].includes(f.field)));
assert.match(l3FormPreparationHtml(sub), /ModeloB/);
const snapshot = JSON.parse(d.modelo_a_snapshot);
snapshot.ivaClosures['opportunity:373'].rows.find(
(r) => r.period === '2026-03',
).entityRef = 'Deliberate edited entity';
const retained = l3FormPreparations({
...d,
context_opportunity: 'opportunity:373',
modelo_a_snapshot: JSON.stringify(snapshot),
}).find((p) => p.branch === 'A/B' && p.period === '2026-03');
assert.equal(retained.current, false);
assert.notEqual(retained.entity, 'Deliberate edited entity');
assert.equal(retained.fields.find((f) => f.field === '24').value, '72.00');
});
void test('non-terminating effective deduction16/3 is reconciled exactly and leaves32/3 forE without decimal approximation or excess', () => {
const { d, b } = partialPrior('1', '3', '16/3');
b.rows = [pending(b.rows[0], 'exact-remainder', '16/3', '16')];
Object.assign(b.rows[0].allocation, {
deductionFrom: '0',
deductionTo: '16/3',
deductionReference: 'SYNTHETIC QA exact allocation',
});
let book = recordBeverageRestitution(d, b);
assert.equal(
book.result.rows[0].status,
'determined',
book.result.rows[0].reason,
);
assert.equal(n(book.result.rows[0].requested), '32/3');
const changed = structuredClone(b);
changed.rows[0].allocation.from = '5.333333';
assert.equal(
recordBeverageRestitution(d, changed).result.rows[0].requested,
null,
);
changed.rows[0].allocation.from = '1/0';
assert.equal(
recordBeverageRestitution(d, changed).result.rows[0].requested,
null,
);
});