import { monthlyProjectedSales, restoreBusinessPlanSnapshot } from './business-financial-plan.ts'; import { cashflowCurrency, restoreCashflowDraftSnapshot } from './monthly-cashflow.ts'; import { isApiSimulationReadResponseV1, type ApiSimulationExecutionV1 } from './simulation-contract.ts'; export type MonthlyIvaInput = { scenario_kind: 'fiscal_rule_published'; monthly_sales: Array<{ date: string; amount: string | null }>; sales_vat_basis: 'net' | 'included' | 'unknown'; currency: string; scope_declared: boolean; }; export type MonthlyIvaSettings = { start: string; basis: MonthlyIvaInput['sales_vat_basis']; declared: boolean; auto: boolean }; export type MonthlyIvaRecord = { input: MonthlyIvaInput; execution: ApiSimulationExecutionV1 }; export function readMonthlyIvaSettings(raw?: string): MonthlyIvaSettings { try { const value = JSON.parse(raw ?? ''); if (typeof value.start === 'string' && ['net', 'included', 'unknown'].includes(value.basis) && typeof value.declared === 'boolean' && typeof value.auto === 'boolean') return value; } catch { /* An invalid setting is not an invented business assumption. */ } return {start: '', basis: 'unknown', declared: false, auto: false}; } export function monthlySalesFromDraft(draft: Record): { amounts: Array; source: string } { if (draft.business_plan_snapshot) { const plan = restoreBusinessPlanSnapshot(String(draft.business_plan_snapshot)); if (!plan) throw new Error('Não foi possível recuperar a projecção de vendas. Os dados guardados não foram substituídos.'); if (draft.currency && plan.currency && draft.currency !== plan.currency) throw new Error(`A projecção de vendas está em ${plan.currency}, mas o projecto está em ${String(draft.currency)}. Reveja a moeda da projecção; os valores não foram convertidos nem reclassificados.`); return {amounts: monthlyProjectedSales(plan), source: 'Vendas por produto e mês do plano empresarial; mesmos pressupostos e representação monetária da projecção de vendas.'}; } if (draft.cashflow_snapshot) { const saved = restoreCashflowDraftSnapshot(String(draft.cashflow_snapshot)); if (!saved) throw new Error('Não foi possível recuperar a receita projectada.'); const origin = cashflowCurrency(saved, String(draft.currency ?? '')); if (!origin.matchesProject) throw new Error(origin.notice); return {amounts: Array.from({length: 12}, () => saved.inputs.monthlyRevenue.trim() === '' ? null : saved.inputs.monthlyRevenue), source: 'Receita mensal constante já declarada na projecção de tesouraria; confirme que corresponde a vendas tributáveis.'}; } return {amounts: Array.from({length: 12}, () => null), source: 'Sem projecção de vendas guardada. Prepare as vendas no plano empresarial; não é necessário voltar a introduzi-las aqui.'}; } export function monthlyIvaInput(draft: Record, settings: MonthlyIvaSettings): MonthlyIvaInput { const sales = monthlySalesFromDraft(draft); if (!/^2026-(0[1-9]|1[0-2])$/.test(settings.start)) throw new Error('Indique o primeiro mês real da projecção. Este lote abrange apenas 2026.'); const start = Number(settings.start.slice(5)); if (sales.amounts.length > 12 || start + sales.amounts.length > 13) throw new Error('O horizonte ultrapassa a cobertura de 2026. Esta versão não calcula os meses fora desse período.'); return {scenario_kind: 'fiscal_rule_published', monthly_sales: sales.amounts.map((amount, index) => ({date: `2026-${String(start + index).padStart(2, '0')}-01`, amount})), sales_vat_basis: settings.basis, currency: String(draft.currency ?? ''), scope_declared: settings.declared}; } export function monthlyIvaKey(input: Omit): string { return JSON.stringify([input.currency, input.sales_vat_basis, input.scope_declared, input.monthly_sales.map((row) => [row.date, row.amount])]); } export function readMonthlyIvaRecord(raw?: string): MonthlyIvaRecord | null { try { const value = JSON.parse(raw ?? ''); if (value?.input?.scenario_kind === 'fiscal_rule_published' && Array.isArray(value.input.monthly_sales) && isApiSimulationReadResponseV1({data: value.execution})) return value; } catch { /* Keep the stored value intact; no fallback fiscal result. */ } return null; } /** Read an admitted execution through the existing ownership-protected endpoint; never create one. */ export async function recoverMonthlyIvaRecord(record: MonthlyIvaRecord, request: typeof fetch = fetch): Promise { const response = await request(`/api/v1/simulations/${encodeURIComponent(record.execution.id)}`, {credentials:'same-origin', headers:{Accept:'application/json'}}); const value: unknown = await response.json(); if (!response.ok || !isApiSimulationReadResponseV1(value) || value.data.id !== record.execution.id) throw new Error('Não foi possível recuperar a execução mensal guardada. Os dados foram preservados e não foi criada uma execução alternativa.'); return {input: record.input, execution: value.data}; } export const monthlyIvaCashTreatment = 'IVA apresentado separadamente: não é somado novamente aos recebimentos declarados nem deduzido como custo ou imposto final a pagar. A tesouraria conserva os recebimentos e pagamentos introduzidos, antes de uma liquidação fiscal não apurada; não se presume cobrança integral das vendas no mesmo mês.';