234 lines
8.0 KiB
JavaScript
234 lines
8.0 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import {
|
|
monthlyProjectedSales,
|
|
forecastBusinessPlan,
|
|
serializeBusinessPlanSnapshot,
|
|
} from '../../resources/js/lib/business-financial-plan.ts';
|
|
import {
|
|
monthlyIvaInput,
|
|
monthlyIvaKey,
|
|
monthlySalesFromDraft,
|
|
readMonthlyIvaRecord,
|
|
monthlyIvaCashTreatment,
|
|
} from '../../resources/js/lib/monthly-iva.ts';
|
|
import {
|
|
partitionSimulationDraft,
|
|
restoreSimulationDraft,
|
|
readArithmeticSnapshot,
|
|
} from '../../resources/js/lib/simulation-form.ts';
|
|
import { createProjectDossierReport } from '../../resources/js/lib/project-dossier-report.ts';
|
|
|
|
function plan() {
|
|
const fields = [
|
|
'openingCash',
|
|
'monthlyCollections',
|
|
'monthlyVariableCostPayments',
|
|
'monthlyInventoryPurchases',
|
|
'monthlyOperatingExpenses',
|
|
'monthlyOperatingPayments',
|
|
'monthlyPayrollExpense',
|
|
'monthlyPayrollPayments',
|
|
'assetInvestment',
|
|
'inventory',
|
|
'receivables',
|
|
'payables',
|
|
'equity',
|
|
'shareholderLoans',
|
|
'bankLoanProceeds',
|
|
'grants',
|
|
'assetDepreciation',
|
|
'monthlyInterestExpense',
|
|
'monthlyInterestPayment',
|
|
'monthlyPrincipalRepayment',
|
|
];
|
|
return {
|
|
...Object.fromEntries(fields.map((key) => [key, '0'])),
|
|
currency: 'MZN',
|
|
periods: 4,
|
|
depreciationLifeMonths: 60,
|
|
products: [
|
|
{
|
|
label: 'Produto QA',
|
|
monthlyUnits: '1',
|
|
unitPrice: '1000.03',
|
|
unitVariableCost: '0',
|
|
monthlyUnitsByPeriod: ['1', '2', '0', '3'],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
const settings = { start: '2026-01', basis: 'net', declared: true, auto: true };
|
|
function draft(value = plan()) {
|
|
return {
|
|
currency: 'MZN',
|
|
business_plan_snapshot: serializeBusinessPlanSnapshot(value),
|
|
monthly_iva_settings: JSON.stringify(settings),
|
|
};
|
|
}
|
|
function snapshot(input) {
|
|
return {
|
|
contract_version: 1,
|
|
scenario_kind: 'fiscal_rule_published',
|
|
base: null,
|
|
currency: 'MZN',
|
|
indicative_total: null,
|
|
legal_interpretation: false,
|
|
formula_version: 'investmoz.tax.v1',
|
|
context: {
|
|
reference_date: '2026-01-01',
|
|
currency: 'MZN',
|
|
currency_fraction_digits: 2,
|
|
scenario_id: '61000100-0000-4000-8000-000000000099',
|
|
scenario_version: 1,
|
|
engine_version: 'fiscal-published-v1',
|
|
ruleset_version: 'IVA-GERAL-BASE-LIQUIDA:1',
|
|
},
|
|
result: {
|
|
status: 'indeterminate',
|
|
value: null,
|
|
unit: 'MZN',
|
|
explanation: 'IVA mensal; não é imposto a pagar.',
|
|
missing_inputs: ['monthly_sales'],
|
|
source_ids: ['qa-source'],
|
|
},
|
|
presentation: { value: null, rounding_applied: false },
|
|
fiscal_provenance: {
|
|
rule: {
|
|
rule_code: 'IVA-GERAL-BASE-LIQUIDA',
|
|
rule_version: 1,
|
|
valid_from: '2023-01-01',
|
|
valid_until: null,
|
|
source: { diploma: 'Fonte sintética QA', article: 'Teste' },
|
|
},
|
|
scope_status: 'user_declared',
|
|
},
|
|
monthly_projection: {
|
|
input,
|
|
cash_treatment: monthlyIvaCashTreatment,
|
|
rows: [
|
|
{
|
|
date: '2026-01-01',
|
|
sales_declared: '1000.03',
|
|
net_sales: '1000.03',
|
|
rate: '16',
|
|
vat: '160.0048',
|
|
gross_sales: '1160.0348',
|
|
status: 'calculated',
|
|
},
|
|
{
|
|
date: '2026-02-01',
|
|
sales_declared: '0',
|
|
net_sales: '0',
|
|
rate: '16',
|
|
vat: '0',
|
|
gross_sales: '0',
|
|
status: 'calculated',
|
|
},
|
|
{
|
|
date: '2026-03-01',
|
|
sales_declared: null,
|
|
net_sales: null,
|
|
rate: null,
|
|
vat: null,
|
|
gross_sales: null,
|
|
status: 'missing_sales',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
}
|
|
void test('monthly IVA reuses exactly the existing per-product sales projection without repeating entry', () => {
|
|
const value = plan();
|
|
assert.deepEqual(
|
|
monthlyProjectedSales(value),
|
|
forecastBusinessPlan(value).rows.map((row) => row.revenue),
|
|
);
|
|
assert.deepEqual(monthlySalesFromDraft(draft()).amounts, [
|
|
'1000.03',
|
|
'2000.06',
|
|
'0.00',
|
|
'3000.09',
|
|
]);
|
|
});
|
|
void test('a missing month remains unknown while an explicit zero and other months stay available', () => {
|
|
const value = plan();
|
|
value.products[0].monthlyUnitsByPeriod = ['1', '', '0', '2'];
|
|
assert.deepEqual(monthlyProjectedSales(value), [
|
|
'1000.03',
|
|
null,
|
|
'0.00',
|
|
'2000.06',
|
|
]);
|
|
const input = monthlyIvaInput(draft(value), settings);
|
|
assert.equal(input.monthly_sales[1].amount, null);
|
|
assert.equal(input.monthly_sales[2].amount, '0.00');
|
|
assert.equal('rate' in input, false);
|
|
assert.equal('monthlyTaxAssessed' in input, false);
|
|
});
|
|
void test('gross basis is preserved and old fiscal results become stale when sales, currency or basis change', () => {
|
|
const value = draft();
|
|
const initial = monthlyIvaInput(value, settings);
|
|
const frozen = JSON.stringify(initial);
|
|
const gross = monthlyIvaInput(value, { ...settings, basis: 'included' });
|
|
assert.equal(gross.monthly_sales[0].amount, '1000.03');
|
|
assert.notEqual(monthlyIvaKey(gross), monthlyIvaKey(initial));
|
|
const changed = plan();
|
|
changed.products[0].monthlyUnitsByPeriod[0] = '2';
|
|
assert.notEqual(
|
|
monthlyIvaKey(monthlyIvaInput(draft(changed), settings)),
|
|
monthlyIvaKey(initial),
|
|
);
|
|
assert.equal(JSON.stringify(initial), frozen);
|
|
assert.throws(
|
|
() => monthlyIvaInput(value, { ...settings, start: '2026-12' }),
|
|
/cobertura/,
|
|
);
|
|
});
|
|
void test('monthly recovery and finance report preserve exact values and mark stale history without recomputing VAT', () => {
|
|
const value = plan();
|
|
value.products[0].monthlyUnitsByPeriod = ['1', '0', '', '2'];
|
|
const original = draft(value);
|
|
const input = monthlyIvaInput(original, settings);
|
|
const result = snapshot(input);
|
|
original.monthly_iva_snapshot = JSON.stringify({
|
|
input,
|
|
execution: {
|
|
id: '61000100-0000-4000-8000-000000000077',
|
|
status: 'completed',
|
|
version_no: 2,
|
|
result_snapshot: result,
|
|
},
|
|
});
|
|
const partition = partitionSimulationDraft(original);
|
|
const recovered = restoreSimulationDraft(
|
|
partition.economic_snapshot,
|
|
partition.personal_snapshot,
|
|
);
|
|
assert.equal(recovered.monthly_iva_snapshot, original.monthly_iva_snapshot);
|
|
assert.equal(
|
|
readMonthlyIvaRecord(recovered.monthly_iva_snapshot).execution
|
|
.result_snapshot.monthly_projection.rows[0].vat,
|
|
'160.0048',
|
|
);
|
|
assert.equal(readArithmeticSnapshot(result).integrity, 'versioned');
|
|
let report = createProjectDossierReport(recovered, {
|
|
selectedSections: new Set(['finance']),
|
|
});
|
|
let text = JSON.stringify(report);
|
|
assert.match(text, /160\.0048/);
|
|
assert.match(text, /1160\.0348/);
|
|
assert.match(text, /sem informação de vendas/);
|
|
assert.match(text, /não é imposto a pagar/);
|
|
value.products[0].monthlyUnitsByPeriod[0] = '2';
|
|
recovered.business_plan_snapshot = serializeBusinessPlanSnapshot(value);
|
|
report = createProjectDossierReport(recovered, {
|
|
selectedSections: new Set(['finance']),
|
|
});
|
|
text = JSON.stringify(report);
|
|
assert.match(text, /Histórico desactualizado/);
|
|
assert.match(text, /160\.0048/);
|
|
assert.match(monthlyIvaCashTreatment, /não é somado novamente/);
|
|
assert.match(monthlyIvaCashTreatment, /nem deduzido como custo/);
|
|
});
|