314 lines
11 KiB
JavaScript
314 lines
11 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
newSecondhand,
|
|
switchSecondhandTreatment,
|
|
switchSecondhandCategory,
|
|
secondhandConditions,
|
|
secondhandCategoryConditions,
|
|
secondhandTreatments,
|
|
secondhandCategories,
|
|
evaluateSecondhand,
|
|
recordSecondhands,
|
|
saveSecondhandBook,
|
|
secondhandBook,
|
|
secondhandCurrent,
|
|
secondhandReportRows,
|
|
} from '../../resources/js/lib/iva-secondhand.ts';
|
|
const frozen = JSON.parse(
|
|
fs.readFileSync(
|
|
'tests/frontend/fixtures/iva-secondhand-preserved.json',
|
|
'utf8',
|
|
),
|
|
);
|
|
const draft = {
|
|
...frozen,
|
|
currency: 'MZN',
|
|
context_opportunity: 'opportunity:354',
|
|
project_title: 'SYNTHETIC QA — Segunda mão / Manica',
|
|
};
|
|
function row(t, c) {
|
|
return {
|
|
...newSecondhand(t + '-' + c, 'MZN'),
|
|
treatment: t,
|
|
category: c,
|
|
description: 'SYNTHETIC QA ' + t + '/' + c,
|
|
period: '2026-12',
|
|
origin: 'projected',
|
|
operationDate: '2026-12-12',
|
|
operationRef: 'SYNTHETIC QA operação',
|
|
documentRef: 'SYNTHETIC QA documento/pressuposto',
|
|
documentDate: '2026-12-12',
|
|
goodsRef: 'SYNTHETIC QA bem individual ' + t + c,
|
|
operatorRef: 'SYNTHETIC QA estatuto operador',
|
|
categoryRef: 'SYNTHETIC QA características/definição3(6)',
|
|
artSubtype: 'print',
|
|
editionCount: '200',
|
|
ceramicUniqueSigned: 'yes',
|
|
collectionSubtype: 'postal',
|
|
postalNotCirculating: 'yes',
|
|
ageYears: '101',
|
|
sourceType: 'private',
|
|
sourceRef: 'SYNTHETIC QA origem particular nacional',
|
|
domesticPurchase: 'yes',
|
|
regimeChoice: 'margin',
|
|
domesticTaxable: 'yes',
|
|
grossSale: '1160',
|
|
purchasePrice: '1000',
|
|
purchaseRef: 'SYNTHETIC QA preço compra justificado',
|
|
grossInvoice: '1300',
|
|
hammerPrice: '1000',
|
|
principalCommission: '100',
|
|
auctionRef:
|
|
'SYNTHETIC QA organizador emnome próprio/contrato comissão/leilão',
|
|
principalRef: 'SYNTHETIC QA comitente/líquido',
|
|
ownVat: '55.17',
|
|
ownVatRef: 'SYNTHETIC QA IVApróprio transmissão identificado',
|
|
scope: 'SYNTHETIC QA bem/partes/operação/período',
|
|
evidence: 'SYNTHETIC QA pressupostos expressos',
|
|
conditions: {
|
|
...Object.fromEntries(
|
|
[
|
|
...secondhandConditions,
|
|
...secondhandCategoryConditions(c),
|
|
].map(([k]) => [k, 'yes']),
|
|
),
|
|
regimeConfirmed: 'yes',
|
|
compositionConfirmed: 'yes',
|
|
},
|
|
};
|
|
}
|
|
for (const t of secondhandTreatments)
|
|
for (const c of secondhandCategories)
|
|
void test(
|
|
t.branch +
|
|
'/P125 ' +
|
|
c.id +
|
|
' ownfacts,eligibleorigin and specificformula',
|
|
() => {
|
|
const r = row(t.id, c.id),
|
|
result = evaluateSecondhand(r, 'MZN');
|
|
assert.equal(result.status, 'determined');
|
|
assert.equal(result.classification, 'qualified_goods');
|
|
assert.equal(
|
|
result.baseAmount,
|
|
t.id === 'reseller_margin' ? '138.00' : '344.83',
|
|
);
|
|
assert.equal(result.vat, null);
|
|
assert.equal(result.rate, null);
|
|
for (const [k] of [
|
|
...secondhandConditions,
|
|
...secondhandCategoryConditions(c.id),
|
|
]) {
|
|
const missing = structuredClone(r);
|
|
delete missing.conditions[k];
|
|
assert.equal(
|
|
evaluateSecondhand(missing, 'MZN').classification,
|
|
'indeterminate',
|
|
);
|
|
missing.conditions[k] = 'no';
|
|
assert.equal(
|
|
evaluateSecondhand(missing, 'MZN').classification,
|
|
'not_applicable',
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand(missing, 'MZN').baseAmount,
|
|
null,
|
|
);
|
|
}
|
|
for (const field of [
|
|
'description',
|
|
'operationRef',
|
|
'documentRef',
|
|
'goodsRef',
|
|
'operatorRef',
|
|
'categoryRef',
|
|
'scope',
|
|
'evidence',
|
|
])
|
|
assert.equal(
|
|
evaluateSecondhand({ ...r, [field]: '' }, 'MZN')
|
|
.classification,
|
|
'indeterminate',
|
|
field,
|
|
);
|
|
for (const patch of [
|
|
{ period: '2025-12' },
|
|
{ operationDate: '2026-11-12' },
|
|
{ documentDate: '2026-02-30' },
|
|
{ currency: 'USD' },
|
|
{ sourceType: 'legacy_35_42' },
|
|
{ sourceType: 'other' },
|
|
{ sourceRef: '' },
|
|
{ domesticPurchase: '' },
|
|
{ domesticTaxable: '' },
|
|
])
|
|
assert.equal(
|
|
evaluateSecondhand({ ...r, ...patch }, 'MZN')
|
|
.baseAmount,
|
|
null,
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand(
|
|
{ ...r, sourceType: 'margin_reseller' },
|
|
'MZN',
|
|
).status,
|
|
'determined',
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand(
|
|
{ ...r, sourceType: 'exempt_9_12_e' },
|
|
'MZN',
|
|
).status,
|
|
'determined',
|
|
);
|
|
if (t.id === 'reseller_margin') {
|
|
assert.equal(
|
|
evaluateSecondhand(
|
|
{ ...r, regimeChoice: 'general' },
|
|
'MZN',
|
|
).baseAmount,
|
|
null,
|
|
);
|
|
for (const field of [
|
|
'grossSale',
|
|
'purchasePrice',
|
|
'purchaseRef',
|
|
])
|
|
assert.equal(
|
|
evaluateSecondhand({ ...r, [field]: '' }, 'MZN')
|
|
.baseAmount,
|
|
null,
|
|
);
|
|
} else {
|
|
for (const field of [
|
|
'grossInvoice',
|
|
'hammerPrice',
|
|
'principalCommission',
|
|
'ownVat',
|
|
'ownVatRef',
|
|
'auctionRef',
|
|
'principalRef',
|
|
])
|
|
assert.equal(
|
|
evaluateSecondhand({ ...r, [field]: '' }, 'MZN')
|
|
.baseAmount,
|
|
null,
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand(
|
|
{ ...r, principalCommission: '1001' },
|
|
'MZN',
|
|
).baseAmount,
|
|
null,
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand({ ...r, ownVat: '401' }, 'MZN')
|
|
.baseAmount,
|
|
null,
|
|
);
|
|
}
|
|
},
|
|
);
|
|
void test('P092 rounding towholeunit,zero vsunknown and negative individualmargin no compensation', () => {
|
|
const r = row('reseller_margin', 'used_goods');
|
|
assert.equal(
|
|
evaluateSecondhand({ ...r, grossSale: '1000.58' }, 'MZN').baseAmount,
|
|
'1.00',
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand({ ...r, grossSale: '1000.579999' }, 'MZN')
|
|
.baseAmount,
|
|
'0.00',
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand({ ...r, grossSale: '1000' }, 'MZN').baseAmount,
|
|
'0.00',
|
|
);
|
|
const loss = evaluateSecondhand({ ...r, grossSale: '900' }, 'MZN');
|
|
assert.equal(loss.grossMargin, '-100.00');
|
|
assert.equal(loss.baseAmount, '0.00');
|
|
assert.match(loss.reason, /sem compensar/);
|
|
assert.equal(
|
|
evaluateSecondhand({ ...r, purchasePrice: '' }, 'MZN').baseAmount,
|
|
null,
|
|
);
|
|
const positive = evaluateSecondhand(r, 'MZN');
|
|
assert.equal(positive.baseAmount, '138.00');
|
|
assert.equal(positive.baseDivisorRate, '16%');
|
|
assert.equal(positive.vat, null);
|
|
});
|
|
void test('P125 boundaries and both switches preserve hidden values withoutautomaticregime', () => {
|
|
const art = row('reseller_margin', 'art');
|
|
assert.equal(
|
|
evaluateSecondhand({ ...art, editionCount: '201' }, 'MZN')
|
|
.classification,
|
|
'not_applicable',
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand({ ...art, editionCount: '' }, 'MZN').classification,
|
|
'indeterminate',
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand(
|
|
{ ...art, artSubtype: 'ceramic', ceramicUniqueSigned: '' },
|
|
'MZN',
|
|
).baseAmount,
|
|
null,
|
|
);
|
|
const antique = row('reseller_margin', 'antique');
|
|
assert.equal(
|
|
evaluateSecondhand({ ...antique, ageYears: '100' }, 'MZN')
|
|
.classification,
|
|
'not_applicable',
|
|
);
|
|
assert.equal(
|
|
evaluateSecondhand({ ...antique, ageYears: '100.1' }, 'MZN')
|
|
.classification,
|
|
'qualified_goods',
|
|
);
|
|
const changed = switchSecondhandTreatment(art, 'auction_margin');
|
|
assert.equal(changed.grossSale, art.grossSale);
|
|
assert.equal(changed.ownVat, art.ownVat);
|
|
assert.equal(changed.conditions.compositionConfirmed, '');
|
|
assert.equal(evaluateSecondhand(changed, 'MZN').baseAmount, null);
|
|
const cat = switchSecondhandCategory(art, 'collection');
|
|
assert.equal(cat.editionCount, '200');
|
|
assert.equal(cat.conditions.collectionNature, undefined);
|
|
});
|
|
void test('All8individualgoods persistwithhistory/report;all priorbooks preserved,noaggregateoffset', () => {
|
|
const rows = secondhandTreatments.flatMap((t) =>
|
|
secondhandCategories.map((c) => row(t.id, c.id)),
|
|
);
|
|
let book = recordSecondhands(draft, { rows });
|
|
book = recordSecondhands(draft, book);
|
|
assert.equal(book.history.length, 1);
|
|
const next = {
|
|
...draft,
|
|
modelo_a_snapshot: saveSecondhandBook(draft, book),
|
|
};
|
|
assert.deepEqual(secondhandBook(next), book);
|
|
assert.ok(secondhandCurrent(next, book));
|
|
assert.equal(secondhandCurrent({ ...next, currency: 'USD' }, book), false);
|
|
assert.equal(secondhandReportRows(next).length, 8);
|
|
const store = JSON.parse(next.modelo_a_snapshot);
|
|
delete store.regulatedSecondhands;
|
|
assert.deepEqual(store, JSON.parse(frozen.modelo_a_snapshot));
|
|
assert.throws(() => recordSecondhands(next, { rows: [rows[0], rows[0]] }));
|
|
assert.throws(() =>
|
|
secondhandBook({
|
|
...next,
|
|
modelo_a_snapshot: JSON.stringify({
|
|
...JSON.parse(next.modelo_a_snapshot),
|
|
regulatedSecondhands: [],
|
|
}),
|
|
}),
|
|
);
|
|
if (process.env.IVA_SECONDHAND_FIXTURE)
|
|
fs.writeFileSync(
|
|
process.env.IVA_SECONDHAND_FIXTURE,
|
|
JSON.stringify(next, null, 2) + '\n',
|
|
);
|
|
});
|