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'}]}}; } 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']); }); 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); }); 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/); }); 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/); });