234 lines
8.3 KiB
JavaScript
234 lines
8.3 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
baggageTreatments,
|
|
baggageConditions,
|
|
} from '../../resources/js/lib/iva-returnee-baggage.ts';
|
|
import {
|
|
newReturnee,
|
|
evaluateReturnee,
|
|
recordReturnees,
|
|
saveReturneeBook,
|
|
returneeBook,
|
|
returneeCurrent,
|
|
returneeReportRows,
|
|
} from '../../resources/js/lib/iva-returnees.ts';
|
|
const frozen = JSON.parse(
|
|
fs.readFileSync(
|
|
'tests/frontend/fixtures/iva-baggage-preserved.json',
|
|
'utf8',
|
|
),
|
|
),
|
|
prior = JSON.parse(frozen.modelo_a_snapshot),
|
|
draft = {
|
|
...frozen,
|
|
context_opportunity: 'opportunity:366',
|
|
project_title: 'SYNTHETIC QA — Bagagem no regresso / Manica',
|
|
};
|
|
function row(treatment, id = treatment, patch = {}) {
|
|
return {
|
|
...newReturnee(id, 'MZN'),
|
|
treatment,
|
|
description: 'SYNTHETIC QA bagagem ' + id,
|
|
beneficiary: 'SYNTHETIC QA viajante ' + id,
|
|
period: '2026-10',
|
|
origin: 'projected',
|
|
scope: 'SYNTHETIC QA bens/operação/período',
|
|
evidence: 'SYNTHETIC QA factos/pressupostos expressos',
|
|
documentRef: 'SYNTHETIC QA documentos disponíveis',
|
|
missionRef: 'SYNTHETIC QA referência de missão pública/estudo',
|
|
importDate: '2026-10-10',
|
|
arrival: '2026-10-10',
|
|
stayStart: '2024-01-01',
|
|
stayEnd: '2026-10-09',
|
|
missionPlannedEnd: '2027-01-01',
|
|
baggageKind: 'personal',
|
|
baggageRoute: 'accompanied',
|
|
earlyReturnRef: 'SYNTHETIC QA regresso por serviço doEstado',
|
|
conditions: {
|
|
...Object.fromEntries(baggageConditions.map(([k]) => [k, 'yes'])),
|
|
earlyService: 'yes',
|
|
},
|
|
...patch,
|
|
};
|
|
}
|
|
const rows = [];
|
|
for (const treatment of baggageTreatments)
|
|
for (const kind of ['personal', 'household']) {
|
|
const r = row(treatment.id, treatment.id + '-' + kind, {
|
|
baggageKind: kind,
|
|
});
|
|
rows.push(r);
|
|
void test(
|
|
treatment.id +
|
|
' / ' +
|
|
kind +
|
|
' sem condições de veículo transplantadas',
|
|
() => {
|
|
const result = evaluateReturnee(r, 'MZN');
|
|
assert.equal(result.status, 'exempt');
|
|
assert.equal(result.importVat, '0.00');
|
|
assert.equal(result.deduction, null);
|
|
assert.equal(r.conditions.national, undefined);
|
|
assert.equal(r.previous, '');
|
|
assert.equal(r.request, '');
|
|
assert.equal(r.historyRef, '');
|
|
assert.match(result.source, /57\(2\)c\/d/);
|
|
assert.match(result.reason, /57\(4\).*não dispensa/);
|
|
},
|
|
);
|
|
}
|
|
void test('P058 bagagem não acompanhada61:180dias próprios,sem60+30deveículos e sem quota5anos', () => {
|
|
const r = row('student_baggage', 'unaccompanied', {
|
|
arrival: '2026-04-13',
|
|
stayEnd: '2026-04-12',
|
|
baggageRoute: 'unaccompanied',
|
|
});
|
|
assert.equal(evaluateReturnee(r, 'MZN').status, 'exempt');
|
|
const late = { ...r, arrival: '2026-04-12', stayEnd: '2026-04-11' };
|
|
assert.equal(evaluateReturnee(late, 'MZN').status, 'indeterminate');
|
|
const advance = { ...r, arrival: '2026-10-20', stayEnd: '2026-10-09' };
|
|
assert.equal(evaluateReturnee(advance, 'MZN').status, 'indeterminate');
|
|
const authorised = {
|
|
...advance,
|
|
id: 'advance-authorised',
|
|
earlyBaggageRef: 'SYNTHETIC QA estância/âmbito61(2)',
|
|
conditions: { ...advance.conditions, advanceClearance: 'yes' },
|
|
};
|
|
assert.equal(evaluateReturnee(authorised, 'MZN').status, 'exempt');
|
|
assert.equal(
|
|
evaluateReturnee({ ...authorised, earlyBaggageRef: '' }, 'MZN').status,
|
|
'indeterminate',
|
|
);
|
|
rows.push(r, authorised);
|
|
});
|
|
void test('P058 missão superior a um ano e regresso antecipado pelo Estado são percursos distintos', () => {
|
|
const r = row('civil_baggage');
|
|
assert.equal(
|
|
evaluateReturnee({ ...r, stayStart: '2025-10-09' }, 'MZN').status,
|
|
'not_applicable',
|
|
);
|
|
assert.equal(
|
|
evaluateReturnee({ ...r, stayStart: '2025-10-08' }, 'MZN').status,
|
|
'exempt',
|
|
);
|
|
const early = row('early_state_baggage', 'early-short', {
|
|
stayStart: '2026-02-01',
|
|
missionPlannedEnd: '2028-02-01',
|
|
});
|
|
assert.equal(evaluateReturnee(early, 'MZN').status, 'exempt');
|
|
assert.equal(
|
|
evaluateReturnee(
|
|
{
|
|
...early,
|
|
conditions: { ...early.conditions, earlyService: 'no' },
|
|
},
|
|
'MZN',
|
|
).status,
|
|
'not_applicable',
|
|
);
|
|
for (const patch of [
|
|
{ missionPlannedEnd: '2027-02-01' },
|
|
{ missionPlannedEnd: '2026-09-01' },
|
|
{ earlyReturnRef: '' },
|
|
{ conditions: { ...early.conditions, earlyService: '' } },
|
|
])
|
|
assert.notEqual(
|
|
evaluateReturnee({ ...early, ...patch }, 'MZN').status,
|
|
'exempt',
|
|
);
|
|
rows.push(early);
|
|
});
|
|
void test('P058 bagagem desconhecida/comercial/fora57(3) não vira IVAzero;57(2)b permanece por corresponder', () => {
|
|
const r = rows[0];
|
|
for (const patch of [
|
|
{ missionRef: '' },
|
|
{ baggageKind: '' },
|
|
{ baggageKind: 'vehicle' },
|
|
{ baggageRoute: '' },
|
|
{ currency: 'USD' },
|
|
{ period: '2027-10' },
|
|
{ importDate: '2026-02-30' },
|
|
{ documentRef: '' },
|
|
{ conditions: { ...r.conditions, mission: '' } },
|
|
]) {
|
|
const v = evaluateReturnee({ ...r, ...patch }, 'MZN');
|
|
assert.equal(v.status, 'indeterminate');
|
|
assert.equal(v.importVat, null);
|
|
}
|
|
for (const [k] of baggageConditions)
|
|
assert.equal(
|
|
evaluateReturnee(
|
|
{ ...r, conditions: { ...r.conditions, [k]: 'no' } },
|
|
'MZN',
|
|
).status,
|
|
'not_applicable',
|
|
);
|
|
assert.equal(
|
|
evaluateReturnee({ ...r, treatment: 'domicile_baggage' }, 'MZN').status,
|
|
'indeterminate',
|
|
);
|
|
});
|
|
void test('P058 bagagem não consome quota de veículo;linhas da mesma bagagem não são duplicação do direito de veículo', () => {
|
|
const first = rows[0],
|
|
second = {
|
|
...first,
|
|
id: 'same-person-household',
|
|
baggageKind: 'household',
|
|
};
|
|
assert.deepEqual(
|
|
recordReturnees(draft, { rows: [first, second] }).result.rows.map(
|
|
(r) => r.status,
|
|
),
|
|
['exempt', 'exempt'],
|
|
);
|
|
});
|
|
void test('P058 gravação/recuperação/histórico/relatório preservam128fechos e anteriores livrosP058/P104/ofertas', () => {
|
|
let book = recordReturnees(draft, { rows });
|
|
assert.equal(returneeCurrent(draft, book), true);
|
|
const before = structuredClone(book.result),
|
|
changed = {
|
|
...book,
|
|
rows: book.rows.map((r, i) =>
|
|
i ? r : { ...r, scope: 'SYNTHETIC QA âmbito revisto' },
|
|
),
|
|
};
|
|
assert.equal(returneeCurrent(draft, changed), false);
|
|
assert.deepEqual(book.result, before);
|
|
book = recordReturnees(draft, changed);
|
|
draft.modelo_a_snapshot = saveReturneeBook(draft, book);
|
|
assert.deepEqual(returneeBook(draft), book);
|
|
assert.deepEqual(book.history[0], before);
|
|
assert.equal(
|
|
returneeBook({ ...draft, context_opportunity: 'opportunity:999' }).rows
|
|
.length,
|
|
0,
|
|
);
|
|
const next = JSON.parse(draft.modelo_a_snapshot);
|
|
assert.deepEqual(
|
|
Object.fromEntries(
|
|
Object.entries(prior).filter(([k]) => k !== 'returneeImports'),
|
|
),
|
|
Object.fromEntries(
|
|
Object.entries(next).filter(([k]) => k !== 'returneeImports'),
|
|
),
|
|
);
|
|
for (const [k, v] of Object.entries(prior.returneeImports ?? {}))
|
|
assert.deepEqual(next.returneeImports[k], v);
|
|
assert.match(returneeReportRows(draft)[0][3], /missão:SYNTHETIC QA/);
|
|
assert.match(returneeReportRows(draft)[0][3], /versão2/);
|
|
assert.throws(
|
|
() =>
|
|
saveReturneeBook(draft, {
|
|
rows: [{ ...rows[0], missionRef: { invalid: true } }],
|
|
}),
|
|
/não recuperáveis/,
|
|
);
|
|
if (process.env.IVA_BAGGAGE_FIXTURE)
|
|
fs.writeFileSync(
|
|
'tests/frontend/fixtures/iva-baggage.json',
|
|
JSON.stringify(draft),
|
|
);
|
|
});
|