282 lines
10 KiB
JavaScript
282 lines
10 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import {
|
|
operationTreatments,
|
|
newSalesOperation,
|
|
calculateSales,
|
|
recordSales,
|
|
salesBaseFields,
|
|
salesBaseMembership,
|
|
salesBook,
|
|
salesCurrent,
|
|
salesReportRows,
|
|
saveSalesBook,
|
|
modeloA06Correspondence,
|
|
} from '../../resources/js/lib/iva-operations.ts';
|
|
import {
|
|
calculateAcquisitions,
|
|
recordAcquisitions,
|
|
saveAcquisitionBook,
|
|
acquisitionBook,
|
|
acquisitionCurrent,
|
|
} from '../../resources/js/lib/iva-acquisitions.ts';
|
|
import { modeloAFields } from '../../resources/js/lib/iva-modelo-a.ts';
|
|
const seed = JSON.parse(
|
|
fs.readFileSync(
|
|
new URL('./fixtures/iva-l3e-preserved.json', import.meta.url),
|
|
'utf8',
|
|
),
|
|
);
|
|
function fixture() {
|
|
const d = structuredClone(seed);
|
|
d.context_opportunity = 'opportunity:375';
|
|
d.project_title = 'SYNTHETIC QA — P001 correspondência05/06';
|
|
const choices = [
|
|
['general', '100'],
|
|
['private_health', '200'],
|
|
['export_goods', '300'],
|
|
['housing_rent', '400'],
|
|
];
|
|
const b = {
|
|
currency: 'MZN',
|
|
rows: choices.map(([treatment, amount], index) => {
|
|
const rule = operationTreatments.find((t) => t.id === treatment);
|
|
return {
|
|
...newSalesOperation(`p001-${index}`, '2026-01', 'MZN'),
|
|
description: `SYNTHETIC QA ${treatment}`,
|
|
establishment: 'SYNTHETIC QA estabelecimento',
|
|
amount,
|
|
basis: 'net',
|
|
treatment,
|
|
confirmed: rule.conditions.map((_, i) => i),
|
|
evidence:
|
|
'SYNTHETIC QA pressupostos do tratamento,sem declaração submetida',
|
|
};
|
|
}),
|
|
periods: [{ period: '2026-01', complete: true }],
|
|
};
|
|
const a = {
|
|
currency: 'MZN',
|
|
taxpayerRegistered: 'yes',
|
|
rows: [],
|
|
periods: [
|
|
{ period: '2026-01', complete: true, absent: ['11', '12', '13'] },
|
|
],
|
|
};
|
|
return { d, b, a };
|
|
}
|
|
function recorded() {
|
|
const f = fixture();
|
|
f.b = recordSales(f.d, f.b, '2026-10-25T16:00:00Z');
|
|
f.d.modelo_a_snapshot = saveSalesBook(f.d, f.b);
|
|
f.a = recordAcquisitions(f.d, f.a, '2026-10-25T16:00:01Z');
|
|
f.d.modelo_a_snapshot = saveAcquisitionBook(f.d, f.a);
|
|
return f;
|
|
}
|
|
void test('whole current catalogue has exclusive field mappings;05and07 keep treatment references;no foreign=06 or invented06 treatment', () => {
|
|
assert.equal(
|
|
new Set(operationTreatments.map((t) => t.id)).size,
|
|
operationTreatments.length,
|
|
);
|
|
assert.ok(operationTreatments.filter((t) => t.field === '05').length > 0);
|
|
assert.ok(operationTreatments.filter((t) => t.field === '07').length > 0);
|
|
assert.equal(operationTreatments.filter((t) => t.field === '06').length, 0);
|
|
for (const rule of operationTreatments) {
|
|
assert.ok(
|
|
rule.field === null || salesBaseFields.includes(rule.field),
|
|
rule.id,
|
|
);
|
|
if (rule.field === '05' || rule.field === '07') {
|
|
assert.ok(rule.source);
|
|
assert.ok(rule.article);
|
|
}
|
|
}
|
|
assert.equal(
|
|
operationTreatments.find((t) => t.id === 'foreign_operation').field,
|
|
null,
|
|
);
|
|
assert.equal(
|
|
operationTreatments.find((t) => t.id === 'foreign_operation').rate,
|
|
null,
|
|
);
|
|
});
|
|
void test('implemented classified operations feed05or07once and14sums01+03+05+06+07 without duplicating the export300', () => {
|
|
const { d, b, a } = recorded(),
|
|
p = b.result.periods[0].fields;
|
|
assert.deepEqual(
|
|
Object.fromEntries(
|
|
['01', '03', '05', '06', '07'].map((f) => [f, p[f]]),
|
|
),
|
|
{
|
|
'01': '100.00',
|
|
'03': '200.00',
|
|
'05': '300.00',
|
|
'06': '0.00',
|
|
'07': '400.00',
|
|
},
|
|
);
|
|
assert.equal(a.result.periods[0].fields['14'], '1000.00');
|
|
assert.doesNotMatch(
|
|
a.result.periods[0].reasons.join(' '),
|
|
/01\/02: vendas ou IVA mensal em falta/,
|
|
);
|
|
const membership = salesBaseMembership(b.result.rows);
|
|
assert.equal(membership['05'][0].id, 'p001-2');
|
|
assert.equal(membership['06'].length, 0);
|
|
assert.equal(Object.values(membership).flat().length, b.rows.length);
|
|
assert.ok(salesCurrent(d, salesBook(d)));
|
|
assert.ok(acquisitionCurrent(d, acquisitionBook(d)));
|
|
});
|
|
void test('mechanical aggregation rejects one operation in05and06or twice in05;distinct06probe is not a new tax classification', () => {
|
|
const { b } = recorded(),
|
|
r = b.result.rows.find((r) => r.field === '05');
|
|
assert.throws(
|
|
() => salesBaseMembership([r, { ...r, field: '06' }]),
|
|
/Mesma operação/,
|
|
);
|
|
assert.throws(() => salesBaseMembership([r, { ...r }]), /Mesma operação/);
|
|
const grouped = salesBaseMembership([
|
|
r,
|
|
{ ...r, id: 'aggregation-only-06', field: '06', net: '50.00' },
|
|
]);
|
|
assert.deepEqual(
|
|
grouped['05'].map((r) => r.net),
|
|
['300.00'],
|
|
);
|
|
assert.deepEqual(
|
|
grouped['06'].map((r) => r.net),
|
|
['50.00'],
|
|
);
|
|
const f = fixture();
|
|
f.b.rows.push({ ...f.b.rows[2], treatment: 'foreign_operation' });
|
|
assert.throws(() => calculateSales(f.d, f.b), /duplicados/);
|
|
});
|
|
void test('foreign or unknown operation never becomes06orzero;declared absence cannot override that unknown in14', () => {
|
|
for (const treatment of ['foreign_operation', 'other']) {
|
|
const { d, b, a } = fixture();
|
|
b.rows[2].treatment = treatment;
|
|
const value = recordSales(d, b);
|
|
assert.equal(value.result.rows[2].field, null);
|
|
assert.equal(value.result.periods[0].fields['06'], null);
|
|
d.modelo_a_snapshot = saveSalesBook(d, value);
|
|
a.periods[0].absent.push('06');
|
|
assert.equal(calculateAcquisitions(d, a).periods[0].fields['06'], null);
|
|
assert.equal(calculateAcquisitions(d, a).periods[0].fields['14'], null);
|
|
}
|
|
});
|
|
void test('06zero needs complete classified scope;incomplete scope stays unknown;exact included5percent remains exact in14', () => {
|
|
const { d, b, a } = fixture();
|
|
b.periods[0].complete = false;
|
|
assert.equal(calculateSales(d, b).periods[0].fields['06'], null);
|
|
b.periods[0].complete = true;
|
|
b.rows[1].basis = 'included';
|
|
b.rows[1].amount = '100';
|
|
const calc = recordSales(d, b);
|
|
d.modelo_a_snapshot = saveSalesBook(d, calc);
|
|
const totals = calculateAcquisitions(d, a);
|
|
assert.deepEqual(totals.periods[0].exactFields['14'], {
|
|
numerator: '18800',
|
|
denominator: '21',
|
|
});
|
|
assert.deepEqual(calc.result.periods[0].exactFields['06'], {
|
|
numerator: '0',
|
|
denominator: '1',
|
|
});
|
|
assert.equal(totals.periods[0].fields['14'], '895.238095');
|
|
});
|
|
void test('deliberate edits do not rewrite05/06history;save/recovery preserves every prior map and report uses frozen inputs', () => {
|
|
let { d, b, a } = recorded();
|
|
const prior = JSON.parse(seed.modelo_a_snapshot),
|
|
first = structuredClone(b.result);
|
|
b.rows[2].description = 'Deliberate edit after calculation';
|
|
d.modelo_a_snapshot = saveSalesBook(d, b);
|
|
assert.equal(salesCurrent(d, salesBook(d)), false);
|
|
assert.doesNotMatch(
|
|
salesReportRows(d).flat().join(' '),
|
|
/Deliberate edit after calculation/,
|
|
);
|
|
b = recordSales(d, b);
|
|
assert.deepEqual(b.history.at(-1), first);
|
|
d.modelo_a_snapshot = saveSalesBook(d, b);
|
|
const recovered = JSON.parse(JSON.stringify(d));
|
|
assert.deepEqual(salesBook(recovered), b);
|
|
const current = JSON.parse(recovered.modelo_a_snapshot);
|
|
for (const [k, v] of Object.entries(prior))
|
|
assert.deepEqual(
|
|
k === 'version'
|
|
? current[k]
|
|
: Object.fromEntries(
|
|
Object.keys(v).map((key) => [key, current[k][key]]),
|
|
),
|
|
v,
|
|
k,
|
|
);
|
|
assert.equal(a.result.periods[0].fields['14'], '1000.00');
|
|
});
|
|
void test('confirmed form labels replace the generic model-search blocker without changing foreign semantics', () => {
|
|
assert.match(
|
|
modeloA06Correspondence,
|
|
/Correspondência confirmada pela Tempo/,
|
|
);
|
|
assert.match(modeloA06Correspondence, /Campo14=01\+03\+05\+06\+07/);
|
|
assert.doesNotMatch(
|
|
modeloA06Correspondence,
|
|
/Não há correspondência unívoca|formulário antigo/,
|
|
);
|
|
assert.equal(
|
|
modeloAFields.find((f) => f.field === '05').purpose,
|
|
'Vendas isentas com direito à dedução',
|
|
);
|
|
assert.equal(
|
|
modeloAFields.find((f) => f.field === '07').purpose,
|
|
'Operações que não conferem direito à dedução',
|
|
);
|
|
assert.match(
|
|
modeloAFields.find((f) => f.field === '06').purpose,
|
|
/artigo18/,
|
|
);
|
|
});
|
|
void test('export guarded API and component proof plus full current treatment-to-field inventory', () => {
|
|
const { d, b, a } = recorded();
|
|
assert.equal(b.result.periods[0].fields['05'], '300.00');
|
|
assert.equal(a.result.periods[0].fields['14'], '1000.00');
|
|
if (process.env.P001_RECONCILIATION_OUTPUT) {
|
|
fs.writeFileSync(
|
|
process.env.P001_RECONCILIATION_OUTPUT,
|
|
JSON.stringify(d),
|
|
);
|
|
fs.writeFileSync(
|
|
process.env.P001_RECONCILIATION_OUTPUT.replace(
|
|
'calculated.json',
|
|
'treatment-field-inventory.json',
|
|
),
|
|
JSON.stringify(
|
|
{
|
|
origin: 'User-confirmed received form;implemented catalogue only',
|
|
labels: Object.fromEntries(
|
|
modeloAFields
|
|
.filter((f) =>
|
|
['05', '06', '07', '14'].includes(f.field),
|
|
)
|
|
.map((f) => [f.field, f.purpose]),
|
|
),
|
|
treatments: operationTreatments.map(
|
|
({ id, label, field, article, source }) => ({
|
|
id,
|
|
label,
|
|
field,
|
|
article,
|
|
source,
|
|
}),
|
|
),
|
|
field06_treatments: [],
|
|
limit: 'No new positive06tax classification inferred;mechanical duplicate guards separately tested.',
|
|
},
|
|
null,
|
|
2,
|
|
),
|
|
);
|
|
}
|
|
});
|