228 lines
8.1 KiB
JavaScript
228 lines
8.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
newImportExemption,
|
|
evaluateImportExemption,
|
|
recordImportExemptions,
|
|
saveImportExemptionBook,
|
|
importExemptionBook,
|
|
importExemptionCurrent,
|
|
importExemptionRules,
|
|
importServiceSource,
|
|
} from '../../resources/js/lib/iva-import-exemptions.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
import { offerReusableData } from '../../resources/js/lib/iva-offer-preparations.ts';
|
|
const frozen = JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/iva-offer-paths-inputs.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
const rules = importExemptionRules.filter((r) => r.branch === 'P050');
|
|
const row = (rule) => ({
|
|
...newImportExemption('g6f-' + rule.id, 'MZN'),
|
|
treatment: rule.id,
|
|
description: 'SYNTHETIC QA ' + rule.label,
|
|
period: '2026-12',
|
|
declaredValue: '5000',
|
|
importer: 'SYNTHETIC QA importador destinatário',
|
|
documentRef: 'SYNTHETIC QA G6F ' + rule.id,
|
|
documentDate: '2026-11-14',
|
|
evidence:
|
|
'SYNTHETIC QA serviço e montante integral incluídos na base identificada, conforme artigo16; pressuposto expresso, não documento real',
|
|
serviceImportRef: 'SYNTHETIC QA importação DU-A,2026-12',
|
|
serviceBaseRef: 'SYNTHETIC QA base DU-A,linha serviço5000MZN',
|
|
serviceProvider: 'SYNTHETIC QA prestador',
|
|
...(rule.id === 'accessory_service_first_destination'
|
|
? {
|
|
serviceFirstDestination:
|
|
'SYNTHETIC QA Manica,primeiro destino no documento de transporte',
|
|
}
|
|
: {}),
|
|
conditions: Object.fromEntries(
|
|
rule.conditions.map(([key]) => [key, 'yes']),
|
|
),
|
|
});
|
|
const draft = () => ({
|
|
...structuredClone(frozen),
|
|
context_opportunity: 'opportunity:336',
|
|
project_title: 'SYNTHETIC QA — G6F / Serviços na base da importação',
|
|
});
|
|
void test('P050 two treatments exempt only service VAT;every missing/failed condition leaves service and imported-goods tax indeterminate', () => {
|
|
assert.equal(rules.length, 2);
|
|
assert.match(importServiceSource, /16\(2\)/);
|
|
for (const rule of rules) {
|
|
const input = row(rule),
|
|
ok = evaluateImportExemption(input, 'MZN');
|
|
assert.equal(ok.serviceVat, '0.00');
|
|
assert.equal(ok.importVat, null);
|
|
assert.match(ok.reason, /IVA do serviço/);
|
|
for (const [key] of rule.conditions)
|
|
for (const value of ['', 'unknown', 'no']) {
|
|
const r = evaluateImportExemption(
|
|
{
|
|
...input,
|
|
conditions: { ...input.conditions, [key]: value },
|
|
},
|
|
'MZN',
|
|
);
|
|
assert.equal(
|
|
r.status,
|
|
value === 'no' ? 'not_applicable' : 'indeterminate',
|
|
);
|
|
assert.equal(r.serviceVat, null);
|
|
assert.equal(r.importVat, null);
|
|
}
|
|
for (const patch of [
|
|
{ serviceImportRef: '' },
|
|
{ serviceBaseRef: '' },
|
|
{ declaredValue: '' },
|
|
{ documentRef: '' },
|
|
{ period: '2025-12' },
|
|
{ currency: 'USD' },
|
|
{ documentDate: '2026-02-30' },
|
|
])
|
|
assert.equal(
|
|
evaluateImportExemption({ ...input, ...patch }, 'MZN')
|
|
.serviceVat,
|
|
null,
|
|
);
|
|
}
|
|
});
|
|
void test('first-destination reference is required only for added accessory service;switching keeps facts but cannot inherit own eligibility', () => {
|
|
const inValue = row(rules[0]),
|
|
accessory = row(rules[1]);
|
|
assert.equal(
|
|
evaluateImportExemption(
|
|
{ ...accessory, serviceFirstDestination: '' },
|
|
'MZN',
|
|
).status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(evaluateImportExemption(inValue, 'MZN').status, 'exempt');
|
|
const changed = { ...inValue, treatment: rules[1].id };
|
|
assert.equal(
|
|
evaluateImportExemption(changed, 'MZN').status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(changed.documentRef, inValue.documentRef);
|
|
assert.ok(
|
|
!rules[0].conditions.some(
|
|
([key]) => key === 'serviceToFirstDestination',
|
|
),
|
|
);
|
|
const beyond = {
|
|
...accessory,
|
|
conditions: {
|
|
...accessory.conditions,
|
|
serviceToFirstDestination: 'no',
|
|
},
|
|
};
|
|
assert.equal(
|
|
evaluateImportExemption(beyond, 'MZN').status,
|
|
'not_applicable',
|
|
);
|
|
assert.equal(
|
|
evaluateImportExemption(
|
|
{
|
|
...inValue,
|
|
conditions: {
|
|
...inValue.conditions,
|
|
serviceFullValueIncluded: 'no',
|
|
},
|
|
},
|
|
'MZN',
|
|
).serviceVat,
|
|
null,
|
|
);
|
|
});
|
|
void test('save/recovery/report preserve27fiscalbooks and946fc089scenarios/requests/history exactly;service is not added to base or deduction', () => {
|
|
const d = draft(),
|
|
prior = JSON.parse(frozen.modelo_a_snapshot);
|
|
let book = recordImportExemptions(d, {
|
|
rows: rules.map((rule) => ({ ...row(rule), conditions: {} })),
|
|
});
|
|
book = recordImportExemptions(d, {
|
|
...book,
|
|
rows: rules.map((rule) => ({
|
|
...row(rule),
|
|
conditions: {
|
|
...row(rule).conditions,
|
|
serviceFullValueIncluded: 'no',
|
|
},
|
|
})),
|
|
});
|
|
book = recordImportExemptions(d, { ...book, rows: rules.map(row) });
|
|
assert.equal(book.history.length, 2);
|
|
assert.ok(
|
|
book.history.every((h) =>
|
|
h.rows.every((r) => r.serviceVat === null && r.importVat === null),
|
|
),
|
|
);
|
|
d.modelo_a_snapshot = saveImportExemptionBook(d, book);
|
|
assert.deepEqual(importExemptionBook(JSON.parse(JSON.stringify(d))), book);
|
|
assert.match(book.result.sourceKey, /import-included-services\/1/);
|
|
const next = JSON.parse(d.modelo_a_snapshot);
|
|
for (const key of ['books', 'salesOperations', 'offerPreparations'])
|
|
assert.deepEqual(next[key], prior[key]);
|
|
for (const [scope, previous] of Object.entries(prior.importExemptions)) {
|
|
assert.deepEqual(next.importExemptions[scope], previous);
|
|
assert.ok(
|
|
importExemptionCurrent(
|
|
{ ...d, context_opportunity: scope },
|
|
previous,
|
|
),
|
|
);
|
|
}
|
|
assert.equal(
|
|
next.books['opportunity:329'].result.periods[0].fields['11'],
|
|
null,
|
|
);
|
|
const report = JSON.stringify(
|
|
createProjectDossierReport(d, {
|
|
selectedSections: new Set(['finance']),
|
|
version: 'QA G6F',
|
|
}),
|
|
);
|
|
for (const token of [
|
|
'Serviço associado à importação P050',
|
|
'IVA do serviço: 0.00',
|
|
'opportunity:336',
|
|
'Manica',
|
|
'2026-12',
|
|
'SYNTHETIC QA G6F',
|
|
'12(1)(e)',
|
|
'primeiro destino',
|
|
'campo11',
|
|
])
|
|
assert.ok(report.includes(token), token);
|
|
assert.ok(!report.includes('IVA na importação: 0.00'));
|
|
if (process.env.IVA_G6F_CALCULATED)
|
|
fs.writeFileSync(process.env.IVA_G6F_CALCULATED, JSON.stringify(d));
|
|
});
|
|
void test('old goods rows keep own tax representation and malformed optional service references cannot overwrite saved originals', () => {
|
|
const d = draft(),
|
|
prior = JSON.parse(d.modelo_a_snapshot);
|
|
for (const book of Object.values(prior.importExemptions))
|
|
assert.ok(
|
|
book.result.rows.every((row) => !Object.hasOwn(row, 'serviceVat')),
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
saveImportExemptionBook(d, {
|
|
rows: [{ ...row(rules[0]), serviceBaseRef: 42 }],
|
|
}),
|
|
/Referências do serviço/,
|
|
);
|
|
const serviceOnly = { ...d, context_opportunity: 'opportunity:336' };
|
|
serviceOnly.modelo_a_snapshot = saveImportExemptionBook(serviceOnly, {
|
|
rows: rules.map(row),
|
|
});
|
|
assert.equal(
|
|
offerReusableData(serviceOnly).length,
|
|
0,
|
|
'P050 services cannot be reused as imported goods in P084',
|
|
);
|
|
});
|