297 lines
9.6 KiB
JavaScript
297 lines
9.6 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
newSalesOperation,
|
|
recordSales,
|
|
saveSalesBook,
|
|
salesBook,
|
|
operationTreatments,
|
|
} from '../../resources/js/lib/iva-operations.ts';
|
|
import {
|
|
newAcquisition,
|
|
evaluateAcquisition,
|
|
recordAcquisitions,
|
|
saveAcquisitionBook,
|
|
acquisitionBook,
|
|
acquisitionCurrent,
|
|
acquisitionReportRows,
|
|
readAcquisitionStore,
|
|
} from '../../resources/js/lib/iva-acquisitions.ts';
|
|
import {
|
|
createAcquisitionRightLink,
|
|
linkedOperationRight,
|
|
} from '../../resources/js/lib/iva-acquisition-right.ts';
|
|
const frozen = JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/iva-l2a-preserved.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
export function rightFixture() {
|
|
const draft = {
|
|
...structuredClone(frozen),
|
|
context_opportunity: 'opportunity:368',
|
|
project_title: 'SYNTHETIC QA — L2-A aquisição e direito',
|
|
};
|
|
const types = [
|
|
'general',
|
|
'export_goods',
|
|
'listed_maize',
|
|
'housing_rent',
|
|
'private_health',
|
|
];
|
|
const operations = {
|
|
currency: 'MZN',
|
|
periods: [{ period: '2026-12', complete: true }],
|
|
rows: types.map((t, i) => ({
|
|
...newSalesOperation('operation-' + i, '2026-12', 'MZN'),
|
|
description: 'SYNTHETIC QA ' + t,
|
|
amount: '100',
|
|
basis: 'net',
|
|
establishment: 'Mozambique',
|
|
treatment: t,
|
|
confirmed: operationTreatments
|
|
.find((x) => x.id === t)
|
|
.conditions.map((_, j) => j),
|
|
evidence: 'SYNTHETIC QA fonte e condições',
|
|
})),
|
|
};
|
|
draft.modelo_a_snapshot = saveSalesBook(
|
|
draft,
|
|
recordSales(draft, operations),
|
|
);
|
|
const rows = types.map((_, i) => ({
|
|
...newAcquisition('purchase-' + i, '2026-12', 'MZN'),
|
|
net: '100',
|
|
vat: '16',
|
|
gross: '116',
|
|
description: 'SYNTHETIC QA compra ' + i,
|
|
documentType: 'invoice',
|
|
documentRef: 'SYNTHETIC QA ' + i,
|
|
documentDate: '2026-12-10',
|
|
supplier: 'SYNTHETIC QA fornecedor',
|
|
supplierRegistered: 'yes',
|
|
legalDocument: 'yes',
|
|
held: 'yes',
|
|
domestic: 'yes',
|
|
allocation: 'taxable_only',
|
|
exclusion: 'none',
|
|
integrity: 'yes',
|
|
deductionLink: {
|
|
...createAcquisitionRightLink(draft, 'operation-' + i),
|
|
scopeConfirmed: 'yes',
|
|
allocationRef: 'Afectação exclusiva SYNTHETIC QA ' + i,
|
|
noExcludedPayments: 'yes',
|
|
},
|
|
}));
|
|
return {
|
|
draft,
|
|
book: {
|
|
currency: 'MZN',
|
|
taxpayerRegistered: 'yes',
|
|
rows,
|
|
periods: [
|
|
{
|
|
period: '2026-12',
|
|
complete: true,
|
|
absent: ['11', '12', '13'],
|
|
},
|
|
],
|
|
},
|
|
};
|
|
}
|
|
void test('right follows classified operation, including exempt with right and taxed17-A without right', () => {
|
|
const { draft, book } = rightFixture();
|
|
const results = book.rows.map((r) =>
|
|
evaluateAcquisition(r, 'MZN', 'yes', draft),
|
|
);
|
|
assert.deepEqual(
|
|
results.map((r) => r.eligibility.status),
|
|
['eligible', 'eligible', 'eligible', 'no_right', 'no_right'],
|
|
);
|
|
assert.deepEqual(
|
|
results.map((r) => r.deductible),
|
|
['16.00', '16.00', '16.00', '0.00', '0.00'],
|
|
);
|
|
for (const r of results) {
|
|
assert.ok(r.eligibility.reason);
|
|
assert.match(r.eligibility.source, /Lei10\/2025/);
|
|
}
|
|
assert.match(results[4].reason, /17-A/);
|
|
});
|
|
void test('own documentary conditions, missing data, imports and mixed use remain distinct from operation right', () => {
|
|
const { draft, book } = rightFixture(),
|
|
row = book.rows[0];
|
|
for (const patch of [
|
|
{ held: '' },
|
|
{ documentRef: '' },
|
|
{ documentDate: '2026-11-10' },
|
|
{ allocation: 'mixed' },
|
|
{ domestic: 'no' },
|
|
{ exclusion: 'special' },
|
|
{ sourceCurrency: 'USD' },
|
|
{ vat: '0', gross: '100', held: '' },
|
|
{ sourceMonth: 1, sourceAmount: '999', sourceBasis: 'net' },
|
|
]) {
|
|
const r = evaluateAcquisition(
|
|
{ ...row, ...patch },
|
|
'MZN',
|
|
'yes',
|
|
draft,
|
|
);
|
|
assert.equal(
|
|
r.eligibility.status,
|
|
'indeterminate',
|
|
JSON.stringify(patch),
|
|
);
|
|
assert.equal(r.deductible, null);
|
|
}
|
|
for (const patch of [
|
|
{ exclusion: 'excluded' },
|
|
{ supplierRegistered: 'no' },
|
|
{ integrity: 'no' },
|
|
])
|
|
assert.equal(
|
|
evaluateAcquisition({ ...row, ...patch }, 'MZN', 'yes', draft)
|
|
.eligibility.status,
|
|
'no_right',
|
|
);
|
|
assert.equal(
|
|
evaluateAcquisition(
|
|
{
|
|
...row,
|
|
deductionLink: { ...row.deductionLink, scopeConfirmed: '' },
|
|
},
|
|
'MZN',
|
|
'yes',
|
|
draft,
|
|
).deductible,
|
|
null,
|
|
);
|
|
assert.equal(
|
|
evaluateAcquisition(
|
|
{
|
|
...row,
|
|
deductionLink: {
|
|
...row.deductionLink,
|
|
noExcludedPayments: 'no',
|
|
},
|
|
},
|
|
'MZN',
|
|
'yes',
|
|
draft,
|
|
).deductible,
|
|
'0.00',
|
|
);
|
|
assert.equal(evaluateAcquisition(row, 'MZN', 'yes').deductible, null);
|
|
});
|
|
void test('totals F include only eligible amounts; unknown never becomes zero; no duplicated projected/effective invoice', () => {
|
|
const { draft, book } = rightFixture();
|
|
const result = recordAcquisitions(draft, book).result;
|
|
assert.equal(result.periods[0].fields['09'], '48.00');
|
|
assert.equal(result.periods[0].fields['15'], '48.00');
|
|
assert.deepEqual(
|
|
['14', '15', '16', '17', '18'].map((f) => result.periods[0].fields[f]),
|
|
['500.00', '48.00', '21.00', '0.00', '27.00'],
|
|
);
|
|
const cleared = structuredClone(book);
|
|
cleared.rightVersion = 'acquisition-operation-right/1';
|
|
cleared.rows = cleared.rows.map(
|
|
({ deductionLink: _deductionLink, ...row }) => row,
|
|
);
|
|
const empty = recordAcquisitions(draft, cleared).result;
|
|
assert.ok(
|
|
empty.rows.every((r) => r.eligibility.status === 'indeterminate'),
|
|
);
|
|
assert.equal(empty.periods[0].fields['15'], null);
|
|
const unknown = structuredClone(book);
|
|
unknown.rows[0].held = '';
|
|
assert.equal(
|
|
recordAcquisitions(draft, unknown).result.periods[0].fields['15'],
|
|
null,
|
|
);
|
|
const duplicate = structuredClone(book);
|
|
duplicate.rows.push({
|
|
...duplicate.rows[0],
|
|
id: 'copy',
|
|
origin: 'effective',
|
|
});
|
|
assert.throws(() => recordAcquisitions(draft, duplicate), /dupla dedução/);
|
|
});
|
|
void test('source edits require explicit adoption; save recovery report and historical versions preserve old books byte for byte', () => {
|
|
let { draft, book } = rightFixture();
|
|
const before = JSON.parse(frozen.modelo_a_snapshot);
|
|
book = recordAcquisitions(draft, book);
|
|
draft.modelo_a_snapshot = saveAcquisitionBook(draft, book);
|
|
const first = structuredClone(book.result);
|
|
book = recordAcquisitions(draft, book);
|
|
draft.modelo_a_snapshot = saveAcquisitionBook(draft, book);
|
|
const recovered = JSON.parse(JSON.stringify(draft));
|
|
assert.deepEqual(acquisitionBook(recovered), book);
|
|
assert.ok(acquisitionCurrent(recovered, book));
|
|
assert.deepEqual(book.history, [first]);
|
|
const after = JSON.parse(draft.modelo_a_snapshot);
|
|
for (const key of Object.keys(before)) {
|
|
if (key === 'version') continue;
|
|
for (const [id, value] of Object.entries(before[key]))
|
|
assert.deepEqual(after[key][id], value, key + id);
|
|
}
|
|
const report = JSON.stringify(acquisitionReportRows(recovered));
|
|
for (const word of [
|
|
'Elegível',
|
|
'Sem direito',
|
|
'Lei10/2025',
|
|
'48.00',
|
|
'operation-4',
|
|
])
|
|
assert.ok(report.includes(word), word);
|
|
const ops = salesBook(draft);
|
|
ops.rows[0].description = 'Alteração deliberada';
|
|
draft.modelo_a_snapshot = saveSalesBook(draft, recordSales(draft, ops));
|
|
assert.equal(acquisitionCurrent(draft, book), false);
|
|
assert.equal(
|
|
linkedOperationRight(draft, book.rows[0].deductionLink).status,
|
|
'indeterminate',
|
|
);
|
|
assert.deepEqual(book.history[0], first);
|
|
assert.deepEqual(
|
|
book.result,
|
|
recovered ? acquisitionBook(recovered).result : null,
|
|
);
|
|
const adopted = createAcquisitionRightLink(
|
|
draft,
|
|
book.rows[0].deductionLink.operationId,
|
|
);
|
|
assert.equal(adopted.scopeConfirmed, '');
|
|
assert.equal(adopted.allocationRef, '');
|
|
assert.equal(linkedOperationRight(draft, adopted).status, 'eligible');
|
|
assert.equal(
|
|
evaluateAcquisition(
|
|
{ ...book.rows[0], deductionLink: adopted },
|
|
'MZN',
|
|
'yes',
|
|
draft,
|
|
).deductible,
|
|
null,
|
|
);
|
|
assert.equal(
|
|
linkedOperationRight(
|
|
{ ...draft, context_opportunity: 'opportunity:329' },
|
|
adopted,
|
|
).status,
|
|
'indeterminate',
|
|
);
|
|
const malformed = JSON.parse(draft.modelo_a_snapshot);
|
|
malformed.books['opportunity:368'].rows[0].deductionLink.version = 9;
|
|
assert.throws(
|
|
() => readAcquisitionStore(JSON.stringify(malformed)),
|
|
/Vínculo/,
|
|
);
|
|
if (process.env.IVA_L2A_CALCULATED)
|
|
fs.writeFileSync(
|
|
process.env.IVA_L2A_CALCULATED,
|
|
JSON.stringify(recovered),
|
|
);
|
|
});
|