309 lines
10 KiB
JavaScript
309 lines
10 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
newServiceNormal,
|
|
switchServiceTreatment,
|
|
serviceNormalTreatments,
|
|
serviceNormalConditions,
|
|
ownServiceConditions,
|
|
evaluateServiceNormal,
|
|
recordServiceNormals,
|
|
saveServiceNormalBook,
|
|
serviceNormalBook,
|
|
serviceNormalCurrent,
|
|
serviceNormalReportRows,
|
|
} from '../../resources/js/lib/iva-service-normal.ts';
|
|
import { valueReviewCurrent } from '../../resources/js/lib/iva-value-review.ts';
|
|
import { exclusionCurrent } from '../../resources/js/lib/iva-exclusion-base.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
const frozen = JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/iva-g7i-preserved-inputs.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
const row = (treatment = 'free_company_service') => ({
|
|
...newServiceNormal('g7i-' + treatment, 'MZN'),
|
|
treatment,
|
|
description: 'SYNTHETIC QA serviço A',
|
|
period: '2026-12',
|
|
operationRef: 'SYNTHETIC QA operação A',
|
|
purposeRef: 'SYNTHETIC QA finalidade/destinatário ou sector isento A',
|
|
assetRef: 'SYNTHETIC QA bens/elementos A',
|
|
deductionRef: 'SYNTHETIC QA referência dedução anterior parcial A',
|
|
documentRef: 'SYNTHETIC QA G7I ' + treatment,
|
|
documentDate: '2026-12-12',
|
|
normalValue: {
|
|
free_company_service: '500.25',
|
|
private_company_asset_use: '700.50',
|
|
exempt_sector_asset_use: '900.75',
|
|
}[treatment],
|
|
valuationRef: 'SYNTHETIC QA valor normal serviço A15(4)',
|
|
valuationScope:
|
|
'SYNTHETIC QA serviço A,estádio/tempo/lugar/concorrência/fornecedor independente,dezembro2026Manica',
|
|
compositionRef: 'SYNTHETIC QA composição15(4)/(5)/(6),sem próprioIVA',
|
|
evidence: 'SYNTHETIC QA pressupostos expressos,não documento real',
|
|
conditions: Object.fromEntries(
|
|
[...serviceNormalConditions, ...ownServiceConditions(treatment)].map(
|
|
([key]) => [key, 'yes'],
|
|
),
|
|
),
|
|
});
|
|
const draft = () => ({
|
|
...structuredClone(frozen),
|
|
context_opportunity: 'opportunity:347',
|
|
project_title: 'SYNTHETIC QA — G7I / Valor normal de serviços',
|
|
});
|
|
void test('P089 three treatments ownconditions and normalvalue references;prior deduction applies only to assetuse', () => {
|
|
for (const t of serviceNormalTreatments) {
|
|
const input = row(t.id);
|
|
assert.equal(
|
|
evaluateServiceNormal(input, 'MZN').baseAmount,
|
|
input.normalValue,
|
|
);
|
|
assert.equal(evaluateServiceNormal(input, 'MZN').vat, null);
|
|
assert.equal(evaluateServiceNormal(input, 'MZN').rate, null);
|
|
for (const [key] of [
|
|
...serviceNormalConditions,
|
|
...ownServiceConditions(t.id),
|
|
])
|
|
for (const value of ['', 'unknown', 'no']) {
|
|
const r = evaluateServiceNormal(
|
|
{
|
|
...input,
|
|
conditions: { ...input.conditions, [key]: value },
|
|
},
|
|
'MZN',
|
|
);
|
|
assert.equal(
|
|
r.status,
|
|
value === 'no' ? 'not_applicable' : 'indeterminate',
|
|
);
|
|
assert.equal(r.baseAmount, null);
|
|
}
|
|
for (const patch of [
|
|
{ description: '' },
|
|
{ operationRef: '' },
|
|
{ purposeRef: '' },
|
|
{ documentRef: '' },
|
|
{ documentDate: '2026-02-30' },
|
|
{ valuationRef: '' },
|
|
{ valuationScope: '' },
|
|
{ compositionRef: '' },
|
|
{ evidence: '' },
|
|
{ period: '2025-12' },
|
|
{ currency: 'USD' },
|
|
{ treatment: '' },
|
|
{ normalValue: '' },
|
|
])
|
|
assert.equal(
|
|
evaluateServiceNormal({ ...input, ...patch }, 'MZN').baseAmount,
|
|
null,
|
|
);
|
|
if (t.id !== 'free_company_service')
|
|
for (const patch of [{ assetRef: '' }, { deductionRef: '' }])
|
|
assert.equal(
|
|
evaluateServiceNormal({ ...input, ...patch }, 'MZN')
|
|
.baseAmount,
|
|
null,
|
|
);
|
|
}
|
|
const free = row();
|
|
assert.equal(
|
|
evaluateServiceNormal(
|
|
{
|
|
...free,
|
|
assetRef: '',
|
|
deductionRef: '',
|
|
conditions: { ...free.conditions, priorDeduction: 'no' },
|
|
},
|
|
'MZN',
|
|
).baseAmount,
|
|
'500.25',
|
|
);
|
|
});
|
|
void test('normalvalue only,no assetcost/substitution;exactdecimal/zero and switch preserves hiddenassetrefs but clears ownconditions', () => {
|
|
const input = row('private_company_asset_use');
|
|
for (const normalValue of ['0', '1.000002', '999999999999999.999999'])
|
|
assert.equal(
|
|
evaluateServiceNormal({ ...input, normalValue }, 'MZN').baseAmount,
|
|
normalValue === '0' ? '0.00' : normalValue,
|
|
);
|
|
for (const normalValue of ['-1', 'NaN', '0.0000001'])
|
|
assert.equal(
|
|
evaluateServiceNormal({ ...input, normalValue }, 'MZN').baseAmount,
|
|
null,
|
|
);
|
|
const changed = switchServiceTreatment(input, 'free_company_service');
|
|
assert.equal(changed.conditions.freeByCompany, undefined);
|
|
assert.equal(changed.conditions.priorDeduction, undefined);
|
|
for (const field of [
|
|
'assetRef',
|
|
'deductionRef',
|
|
'normalValue',
|
|
'valuationRef',
|
|
'valuationScope',
|
|
'purposeRef',
|
|
])
|
|
assert.equal(changed[field], input[field]);
|
|
assert.equal(evaluateServiceNormal(changed, 'MZN').baseAmount, null);
|
|
assert.equal(
|
|
evaluateServiceNormal(
|
|
{
|
|
...changed,
|
|
conditions: {
|
|
...changed.conditions,
|
|
freeByCompany: 'yes',
|
|
nonBusinessPurpose: 'yes',
|
|
priorDeduction: 'no',
|
|
},
|
|
},
|
|
'MZN',
|
|
).baseAmount,
|
|
'700.50',
|
|
);
|
|
});
|
|
void test('save/recovery/history/report preserve all86 delivered books/currentkeys including P103/P102/P101/P095', () => {
|
|
const d = draft(),
|
|
prior = JSON.parse(d.modelo_a_snapshot);
|
|
assert.deepEqual(serviceNormalBook(d), { rows: [] });
|
|
assert.deepEqual(serviceNormalReportRows(d), []);
|
|
let book = recordServiceNormals(d, {
|
|
rows: serviceNormalTreatments.map((t) => ({
|
|
...row(t.id),
|
|
conditions: {},
|
|
})),
|
|
});
|
|
book = recordServiceNormals(d, {
|
|
...book,
|
|
rows: serviceNormalTreatments.map((t) => ({
|
|
...row(t.id),
|
|
conditions: { ...row(t.id).conditions, domesticOperation: 'no' },
|
|
})),
|
|
});
|
|
book = recordServiceNormals(d, {
|
|
...book,
|
|
rows: serviceNormalTreatments.map((t) => row(t.id)),
|
|
});
|
|
assert.equal(book.history.length, 2);
|
|
assert.ok(
|
|
book.history.every((h) =>
|
|
h.rows.every((r) => r.baseAmount === null && r.vat === null),
|
|
),
|
|
);
|
|
assert.ok(serviceNormalCurrent(d, book));
|
|
for (const field of [
|
|
'treatment',
|
|
'normalValue',
|
|
'valuationScope',
|
|
'deductionRef',
|
|
'assetRef',
|
|
'purposeRef',
|
|
])
|
|
assert.ok(
|
|
!serviceNormalCurrent(d, {
|
|
...book,
|
|
rows: book.rows.map((r) => ({ ...r, [field]: 'changed' })),
|
|
}),
|
|
);
|
|
for (const patch of [
|
|
{ context_opportunity: 'opportunity:348' },
|
|
{ currency: 'USD' },
|
|
{ selection_labels: { province_id: 'Maputo' } },
|
|
])
|
|
assert.ok(!serviceNormalCurrent({ ...d, ...patch }, book));
|
|
d.modelo_a_snapshot = saveServiceNormalBook(d, book);
|
|
assert.deepEqual(serviceNormalBook(JSON.parse(JSON.stringify(d))), book);
|
|
const next = JSON.parse(d.modelo_a_snapshot);
|
|
const { serviceNormals: _serviceNormals, ...untouched } = next;
|
|
assert.deepEqual(untouched, prior);
|
|
for (const [scope, previous] of Object.entries(prior.valueReviews))
|
|
assert.ok(
|
|
valueReviewCurrent({ ...d, context_opportunity: scope }, previous),
|
|
);
|
|
for (const [scope, previous] of Object.entries(
|
|
prior.considerationExclusions,
|
|
))
|
|
assert.ok(
|
|
exclusionCurrent({ ...d, context_opportunity: scope }, previous),
|
|
);
|
|
assert.equal(
|
|
next.valueReviews['opportunity:346'].result.rows[1].baseAmount,
|
|
'1400.75',
|
|
);
|
|
assert.equal(
|
|
next.considerationExclusions['opportunity:345'].result.rows[0]
|
|
.baseAmount,
|
|
'799.70',
|
|
);
|
|
assert.equal(
|
|
next.regulatedFuelBases['opportunity:342'].result.rows[0].baseAmount,
|
|
'1400.15',
|
|
);
|
|
assert.equal(
|
|
next.books['opportunity:329'].result.periods[0].fields['11'],
|
|
null,
|
|
);
|
|
const report = JSON.stringify(
|
|
createProjectDossierReport(d, {
|
|
selectedSections: new Set(['finance']),
|
|
version: 'QA G7I',
|
|
}),
|
|
);
|
|
for (const token of [
|
|
'Base P089',
|
|
'500.25',
|
|
'700.50',
|
|
'900.75',
|
|
'IVA: Indeterminado',
|
|
'opportunity:347',
|
|
'Manica',
|
|
'2026-12',
|
|
'15(2)(c)',
|
|
'4(2)(a)',
|
|
'dedução anterior parcial A',
|
|
'campo11',
|
|
])
|
|
assert.ok(report.includes(token), token);
|
|
assert.deepEqual(
|
|
serviceNormalReportRows({
|
|
...d,
|
|
context_opportunity: 'opportunity:348',
|
|
}),
|
|
[],
|
|
);
|
|
if (process.env.IVA_G7I_CALCULATED)
|
|
fs.writeFileSync(process.env.IVA_G7I_CALCULATED, JSON.stringify(d));
|
|
});
|
|
void test('corrupt service rows/store cannot overwrite originals;duplicateoperationids cannotbe recorded', () => {
|
|
const d = draft(),
|
|
original = d.modelo_a_snapshot;
|
|
for (const patch of [
|
|
{ normalValue: 4 },
|
|
{ conditions: [] },
|
|
{ deductionRef: null },
|
|
])
|
|
assert.throws(
|
|
() => saveServiceNormalBook(d, { rows: [{ ...row(), ...patch }] }),
|
|
/originais preservados/,
|
|
);
|
|
assert.equal(d.modelo_a_snapshot, original);
|
|
assert.throws(
|
|
() => recordServiceNormals(d, { rows: [row(), row()] }),
|
|
/distintos/,
|
|
);
|
|
assert.throws(() => recordServiceNormals(d, { rows: [] }), /distintos/);
|
|
assert.throws(
|
|
() =>
|
|
serviceNormalBook({
|
|
...d,
|
|
modelo_a_snapshot: JSON.stringify({
|
|
...JSON.parse(original),
|
|
serviceNormals: [],
|
|
}),
|
|
}),
|
|
/originais preservados/,
|
|
);
|
|
});
|