Files
Investidor-V2/tests/frontend/iva-onboard-supplies.test.mjs
T

199 lines
7.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import {
onboardSupplyTreatments,
supplyCategories,
onboardSupplyIssue,
} from '../../resources/js/lib/iva-onboard-supplies.ts';
import {
newSalesOperation,
evaluateSalesOperation,
recordSales,
saveSalesBook,
salesBook,
salesCurrent,
} from '../../resources/js/lib/iva-operations.ts';
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
const frozen = JSON.parse(
fs.readFileSync(
new URL('./fixtures/iva-g4c-inputs.json', import.meta.url),
'utf8',
),
);
const positive = ['provisions', 'fuel', 'beverages', 'cargo_products'];
const row = (rule, i) => ({
...newSalesOperation(`g4d-${i}`, '2026-12', 'MZN'),
description: `SYNTHETIC QA — ${rule.label}`,
establishment: 'SYNTHETIC QA estabelecimento',
amount: '100',
basis: i % 2 ? 'included' : 'net',
treatment: rule.id,
supplyCategory: positive[i],
confirmed: rule.conditions.map((_, n) => n),
evidence:
'SYNTHETIC QA — documento/pressuposto identifica bens postos a bordo, destino, embarcação/avião e actividade do operador; projecção, sem certificar despacho nem restituição',
});
function fixture() {
const draft = {
...structuredClone(frozen),
context_opportunity: 'opportunity:326',
project_title: 'SYNTHETIC QA — G4D / Abastecimentos a bordo',
};
const book = recordSales(draft, {
currency: 'MZN',
rows: onboardSupplyTreatments.map(row),
periods: [{ period: '2026-12', complete: true }],
});
draft.modelo_a_snapshot = saveSalesBook(draft, book);
return { draft, book };
}
void test('four supply branches require each legal condition and identification; values with/without included VAT use existing motor', () => {
assert.equal(onboardSupplyTreatments.length, 4);
for (const [i, rule] of onboardSupplyTreatments.entries()) {
const input = row(rule, i),
result = evaluateSalesOperation(input, 'MZN');
assert.equal(result.field, '05');
assert.equal(result.vat, '0.00');
assert.equal(result.net, '100.00');
assert.equal(result.gross, '100.00');
for (const missing of input.confirmed)
assert.equal(
evaluateSalesOperation(
{
...input,
confirmed: input.confirmed.filter((n) => n !== missing),
},
'MZN',
).vat,
null,
);
for (const patch of [
{ amount: '' },
{ evidence: '' },
{ sourceCurrency: 'USD' },
{ period: '2027-01' },
{ basis: '' },
{ description: '' },
{ establishment: '' },
])
assert.equal(
evaluateSalesOperation({ ...input, ...patch }, 'MZN').vat,
null,
);
}
});
void test('article13 categories and exclusions are distinct: beverages do not cause automatic exemption or deduction', () => {
for (const [i, rule] of onboardSupplyTreatments.entries())
for (const [category] of supplyCategories) {
const result = evaluateSalesOperation(
{ ...row(rule, i), supplyCategory: category },
'MZN',
);
const excluded =
i === 1 &&
(category === 'provisions' || category === 'beverages'),
refund = category === 'beverages' && (i === 0 || i === 3);
if (excluded || refund) {
assert.equal(result.vat, null);
assert.equal(result.net, null);
assert.equal(result.field, null);
assert.equal(result.rate, null);
assert.match(
result.reason,
excluded ? /excluídas/ : /dedução ou restituição/,
);
} else assert.equal(result.vat, '0.00');
}
assert.match(
onboardSupplyIssue('onboard_international_aircraft', 'beverages'),
/20\(1\)\(d\)/,
);
assert.equal(
onboardSupplyIssue('onboard_departing_warship', 'beverages'),
null,
); // 13(2) lists c/d/h, not e.
for (const category of [undefined, '', null, 'unknown'])
for (const rule of onboardSupplyTreatments) {
const result = evaluateSalesOperation(
{ ...row(rule, 0), supplyCategory: category },
'MZN',
);
assert.equal(result.vat, null);
assert.match(result.reason, /Identifique a natureza/);
}
});
void test('changing category preserves price and historical result; unresolved beverage/provisions never turn into zero totals', () => {
const { draft, book } = fixture();
assert.equal(book.result.periods[0].fields['05'], '400.00');
for (const [index, category] of [
[0, 'beverages'],
[1, 'provisions'],
[3, ''],
]) {
const changed = recordSales(draft, {
...book,
rows: book.rows.map((r, i) =>
i === index
? { ...r, supplyCategory: category, confirmed: [] }
: r,
),
});
assert.equal(changed.rows[index].amount, '100');
assert.equal(changed.rows[index].sourceCurrency, 'MZN');
assert.equal(changed.rows[index].period, '2026-12');
assert.equal(changed.result.periods[0].fields['05'], null);
assert.deepEqual(changed.history.at(-1), book.result);
const edited = {
...draft,
modelo_a_snapshot: saveSalesBook(draft, {
...book,
rows: changed.rows,
}),
};
assert.equal(salesCurrent(edited, salesBook(edited)), false);
}
});
void test('QA snapshot and report retain category/context/source; all17 prior books stay identical/current without recalculation', () => {
const { draft, book } = fixture();
assert.ok(salesCurrent(draft, book));
assert.deepEqual(
salesBook(JSON.parse(JSON.stringify(draft))),
JSON.parse(JSON.stringify(book)),
);
const before = JSON.parse(frozen.modelo_a_snapshot).salesOperations,
after = JSON.parse(draft.modelo_a_snapshot).salesOperations;
assert.equal(Object.keys(before).length, 17);
for (const [context, original] of Object.entries(before)) {
assert.deepEqual(after[context], original);
assert.ok(
salesCurrent(
{ ...draft, context_opportunity: context },
after[context],
),
);
}
const report = JSON.stringify(
createProjectDossierReport(draft, {
selectedSections: new Set(['finance']),
version: 'QA G4D',
}),
);
for (const token of [
'opportunity:326',
'Manica',
'2026-12',
'400.00',
'13(1)(c)',
'13(1)(d)',
'13(1)(e)',
'13(1)(h)',
'Natureza do abastecimento',
'Bebidas como provisões',
'QA G4D',
])
assert.ok(report.includes(token), token);
if (process.env.IVA_G4D_CALCULATED)
fs.writeFileSync(process.env.IVA_G4D_CALCULATED, JSON.stringify(draft));
});