366 lines
13 KiB
JavaScript
366 lines
13 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
digitalTreatments,
|
|
digitalConditions,
|
|
newDigitalRow,
|
|
evaluateDigital,
|
|
recordDigital,
|
|
saveDigitalBook,
|
|
digitalBook,
|
|
digitalCurrent,
|
|
digitalReportRows,
|
|
switchDigitalKind,
|
|
switchDigitalTreatment,
|
|
} from '../../resources/js/lib/iva-digital.ts';
|
|
import {
|
|
newForeignOperation,
|
|
recordForeignOperations,
|
|
saveForeignOperationBook,
|
|
foreignOperationCurrent,
|
|
foreignSources,
|
|
} from '../../resources/js/lib/iva-foreign-operations.ts';
|
|
const frozen = JSON.parse(
|
|
fs.readFileSync(
|
|
'tests/frontend/fixtures/iva-digital-preserved.json',
|
|
'utf8',
|
|
),
|
|
),
|
|
prior = JSON.parse(frozen.modelo_a_snapshot);
|
|
const draft = {
|
|
...frozen,
|
|
context_opportunity: 'opportunity:359',
|
|
project_title: 'SYNTHETIC QA — Digital / Manica',
|
|
};
|
|
function row(rule, direction = 'inbound') {
|
|
return {
|
|
...newDigitalRow(rule.id + '-' + direction),
|
|
kind: rule.kind,
|
|
treatment: rule.id,
|
|
description: 'SYNTHETIC QA ' + rule.label,
|
|
period: '2026-12',
|
|
origin: 'projected',
|
|
operationDate: '2026-12-10',
|
|
operationRef: 'SYNTHETIC QA op ' + rule.id + '-' + direction,
|
|
documentRef: 'SYNTHETIC QA contrato/previsão',
|
|
documentDate: '2026-12-01',
|
|
subjectRef: 'SYNTHETIC QA objecto/direitos/funcionalidade delimitados',
|
|
useRef: 'SYNTHETIC QA destinatário e utilização',
|
|
natureRef: 'SYNTHETIC QA natureza contratual ' + rule.kind,
|
|
scope: 'SYNTHETIC QA operação/dezembro2026/âmbito identificado',
|
|
evidence: 'SYNTHETIC QA factos ou pressupostos expressos',
|
|
providerPlace: 'SYNTHETIC QA estabelecimento fornecedor',
|
|
providerCountry: direction === 'inbound' ? 'foreign' : 'MZ',
|
|
providerRef: 'SYNTHETIC QA contrato/estabelecimento fornecedor',
|
|
buyerPlace: 'SYNTHETIC QA domicílio adquirente',
|
|
buyerCountry: direction === 'outbound' ? 'foreign' : 'MZ',
|
|
buyerRef: 'SYNTHETIC QA contrato/domicílio adquirente',
|
|
buyerTaxable: 'yes',
|
|
buyerTaxRef:
|
|
'SYNTHETIC QA sujeito passivo nacional, incluindo isento sem dedução',
|
|
providerTaxable: 'yes',
|
|
providerTaxRef: 'SYNTHETIC QA sujeito passivo fornecedor',
|
|
goodsPlace: 'MZ',
|
|
goodsPlaceRef:
|
|
'SYNTHETIC QA factos local6(1),não inferido de IP/servidor',
|
|
conditions: Object.fromEntries(
|
|
digitalConditions(rule.kind).map(([k]) => [k, 'yes']),
|
|
),
|
|
};
|
|
}
|
|
const cases = digitalTreatments.flatMap((rule) =>
|
|
['inbound', 'outbound', 'domestic'].map((direction) =>
|
|
row(rule, direction),
|
|
),
|
|
);
|
|
for (const r of cases)
|
|
void test(`${r.kind === 'goods' ? 'P130' : 'P121'} ${r.id} classification and responsibility`, () => {
|
|
const v = evaluateDigital(r);
|
|
assert.equal(v.status, 'determined');
|
|
assert.equal(
|
|
v.territory,
|
|
r.buyerCountry === 'foreign' ? 'outside' : 'national',
|
|
);
|
|
assert.equal(
|
|
v.responsible,
|
|
r.buyerCountry === 'foreign'
|
|
? 'outside'
|
|
: r.providerCountry === 'foreign'
|
|
? 'acquirer'
|
|
: 'provider',
|
|
);
|
|
assert.equal(
|
|
v.declarationReferenceDate,
|
|
r.providerCountry === 'foreign' && r.buyerCountry === 'MZ'
|
|
? '2027-01-10'
|
|
: null,
|
|
);
|
|
assert.equal(
|
|
v.procedure,
|
|
r.buyerCountry === 'foreign'
|
|
? 'outside'
|
|
: r.providerCountry === 'foreign'
|
|
? 'regulation_pending'
|
|
: 'general',
|
|
);
|
|
for (const k of ['vat', 'baseAmount', 'rate', 'deduction'])
|
|
assert.equal(v[k], null);
|
|
});
|
|
void test('Unknown facts do not become national, exempt or zero; no name-only kind inference', () => {
|
|
const r = cases[0];
|
|
for (const patch of [
|
|
{ kind: '' },
|
|
{ kind: 'goods' },
|
|
{ period: '2025-12' },
|
|
{ operationDate: '2026-02-30' },
|
|
{ operationDate: '2026-11-10' },
|
|
{ origin: 'unknown' },
|
|
{ documentDate: '2026-02-30' },
|
|
{ subjectRef: '' },
|
|
{ useRef: '' },
|
|
{ natureRef: '' },
|
|
{ scope: '' },
|
|
{ evidence: '' },
|
|
{ buyerCountry: '' },
|
|
{ buyerRef: '' },
|
|
{ providerRef: '' },
|
|
])
|
|
assert.equal(
|
|
evaluateDigital({ ...r, ...patch }).status,
|
|
'indeterminate',
|
|
);
|
|
for (const [k] of digitalConditions(r.kind)) {
|
|
assert.equal(
|
|
evaluateDigital({ ...r, conditions: { ...r.conditions, [k]: '' } })
|
|
.status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
evaluateDigital({
|
|
...r,
|
|
conditions: { ...r.conditions, [k]: 'no' },
|
|
}).status,
|
|
'not_applicable',
|
|
);
|
|
}
|
|
const outbound = {
|
|
...cases[1],
|
|
providerCountry: '',
|
|
providerRef: '',
|
|
providerPlace: '',
|
|
buyerTaxable: '',
|
|
buyerTaxRef: '',
|
|
};
|
|
assert.equal(evaluateDigital(outbound).territory, 'outside');
|
|
});
|
|
void test('26(4) quality/reference independent of national territory; consumer never automatic withholding', () => {
|
|
for (const r of [cases[0], cases.find((r) => r.kind === 'goods')])
|
|
for (const patch of [
|
|
{ buyerTaxable: '' },
|
|
{ buyerTaxRef: '' },
|
|
{ buyerTaxable: 'no' },
|
|
]) {
|
|
const v = evaluateDigital({ ...r, ...patch });
|
|
assert.equal(v.territory, 'national');
|
|
assert.equal(v.status, 'determined');
|
|
assert.equal(v.responsible, 'indeterminate');
|
|
assert.equal(v.declarationReferenceDate, null);
|
|
}
|
|
const r = {
|
|
...cases[0],
|
|
buyerTaxable: 'no',
|
|
buyerTaxRef: 'SYNTHETIC QA consumidor',
|
|
representative: 'yes',
|
|
representativeRef: 'SYNTHETIC QA residente/procuração/poderes',
|
|
communicationPrior: 'yes',
|
|
communicationRef: 'SYNTHETIC QA comunicação anterior',
|
|
};
|
|
assert.equal(evaluateDigital(r).responsible, 'representative');
|
|
assert.equal(evaluateDigital(r).declarationReferenceDate, null);
|
|
for (const patch of [
|
|
{ providerTaxRef: '' },
|
|
{ representativeRef: '' },
|
|
{ communicationPrior: 'no' },
|
|
{ communicationRef: '' },
|
|
])
|
|
assert.equal(
|
|
evaluateDigital({ ...r, ...patch }).responsible,
|
|
'indeterminate',
|
|
);
|
|
const business = {
|
|
...r,
|
|
representative: 'no',
|
|
businessUse: 'yes',
|
|
businessRef: 'SYNTHETIC QA actividade/aquisição profissional',
|
|
};
|
|
assert.equal(evaluateDigital(business).responsible, 'acquirer');
|
|
assert.equal(
|
|
evaluateDigital({ ...business, businessUse: 'no' }).responsible,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
evaluateDigital({ ...business, businessRef: '' }).responsible,
|
|
'indeterminate',
|
|
);
|
|
});
|
|
void test('Domestic digital goods need demonstrated6(1) location, not consumer address/server; domestic services6(3)', () => {
|
|
const r = cases.find(
|
|
(r) => r.kind === 'goods' && r.id.endsWith('domestic'),
|
|
);
|
|
assert.equal(evaluateDigital({ ...r, goodsPlace: '' }).territory, null);
|
|
assert.equal(
|
|
evaluateDigital({ ...r, goodsPlaceRef: '' }).status,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
evaluateDigital({ ...r, goodsPlace: 'foreign' }).territory,
|
|
'outside',
|
|
);
|
|
assert.equal(
|
|
evaluateDigital({ ...cases[2], goodsPlace: '', goodsPlaceRef: '' })
|
|
.territory,
|
|
'national',
|
|
);
|
|
assert.equal(
|
|
evaluateDigital({ ...r, providerTaxable: '' }).responsible,
|
|
'indeterminate',
|
|
);
|
|
});
|
|
void test('Switching nature/treatment preserves deliberate references and hidden facts, rechecks specific conditions', () => {
|
|
const r = cases[0],
|
|
sw = switchDigitalKind(r, 'goods');
|
|
assert.equal(sw.treatment, '');
|
|
assert.deepEqual(sw.conditions, {});
|
|
for (const k of [
|
|
'natureRef',
|
|
'buyerTaxRef',
|
|
'goodsPlaceRef',
|
|
'scope',
|
|
'evidence',
|
|
])
|
|
assert.equal(sw[k], r[k]);
|
|
assert.deepEqual(switchDigitalTreatment(r, 'teaching').conditions, {});
|
|
});
|
|
void test('Opportunity isolation, stable history and malformed-store guard; all prior books including P002 immutable', () => {
|
|
const a = recordDigital(draft, { rows: cases }),
|
|
d = { ...draft, modelo_a_snapshot: saveDigitalBook(draft, a) },
|
|
b = recordDigital(d, a);
|
|
d.modelo_a_snapshot = saveDigitalBook(d, b);
|
|
assert.equal(digitalCurrent(d, b), true);
|
|
assert.equal(b.history.length, 1);
|
|
assert.equal(digitalReportRows(d).length, 42);
|
|
assert.deepEqual(
|
|
digitalBook({ ...d, context_opportunity: 'opportunity:358' }),
|
|
{ rows: [] },
|
|
);
|
|
const changed = {
|
|
...b,
|
|
rows: b.rows.map((r, i) =>
|
|
i === 0 ? { ...r, buyerCountry: 'foreign' } : r,
|
|
),
|
|
};
|
|
assert.equal(digitalCurrent(d, changed), false);
|
|
assert.equal(b.history[0].inputs[0].buyerCountry, 'MZ');
|
|
assert.equal(b.history[0].rows[0].territory, 'national');
|
|
const store = JSON.parse(d.modelo_a_snapshot);
|
|
delete store.digitalOperations;
|
|
assert.deepEqual(store, prior);
|
|
assert.throws(() => recordDigital(d, { rows: [cases[0], cases[0]] }));
|
|
assert.throws(() =>
|
|
digitalBook({
|
|
...d,
|
|
modelo_a_snapshot: JSON.stringify({
|
|
...store,
|
|
digitalOperations: [],
|
|
}),
|
|
}),
|
|
);
|
|
assert.throws(() =>
|
|
digitalBook({
|
|
...d,
|
|
modelo_a_snapshot: JSON.stringify({
|
|
...store,
|
|
digitalOperations: { x: { rows: [{ id: 'incomplete' }] } },
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
void test('P002 reuses new digital location in same opportunity, guards stale source and keeps accepted P002 snapshots', () => {
|
|
const a = recordDigital(draft, { rows: cases }),
|
|
d = { ...draft, modelo_a_snapshot: saveDigitalBook(draft, a) },
|
|
r = {
|
|
...newForeignOperation('digital-outside'),
|
|
sourceKind: 'digital',
|
|
sourceRowId: cases[1].id,
|
|
hypotheticalTaxable: 'yes',
|
|
hypotheticalRef: 'SYNTHETIC QA hipótese interna',
|
|
excluded19_2: 'no',
|
|
scope: 'SYNTHETIC QA operação digital exterior',
|
|
evidence: 'SYNTHETIC QA pressupostos',
|
|
};
|
|
assert.equal(foreignSources(d, 'digital').length, 42);
|
|
const b = recordForeignOperations(d, { rows: [r] });
|
|
assert.equal(b.result.rows[0].relation19, 'potential');
|
|
assert.equal(b.result.rows[0].deduction, null);
|
|
d.modelo_a_snapshot = saveForeignOperationBook(d, b);
|
|
assert.equal(foreignOperationCurrent(d, b), true);
|
|
const next = {
|
|
...d,
|
|
modelo_a_snapshot: saveDigitalBook(d, recordDigital(d, a)),
|
|
};
|
|
assert.equal(foreignOperationCurrent(next, b), false);
|
|
assert.equal(b.result.rows[0].source.revision, 1);
|
|
assert.deepEqual(
|
|
JSON.parse(d.modelo_a_snapshot).foreignOperations['opportunity:358'],
|
|
prior.foreignOperations['opportunity:358'],
|
|
);
|
|
});
|
|
void test('Fixture for private API includes42 examples and unresolved/representative/business cases', () => {
|
|
const inbound = cases[0],
|
|
extra = [
|
|
{
|
|
...inbound,
|
|
id: 'consumer',
|
|
buyerTaxable: 'no',
|
|
buyerTaxRef: 'SYNTHETIC QA consumidor',
|
|
},
|
|
{
|
|
...inbound,
|
|
id: 'unknown_quality',
|
|
buyerTaxable: '',
|
|
buyerTaxRef: '',
|
|
},
|
|
{
|
|
...inbound,
|
|
id: 'representative',
|
|
buyerTaxable: 'no',
|
|
buyerTaxRef: 'SYNTHETIC QA não sujeito passivo',
|
|
representative: 'yes',
|
|
representativeRef: 'SYNTHETIC QA representante/procuração',
|
|
communicationPrior: 'yes',
|
|
communicationRef: 'SYNTHETIC QA comunicação anterior',
|
|
},
|
|
{
|
|
...inbound,
|
|
id: 'business_26_3',
|
|
buyerTaxable: 'no',
|
|
buyerTaxRef: 'SYNTHETIC QA não sujeito passivo',
|
|
representative: 'no',
|
|
businessUse: 'yes',
|
|
businessRef: 'SYNTHETIC QA actividade profissional',
|
|
},
|
|
],
|
|
a = recordDigital(draft, { rows: [...cases, ...extra] }),
|
|
d = { ...draft, modelo_a_snapshot: saveDigitalBook(draft, a) },
|
|
b = recordDigital(d, a);
|
|
d.modelo_a_snapshot = saveDigitalBook(d, b);
|
|
assert.equal(b.rows.length, 46);
|
|
assert.equal(b.history.length, 1);
|
|
if (process.env.IVA_DIGITAL_FIXTURE)
|
|
fs.writeFileSync(
|
|
process.env.IVA_DIGITAL_FIXTURE,
|
|
JSON.stringify(d, null, 2) + '\n',
|
|
);
|
|
});
|