212 lines
7.3 KiB
JavaScript
212 lines
7.3 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
acquisitionTreatmentIssue,
|
|
continueWithoutFormerExploration,
|
|
formerExplorationSource,
|
|
} from '../../resources/js/lib/iva-acquisition-continuity.ts';
|
|
import {
|
|
newAcquisition,
|
|
evaluateAcquisition,
|
|
recordAcquisitions,
|
|
acquisitionBook,
|
|
acquisitionCurrent,
|
|
saveAcquisitionBook,
|
|
acquisitionReportRows,
|
|
} from '../../resources/js/lib/iva-acquisitions.ts';
|
|
import { salesCurrent } from '../../resources/js/lib/iva-operations.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
const frozen = JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/iva-g5a-inputs.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
const row = () => ({
|
|
...newAcquisition('g5b-0', '2026-12', 'MZN'),
|
|
field: '10',
|
|
net: '100',
|
|
vat: '16',
|
|
gross: '116',
|
|
documentType: 'invoice',
|
|
documentRef: 'SYNTHETIC QA G5B 001',
|
|
documentDate: '2026-12-10',
|
|
supplier: 'SYNTHETIC QA fornecedor',
|
|
supplierRegistered: 'yes',
|
|
legalDocument: 'yes',
|
|
held: 'yes',
|
|
domestic: 'yes',
|
|
allocation: 'taxable_only',
|
|
exclusion: 'none',
|
|
integrity: 'yes',
|
|
description:
|
|
'SYNTHETIC QA — serviço de pesquisa; antiga referência14(2)(c)',
|
|
acquisitionTreatment: 'former_exploration_exemption',
|
|
});
|
|
function fixture() {
|
|
const draft = {
|
|
...structuredClone(frozen),
|
|
context_opportunity: 'opportunity:329',
|
|
project_title: 'SYNTHETIC QA — G5B / Continuidade da aquisição',
|
|
};
|
|
const prior = recordAcquisitions(draft, {
|
|
currency: 'MZN',
|
|
taxpayerRegistered: 'yes',
|
|
rows: [row()],
|
|
periods: [{ period: '2026-12', complete: true, absent: [] }],
|
|
});
|
|
draft.modelo_a_snapshot = saveAcquisitionBook(draft, prior);
|
|
return { draft, prior };
|
|
}
|
|
void test('2017 rectification prevents former14(2)(c) exemption in2026,including explicit zero;unknown is not a normal treatment', () => {
|
|
assert.match(formerExplorationSource, /BR89/);
|
|
assert.match(
|
|
acquisitionTreatmentIssue('former_exploration_exemption'),
|
|
/eliminada/,
|
|
);
|
|
for (const vat of ['16', '0', '']) {
|
|
const input = { ...row(), vat, gross: vat === '0' ? '100' : '116' },
|
|
result = evaluateAcquisition(input, 'MZN', 'yes');
|
|
assert.equal(result.deductible, null);
|
|
assert.match(result.reason, /Não se aplica nesta cobertura2026/);
|
|
assert.equal(input.net, '100');
|
|
}
|
|
assert.equal(
|
|
evaluateAcquisition(
|
|
{ ...row(), acquisitionTreatment: 'unknown' },
|
|
'MZN',
|
|
'yes',
|
|
).deductible,
|
|
null,
|
|
);
|
|
assert.match(
|
|
evaluateAcquisition({ ...row(), period: '2016-12' }, 'MZN', 'yes')
|
|
.reason,
|
|
/fora do âmbito/,
|
|
);
|
|
});
|
|
void test('explicit continuation keeps values,document,answers and source;normal deduction requires existing conditions', () => {
|
|
const input = row(),
|
|
next = continueWithoutFormerExploration(input);
|
|
for (const key of Object.keys(input).filter(
|
|
(k) => k !== 'acquisitionTreatment',
|
|
))
|
|
assert.deepEqual(next[key], input[key], key);
|
|
assert.equal(input.acquisitionTreatment, 'former_exploration_exemption');
|
|
assert.equal(next.acquisitionTreatment, 'normal');
|
|
assert.equal(
|
|
next.acquisitionTreatmentOrigin.id,
|
|
'former_exploration_exemption',
|
|
);
|
|
assert.equal(evaluateAcquisition(next, 'MZN', 'yes').deductible, '16.00');
|
|
for (const patch of [
|
|
{ held: '' },
|
|
{ allocation: 'mixed' },
|
|
{ legalDocument: '' },
|
|
{ sourceCurrency: 'USD' },
|
|
{ vat: '', gross: '' },
|
|
{ domestic: 'no' },
|
|
{ documentDate: '2026-11-10' },
|
|
])
|
|
assert.equal(
|
|
evaluateAcquisition({ ...next, ...patch }, 'MZN', 'yes').deductible,
|
|
null,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
continueWithoutFormerExploration({
|
|
...input,
|
|
acquisitionTreatment: 'normal',
|
|
}),
|
|
/antiga referência/,
|
|
);
|
|
});
|
|
void test('manual purchase month does not require sales;previous classification remains in history and incomplete totals remain unknown', () => {
|
|
const { draft, prior } = fixture();
|
|
assert.equal(draft.monthly_iva_settings, undefined);
|
|
assert.equal(prior.result.rows[0].deductible, null);
|
|
assert.equal(prior.result.periods[0].fields['10'], null);
|
|
const next = recordAcquisitions(draft, {
|
|
...prior,
|
|
rows: [continueWithoutFormerExploration(prior.rows[0])],
|
|
});
|
|
assert.deepEqual(next.history, [prior.result]);
|
|
assert.equal(next.result.rows[0].deductible, '16.00');
|
|
assert.equal(next.result.periods[0].fields['10'], '16.00');
|
|
for (const f of ['01', '02', '11', '14', '15', '16', '17', '18'])
|
|
assert.equal(next.result.periods[0].fields[f], null, f);
|
|
assert.throws(
|
|
() => recordAcquisitions(draft, { ...next, periods: [] }),
|
|
/meses aplicáveis/,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
recordAcquisitions(draft, {
|
|
...next,
|
|
periods: [...next.periods, ...next.periods],
|
|
}),
|
|
/meses aplicáveis/,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
recordAcquisitions(draft, {
|
|
...next,
|
|
rows: [
|
|
{
|
|
...next.rows[0],
|
|
sourceMonth: 1,
|
|
sourceAmount: '100',
|
|
sourceBasis: 'net',
|
|
},
|
|
],
|
|
}),
|
|
/orçamentos do plano mudaram/,
|
|
);
|
|
});
|
|
void test('save/recovery/report retain rectification,continuation and history;all20 prior sales books stay identical/current', () => {
|
|
const { draft, prior } = fixture();
|
|
const book = recordAcquisitions(draft, {
|
|
...prior,
|
|
rows: [continueWithoutFormerExploration(prior.rows[0])],
|
|
});
|
|
draft.modelo_a_snapshot = saveAcquisitionBook(draft, book);
|
|
assert.ok(acquisitionCurrent(draft, book));
|
|
assert.deepEqual(
|
|
acquisitionBook(JSON.parse(JSON.stringify(draft))),
|
|
JSON.parse(JSON.stringify(book)),
|
|
);
|
|
const before = JSON.parse(frozen.modelo_a_snapshot),
|
|
after = JSON.parse(draft.modelo_a_snapshot);
|
|
assert.equal(Object.keys(before.salesOperations).length, 20);
|
|
assert.deepEqual(after.salesOperations, before.salesOperations);
|
|
assert.deepEqual(Object.keys(after.books), ['opportunity:329']);
|
|
for (const [context, original] of Object.entries(before.salesOperations))
|
|
assert.ok(
|
|
salesCurrent({ ...draft, context_opportunity: context }, original),
|
|
);
|
|
const report = JSON.stringify(
|
|
createProjectDossierReport(draft, {
|
|
selectedSections: new Set(['finance']),
|
|
version: 'QA G5B',
|
|
}),
|
|
);
|
|
for (const token of [
|
|
'opportunity:329',
|
|
'Manica',
|
|
'2026-12',
|
|
'16.00',
|
|
'BR89',
|
|
'Continuidade14(2)(c)',
|
|
'eliminado',
|
|
'expressamente escolhida',
|
|
])
|
|
assert.ok(report.includes(token), token);
|
|
assert.match(
|
|
JSON.stringify(acquisitionReportRows(draft)),
|
|
/antiga isenção/,
|
|
);
|
|
if (process.env.IVA_G5B_CALCULATED)
|
|
fs.writeFileSync(process.env.IVA_G5B_CALCULATED, JSON.stringify(draft));
|
|
});
|