206 lines
7.8 KiB
JavaScript
206 lines
7.8 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
temporaryBenefits,
|
|
temporaryBenefitNotice,
|
|
temporaryBenefitVersion,
|
|
} from '../../resources/js/lib/iva-temporary-benefits.ts';
|
|
import {
|
|
continueTemporaryOperation,
|
|
evaluateSalesOperation,
|
|
newSalesOperation,
|
|
operationTreatments,
|
|
recordSales,
|
|
salesBook,
|
|
salesCurrent,
|
|
saveSalesBook,
|
|
} 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-annex-i-inputs.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
function operation(benefit, index = 0) {
|
|
return {
|
|
...newSalesOperation(`g3-${index}`, '2026-10', 'MZN'),
|
|
treatment: benefit.id,
|
|
description: `SYNTHETIC QA — ${benefit.branch} / ${benefit.label}`,
|
|
establishment: 'SYNTHETIC QA estabelecimento',
|
|
amount: index === 0 ? '116' : '100',
|
|
basis: index === 0 ? 'included' : 'net',
|
|
evidence:
|
|
'SYNTHETIC QA — natureza e contraprestação pressupostas para demonstração; não certificação',
|
|
};
|
|
}
|
|
export function fixture() {
|
|
const draft = {
|
|
...structuredClone(frozen),
|
|
project_title: 'SYNTHETIC QA — G3 / Cessação de benefícios temporários',
|
|
context_opportunity: 'opportunity:322',
|
|
};
|
|
const pending = recordSales(draft, {
|
|
currency: 'MZN',
|
|
rows: temporaryBenefits.map(operation),
|
|
periods: [{ period: '2026-10', complete: true }],
|
|
});
|
|
const rows = pending.rows.map((row) => {
|
|
const next = continueTemporaryOperation(row, 'general');
|
|
next.confirmed = operationTreatments
|
|
.find((rule) => rule.id === next.treatment)
|
|
.conditions.map((_, i) => i);
|
|
return next;
|
|
});
|
|
const agricultural = continueTemporaryOperation(
|
|
{ ...operation(temporaryBenefits[5], 8), id: 'g3-cane-independent' },
|
|
'production_agriculture',
|
|
);
|
|
agricultural.confirmed = operationTreatments
|
|
.find((rule) => rule.id === agricultural.treatment)
|
|
.conditions.map((_, i) => i);
|
|
rows.push(agricultural);
|
|
const book = recordSales(draft, { ...pending, rows });
|
|
draft.modelo_a_snapshot = saveSalesBook(draft, book);
|
|
return { draft, book, pending };
|
|
}
|
|
|
|
void test('G3 has seven stable branches; expired benefits never become zero-rated or taxable automatically in2026', () => {
|
|
assert.deepEqual(
|
|
temporaryBenefits.map((item) => item.branch),
|
|
['P038', 'P039', 'P040', 'P041', 'P042', 'P043', 'P045'],
|
|
);
|
|
for (const benefit of temporaryBenefits) {
|
|
const row = operation(benefit),
|
|
result = evaluateSalesOperation(row, 'MZN');
|
|
assert.equal(result.status, 'indeterminate');
|
|
for (const field of ['field', 'rate', 'net', 'vat', 'gross'])
|
|
assert.equal(result[field], null, field);
|
|
assert.match(result.reason, /terminou em31\/12\/2025/);
|
|
assert.match(result.source, /Lei10\/2025/);
|
|
for (const period of ['2026-01', '2026-12'])
|
|
assert.match(
|
|
temporaryBenefitNotice(benefit.id, period),
|
|
/não é aplicável/,
|
|
);
|
|
for (const period of ['2025-12', '2027-01', ''])
|
|
assert.match(
|
|
evaluateSalesOperation({ ...row, period }, 'MZN').reason,
|
|
/fora da cobertura/,
|
|
);
|
|
assert.match(
|
|
evaluateSalesOperation({ ...row, amount: '', evidence: '' }, 'MZN')
|
|
.reason,
|
|
/ficam por determinar/,
|
|
);
|
|
}
|
|
assert.match(
|
|
temporaryBenefits[1].historicalScope,
|
|
/efectuadas pela indústria/,
|
|
);
|
|
assert.match(temporaryBenefits[1].historicalScope, /9\(14\)/);
|
|
assert.match(temporaryBenefits[5].historicalScope, /9\(14\)/);
|
|
assert.throws(() => temporaryBenefitNotice('unknown', '2026-10'));
|
|
});
|
|
|
|
void test('explicit continuation keeps amounts/origin/identity and requires new conditions; cane has an independent agricultural route', () => {
|
|
for (const [i, benefit] of temporaryBenefits.entries()) {
|
|
const row = operation(benefit, i),
|
|
next = continueTemporaryOperation(row, 'general');
|
|
for (const field of [
|
|
'id',
|
|
'period',
|
|
'description',
|
|
'establishment',
|
|
'amount',
|
|
'basis',
|
|
'evidence',
|
|
'sourceAmount',
|
|
'sourcePeriod',
|
|
'sourceCurrency',
|
|
])
|
|
assert.deepEqual(next[field], row[field], field);
|
|
assert.equal(next.temporaryBenefitOrigin, benefit.id);
|
|
assert.deepEqual(next.confirmed, []);
|
|
assert.ok(next.source.startsWith(row.source));
|
|
assert.ok(next.source.includes(benefit.branch));
|
|
assert.ok(next.source.includes(temporaryBenefitVersion));
|
|
assert.equal(evaluateSalesOperation(next, 'MZN').vat, null);
|
|
const confirmed = { ...next, confirmed: [0, 1] };
|
|
assert.equal(evaluateSalesOperation(confirmed, 'MZN').vat, '16.00');
|
|
assert.equal(
|
|
evaluateSalesOperation({ ...confirmed, amount: '' }, 'MZN').vat,
|
|
null,
|
|
);
|
|
assert.equal(
|
|
evaluateSalesOperation({ ...confirmed, evidence: '' }, 'MZN').vat,
|
|
null,
|
|
);
|
|
assert.equal(
|
|
evaluateSalesOperation(
|
|
{ ...confirmed, sourceCurrency: 'USD' },
|
|
'MZN',
|
|
).vat,
|
|
null,
|
|
);
|
|
assert.throws(() => continueTemporaryOperation(row, 'missing'));
|
|
assert.throws(() => continueTemporaryOperation(row, benefit.id));
|
|
}
|
|
const { book } = fixture();
|
|
assert.equal(book.result.rows.at(-1).field, '05');
|
|
assert.equal(book.result.rows.at(-1).vat, '0.00');
|
|
assert.equal(book.result.rows[0].net, '100.00');
|
|
assert.equal(book.result.rows[0].vat, '16.00');
|
|
assert.equal(book.rows[0].amount, '116');
|
|
assert.equal(book.rows[0].basis, 'included');
|
|
});
|
|
|
|
void test('actual cessation→choice→calculation→stored snapshot→report preserves all13 delivered books and history', () => {
|
|
const { draft, book, pending } = fixture();
|
|
for (const value of Object.values(pending.result.periods[0].fields))
|
|
assert.equal(value, null);
|
|
assert.deepEqual(book.history.at(-1), pending.result);
|
|
assert.equal(book.result.periods[0].fields['01'], '700.00');
|
|
assert.equal(book.result.periods[0].fields['02'], '112.00');
|
|
assert.equal(book.result.periods[0].fields['05'], '100.00');
|
|
assert.equal(salesCurrent(draft, book), true);
|
|
assert.deepEqual(salesBook(JSON.parse(JSON.stringify(draft))), book);
|
|
const before = JSON.parse(frozen.modelo_a_snapshot).salesOperations,
|
|
after = JSON.parse(draft.modelo_a_snapshot).salesOperations;
|
|
assert.equal(Object.keys(before).length, 13);
|
|
for (const [context, original] of Object.entries(before)) {
|
|
assert.deepEqual(after[context], original, context);
|
|
assert.equal(
|
|
salesCurrent(
|
|
{ ...draft, context_opportunity: context },
|
|
after[context],
|
|
),
|
|
true,
|
|
context,
|
|
);
|
|
}
|
|
const model = createProjectDossierReport(draft, {
|
|
selectedSections: new Set(['finance']),
|
|
version: 'QA G3 — Cessação2026',
|
|
});
|
|
const encoded = JSON.stringify(model);
|
|
for (const token of [
|
|
'opportunity:322',
|
|
'Manica',
|
|
'2026-10',
|
|
'700.00',
|
|
'112.00',
|
|
'100.00',
|
|
'31/12/2025',
|
|
temporaryBenefitVersion,
|
|
'P045',
|
|
'Lei3/2025',
|
|
'Lei22/2022',
|
|
])
|
|
assert.ok(encoded.includes(token), token);
|
|
if (process.env.IVA_G3_CALCULATED)
|
|
fs.writeFileSync(process.env.IVA_G3_CALCULATED, JSON.stringify(draft));
|
|
});
|