269 lines
9.6 KiB
JavaScript
269 lines
9.6 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import fs from 'node:fs';
|
|
import {
|
|
annexIEntries,
|
|
annexIVersion,
|
|
} from '../../resources/js/lib/iva-annex-i.ts';
|
|
import {
|
|
operationTreatments,
|
|
newSalesOperation,
|
|
evaluateSalesOperation,
|
|
continueAnnexIOperation,
|
|
recordSales,
|
|
saveSalesBook,
|
|
salesBook,
|
|
salesCurrent,
|
|
} from '../../resources/js/lib/iva-operations.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
const rule = operationTreatments.find((t) => t.id === 'annexI_goods');
|
|
const seed = () =>
|
|
JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/iva-pharma-inputs.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
const operation = (entry, i = 0) => ({
|
|
...newSalesOperation(`g2l-${i}`, '2026-09', 'MZN'),
|
|
description: `SYNTHETIC QA ${entry.code} ${entry.id}`,
|
|
establishment: 'SYNTHETIC QA estabelecimento Manica',
|
|
amount: '100',
|
|
basis: i % 2 ? 'included' : 'net',
|
|
treatment: rule.id,
|
|
annexIItem: entry.id,
|
|
confirmed: rule.conditions.map((_, n) => n),
|
|
evidence:
|
|
'SYNTHETIC QA: correspondência código/designação e qualificadores da posição expressamente pressupostos; não é classificação aduaneira ou certificação',
|
|
});
|
|
function fixture() {
|
|
const draft = seed();
|
|
draft.context_opportunity = 'opportunity:321';
|
|
draft.project_title = 'SYNTHETIC QA — G2L Anexo I';
|
|
const codes = [
|
|
'0306.36.10',
|
|
'3102.10.00',
|
|
'1007.10.00',
|
|
'8501.61.00',
|
|
'8701.95.00',
|
|
'8201.50.00',
|
|
'0702.00.00',
|
|
'3808.91.00',
|
|
'1101.00.00',
|
|
'0402.21.10',
|
|
];
|
|
const rows = codes.map((code, i) => {
|
|
const entry = annexIEntries.find((e) => e.code === code);
|
|
let row = operation(entry, i);
|
|
if (entry.relatedRequired) {
|
|
row = continueAnnexIOperation(row, entry.relatedTreatments[0]);
|
|
row.confirmed = operationTreatments
|
|
.find((t) => t.id === row.treatment)
|
|
.conditions.map((_, n) => n);
|
|
} else if (entry.relatedTreatments) {
|
|
row.annexIOnly = true;
|
|
row.evidence +=
|
|
code === '1101.00.00'
|
|
? '; mistura de trigo com centeio, não farinha de trigo simples'
|
|
: '; leite para utilização industrial, sem destino a lactente até um ano';
|
|
}
|
|
return row;
|
|
});
|
|
const book = recordSales(draft, {
|
|
currency: 'MZN',
|
|
rows,
|
|
periods: [{ period: '2026-09', complete: true }],
|
|
});
|
|
draft.modelo_a_snapshot = saveSalesBook(draft, book);
|
|
return { draft, book };
|
|
}
|
|
|
|
void test('AnexoI has 172 distinct documentary positions, own qualifiers, cross-page text and five contextual tractor entries', () => {
|
|
assert.equal(annexIEntries.length, 172);
|
|
assert.equal(new Set(annexIEntries.map((e) => e.code)).size, 172);
|
|
assert.equal(new Set(annexIEntries.map((e) => e.id)).size, 172);
|
|
const scissors = annexIEntries.find((e) => e.code === '8201.50.00');
|
|
assert.equal(scissors.pdfPage, 8);
|
|
assert.equal(scissors.lastPdfPage, 9);
|
|
assert.match(scissors.label, /uma das mãos/);
|
|
assert.equal(
|
|
annexIEntries.filter((e) => e.context === 'Outros tractores').length,
|
|
5,
|
|
);
|
|
assert.equal(
|
|
annexIEntries
|
|
.find((e) => e.code === '8701.30.00')
|
|
.label.includes('Outros tractores'),
|
|
false,
|
|
);
|
|
for (const [i, entry] of annexIEntries.entries()) {
|
|
const row = { ...operation(entry, i), annexIOnly: true },
|
|
r = evaluateSalesOperation(row, 'MZN');
|
|
assert.equal(r.vat, entry.relatedRequired ? null : '0.00');
|
|
if (!entry.relatedRequired) {
|
|
assert.equal(r.field, '07');
|
|
assert.equal(r.gross, '100.00');
|
|
}
|
|
for (const token of [
|
|
entry.id,
|
|
entry.code,
|
|
entry.label,
|
|
`PDF${entry.pdfPage}`,
|
|
annexIVersion,
|
|
])
|
|
assert.ok(r.source.includes(token), token);
|
|
}
|
|
});
|
|
|
|
void test('P035 requires actual position and qualifiers; overlapping descriptions require explicit applicability, never silently field07', () => {
|
|
const { draft, book } = fixture(),
|
|
row = book.rows[0];
|
|
for (const missing of row.confirmed)
|
|
assert.equal(
|
|
evaluateSalesOperation(
|
|
{
|
|
...row,
|
|
confirmed: row.confirmed.filter((n) => n !== missing),
|
|
},
|
|
'MZN',
|
|
).vat,
|
|
null,
|
|
);
|
|
for (const patch of [
|
|
{ annexIItem: undefined },
|
|
{ annexIItem: '' },
|
|
{ annexIItem: '0306.36.10' },
|
|
{ annexIItem: 'annexIII-2022-001' },
|
|
{ evidence: '' },
|
|
{ establishment: '' },
|
|
{ amount: '' },
|
|
{ period: '2025-09' },
|
|
{ sourceCurrency: 'USD' },
|
|
])
|
|
assert.equal(
|
|
evaluateSalesOperation({ ...row, ...patch }, 'MZN').vat,
|
|
null,
|
|
);
|
|
assert.equal(evaluateSalesOperation(row, 'USD').vat, null);
|
|
const overlaps = annexIEntries.filter((e) => e.relatedTreatments);
|
|
assert.equal(overlaps.length, 13);
|
|
assert.equal(overlaps.filter((e) => e.relatedRequired).length, 3);
|
|
for (const entry of overlaps) {
|
|
const pending = operation(entry);
|
|
assert.equal(evaluateSalesOperation(pending, 'MZN').vat, null);
|
|
assert.equal(
|
|
evaluateSalesOperation({ ...pending, annexIOnly: true }, 'MZN').vat,
|
|
entry.relatedRequired ? null : '0.00',
|
|
);
|
|
}
|
|
const incomplete = recordSales(draft, {
|
|
...book,
|
|
rows: [{ ...row, annexIItem: '' }],
|
|
});
|
|
assert.equal(incomplete.result.periods[0].fields['07'], null);
|
|
assert.deepEqual(incomplete.history.at(-1), book.result);
|
|
});
|
|
|
|
void test('explicit continuation reuses existing treatment identity, preserves money/origin/annex and requires new conditions', () => {
|
|
for (const entry of annexIEntries.filter((e) => e.relatedTreatments)) {
|
|
for (const target of entry.relatedTreatments) {
|
|
const before = operation(entry),
|
|
after = continueAnnexIOperation(before, target),
|
|
targetRule = operationTreatments.find((t) => t.id === target);
|
|
assert.equal(after.treatment, target);
|
|
assert.equal(after.annexIItem, entry.id);
|
|
assert.deepEqual(after.confirmed, []);
|
|
assert.ok(after.source.startsWith(before.source));
|
|
assert.ok(after.source.includes(entry.id));
|
|
for (const key of [
|
|
'id',
|
|
'period',
|
|
'amount',
|
|
'basis',
|
|
'sourceCurrency',
|
|
'sourcePeriod',
|
|
'establishment',
|
|
'description',
|
|
'evidence',
|
|
])
|
|
assert.equal(after[key], before[key], key);
|
|
assert.equal(evaluateSalesOperation(after, 'MZN').vat, null);
|
|
const r = evaluateSalesOperation(
|
|
{ ...after, confirmed: targetRule.conditions.map((_, n) => n) },
|
|
'MZN',
|
|
);
|
|
assert.equal(r.field, '05');
|
|
assert.equal(r.vat, '0.00');
|
|
}
|
|
}
|
|
assert.throws(() =>
|
|
continueAnnexIOperation(operation(annexIEntries[0]), 'general'),
|
|
);
|
|
const e = annexIEntries.find((v) => v.code === '0702.00.00');
|
|
assert.throws(() =>
|
|
continueAnnexIOperation(
|
|
{ ...operation(e), treatment: 'listed_fresh_chilled_tomato' },
|
|
'listed_fresh_chilled_tomato',
|
|
),
|
|
);
|
|
});
|
|
|
|
void test('G2L recovery/report preserve twelve current books including G2K and calendar, without recalculation', () => {
|
|
const { draft, book } = fixture(),
|
|
original = seed(),
|
|
old = JSON.parse(original.modelo_a_snapshot).salesOperations;
|
|
const recovered = JSON.parse(JSON.stringify(draft)),
|
|
store = JSON.parse(recovered.modelo_a_snapshot).salesOperations;
|
|
assert.equal(Object.keys(old).length, 12);
|
|
for (const [id, value] of Object.entries(old)) {
|
|
assert.deepEqual(store[id], value);
|
|
assert.equal(
|
|
salesCurrent({ ...recovered, context_opportunity: id }, value),
|
|
true,
|
|
id,
|
|
);
|
|
}
|
|
assert.equal(salesCurrent(recovered, salesBook(recovered)), true);
|
|
assert.equal(book.result.periods[0].fields['07'], '800.00');
|
|
assert.equal(book.result.periods[0].fields['05'], '200.00');
|
|
const report = JSON.stringify(
|
|
createProjectDossierReport(recovered, {
|
|
selectedSections: new Set(['finance']),
|
|
}),
|
|
);
|
|
for (const token of [
|
|
'800.00',
|
|
'200.00',
|
|
'opportunity:321',
|
|
'Manica',
|
|
'2026-09',
|
|
'MZN',
|
|
'AnexoI/2022',
|
|
'Outros tractores',
|
|
annexIVersion,
|
|
...book.rows.map((r) => r.annexIItem),
|
|
])
|
|
assert.ok(report.includes(token), token);
|
|
for (const token of [
|
|
'pharma_manufacturing_inputs',
|
|
'fundraising_public',
|
|
'production_forestry_opted',
|
|
])
|
|
assert.equal(report.includes(token), false);
|
|
const changed = {
|
|
...book,
|
|
rows: book.rows.map((r, i) =>
|
|
i === 8 ? { ...r, annexIOnly: false } : r,
|
|
),
|
|
};
|
|
assert.equal(salesCurrent(draft, changed), false);
|
|
assert.deepEqual(changed.result, book.result);
|
|
assert.deepEqual(recovered.selection_labels, original.selection_labels);
|
|
assert.equal(recovered.currency, original.currency);
|
|
});
|
|
if (process.env.IVA_G2L_OUT)
|
|
fs.writeFileSync(
|
|
process.env.IVA_G2L_OUT,
|
|
JSON.stringify(fixture().draft, null, 2),
|
|
);
|