import { annualReportRows } from './irpc-annual-settlement.ts'; import { proactiveInvestmentRows } from './proactive-investment-report.ts'; import { normalReportRows } from './irpc-normal.ts'; import { irpcReportRows } from './irpc-withholdings.ts'; import { ispcResidualReportRows } from './ispc-residuals.ts'; import { ispcWithholdingReportRows } from './ispc-withholding.ts'; import { ispcQuarterReportRows } from './ispc-quarter.ts'; import { ispcEligibilityReportRows } from './ispc-eligibility.ts'; import { fiscalCalculationGapReportRows } from './iva-calculation-gaps.ts'; import { l3FormPreparationReportRows } from './l3-form-preparation.ts'; import { remissionReportRows } from './iva-remission-imports.ts'; import { digitalReportRows } from './iva-digital.ts'; import { objectiveImportReportRows } from './iva-objective-imports.ts'; import { minerReportRows } from './iva-miners.ts'; import { returneeReportRows } from './iva-returnees.ts'; import { travelReportRows } from './iva-travel.ts'; import { travelEventReportRows } from './iva-travel-events.ts'; import { foreignOperationReportRows } from './iva-foreign-operations.ts'; import { currencyEventReportRows } from './iva-currency-events.ts'; import { propertyTransferReportRows } from './iva-property-transfers.ts'; import { goodsPresumptionReportRows } from './iva-goods-presumptions.ts'; import { displayValue } from './display-value.ts'; import { investmentFieldOrigin } from './context-inheritance.ts'; import { investmentLandPreparation } from './investment-land-preparation.ts'; import { monthlyIvaInput, monthlyIvaKey, readMonthlyIvaRecord, readMonthlyIvaSettings, } from './monthly-iva.ts'; import { modeloAPurchaseReferences } from './iva-modelo-a.ts'; import { acquisitionReportRows } from './iva-acquisitions.ts'; import { closureReportRows } from './iva-closure.ts'; import { substitutionReportRows } from './iva-substitution.ts'; import { refundReportRows } from './iva-refund.ts'; import { beverageRestitutionReportRows } from './iva-beverage-restitution.ts'; import { importExemptionReportRows } from './iva-import-exemptions.ts'; import { publicBaseReportRows } from './iva-public-authority-base.ts'; import { commissionBaseReportRows } from './iva-commission-base.ts'; import { actSaleReportRows } from './iva-act-sale-base.ts'; import { fuelBaseReportRows } from './iva-fuel-base.ts'; import { noncashBaseReportRows } from './iva-noncash-base.ts'; import { compositionReportRows } from './iva-composition-base.ts'; import { exclusionReportRows } from './iva-exclusion-base.ts'; import { valueReviewReportRows } from './iva-value-review.ts'; import { serviceNormalReportRows } from './iva-service-normal.ts'; import { goodsLocationReportRows } from './iva-goods-location.ts'; import { serviceLocationReportRows } from './iva-service-location.ts'; import { listedServiceReportRows } from './iva-listed-services.ts'; import { reducedBaseReportRows } from './iva-reduced-bases.ts'; import { ownNameReportRows } from './iva-own-name.ts'; import { goodsAllocationReportRows } from './iva-goods-allocation.ts'; import { secondhandReportRows } from './iva-secondhand.ts'; import { offerPreparationReportRows } from './iva-offer-preparations.ts'; import { salesReportRows } from './iva-operations.ts'; import { fiscalQaRows } from './fiscal-qa-preview.ts'; import { csvCellWithDelimiter } from './csv.js'; import { restoreCashflowDraftSnapshot, cashflowCurrency, projectMonthlyCashflow, } from './monthly-cashflow.ts'; import { calculateFinancialTools, restoreFinancialToolsSnapshot, } from './financial-calculators.ts'; import { forecastBusinessPlan, restoreBusinessPlanSnapshot, } from './business-financial-plan.ts'; import { getProjectReadiness, readPlannedRiskResponses, } from './project-readiness.ts'; export type TeamNeed = { competency: string; state: 'available' | 'gap' | 'unknown'; action: string; estimated_cost: string; due_date: string; }; export type ProjectMilestone = { title: string; due_date: string; status: 'planned' | 'in_progress' | 'completed'; planned_amount: string; actual_amount: string; }; export function readRows(value: string, allowed: readonly string[]): T[] { try { const parsed: unknown = JSON.parse(value || '[]'); if (!Array.isArray(parsed)) return []; return parsed.filter((row): row is T => Boolean( row && typeof row === 'object' && !Array.isArray(row) && allowed.every( (key) => typeof (row as Record)[key] === 'string', ), ), ); } catch { return []; } } export type ProjectDossierRow = [ section: string, field: string, value: string, origin: string, ]; export function buildProjectDossierRows( draft: Record, ): ProjectDossierRow[] { const rows: ProjectDossierRow[] = [ ['Secção', 'Campo', 'Valor', 'Origem / estado'], [ 'Projecto', 'Título', String(draft.project_title ?? ''), 'Declarado pelo utilizador', ], [ 'Projecto', 'Versão do questionário', String(draft.project_questionnaire_version ?? 'legacy-unversioned'), 'Metadado do questionário; não altera resultados guardados', ], [ 'Projecto', 'Resumo', String(draft.project_summary ?? ''), 'Declarado pelo utilizador', ], [ 'Projecto', 'Problema', String(draft.project_problem ?? ''), 'Declarado pelo utilizador', ], [ 'Projecto', 'Clientes/beneficiários', String(draft.target_customers ?? ''), 'Declarado pelo utilizador', ], [ 'Projecto', 'Proposta de valor', String(draft.value_proposition ?? ''), 'Declarado pelo utilizador', ], [ 'Projecto', 'Tipo de projecto', String(draft.project_type_label ?? draft.project_type ?? ''), 'Declarado/seleccionado pelo utilizador', ], [ 'Projecto', 'Sector', String(draft.sector_id_label ?? draft.sector_id ?? ''), 'Selecção declarada pelo utilizador', ], [ 'Projecto', 'Actividade económica', String(draft.activity_id_label ?? draft.activity_id ?? ''), 'Selecção declarada pelo utilizador', ], [ 'Projecto', 'Produtos e serviços', String(draft.products_services ?? ''), 'Declarado pelo utilizador', ], [ 'Projecto', 'Fase de maturidade', String(draft.maturity_stage_label ?? draft.maturity_stage ?? ''), 'Declarada pelo utilizador', ], [ 'Projecto', 'Data prevista de início', String(draft.expected_start_date ?? ''), 'Declarada pelo utilizador; data não verificada', ], [ 'Projecto', 'Duração prevista', draft.project_duration_years ? `${displayValue(draft.project_duration_years)} anos` : '', 'Declarada pelo utilizador', ], [ 'Mercado', 'Hipótese', String(draft.market_assumption ?? ''), 'Informação declarada, não verificada', ], [ 'Mercado', 'Fonte e data', String(draft.market_source ?? ''), 'Opcional; não verificada', ], [ 'Mercado', 'Concorrência e alternativas', String(draft.competitor_assumption ?? ''), 'Hipótese declarada; não verificada', ], [ 'Mercado', 'Canal de venda', String(draft.sales_channel_assumption ?? ''), 'Planeamento declarado; não confirmado', ], [ 'Mercado', 'Acções de divulgação', String(draft.marketing_approach ?? ''), 'Planeamento declarado; não confirmado', ], [ 'Localização', 'Província', String(draft.province_id_label ?? ''), 'Selecção de catálogo territorial', ], [ 'Localização', 'Distrito', String(draft.district_id_label ?? ''), 'Selecção de catálogo territorial', ], [ 'Localização', 'Posto administrativo', String(draft.admin_post_id_label ?? ''), 'Selecção de catálogo territorial', ], [ 'Localização', 'Localidade', String(draft.locality_id_label ?? ''), 'Selecção de catálogo territorial', ], [ 'Localização', 'Critérios e pontos a confirmar', String(draft.location_rationale ?? ''), 'Declaração do utilizador; não confirma disponibilidade', ], [ 'Plano financeiro', 'Estado do plano detalhado', draft.business_plan_snapshot ? 'Snapshot versionado disponível; usar também a exportação do plano financeiro' : 'Sem plano empresarial detalhado preenchido', 'Pressupostos declarados; não é demonstração contabilística', ], [ 'Equipa', 'Nota anterior sobre competências', String(draft.skills_needed ?? ''), 'Texto declarado existente', ], [ 'Execução', 'Nota anterior sobre marcos', String(draft.project_milestones ?? ''), 'Texto declarado existente', ], [ 'Financiamento', 'Estado de pré-elegibilidade', 'Informação insuficiente', 'Sem catálogo documentado de produtos e critérios', ], [ 'Necessidade de negócio', 'Descrição', String(draft.opportunity_description ?? ''), 'Declarado pelo utilizador', ], [ 'Necessidade de negócio', 'Infra-estrutura necessária', (Array.isArray(draft.infrastructure_needs) ? draft.infrastructure_needs : [] ).join(', '), 'Declarado/seleccionado pelo utilizador', ], [ 'Financiamento', 'Montante pretendido', String(draft.opportunity_value ?? ''), 'Declarado pelo utilizador; ver moeda separada', ], [ 'Financiamento', 'Moeda do montante', String( draft.opportunity_currency_label ?? draft.opportunity_currency ?? '', ), 'Declarada/seleccionada; sem avaliação de produto', ], ]; const plannedResponses = readPlannedRiskResponses(draft); for (const item of getProjectReadiness(draft)) { const response = plannedResponses.find( (entry) => entry.risk_id === item.id, ); const responseText = response ? [ response.action, response.owner ? `Responsável: ${response.owner}` : '', response.estimated_cost ? `Custo previsto: ${response.estimated_cost} ${String(draft.currency ?? '')}` : '', response.due_date ? `Prazo: ${response.due_date}` : '', ] .filter(Boolean) .join(' · ') : 'Resposta ainda não registada'; rows.push([ 'Risco', item.title, `${item.origin} ${item.affected} Consequência possível: ${item.consequence} Acção: ${item.nextAction} Resposta planeada: ${responseText}`, 'Derivado das respostas e campos em falta; estimativa qualitativa, sem probabilidade ou gravidade atribuída', ]); } const finance = restoreFinancialToolsSnapshot( String(draft.financial_tools_snapshot ?? ''), ); if (finance) { const calculation = calculateFinancialTools({ loan: { principal: finance.loan.principal, annualRatePercent: finance.loan.rate, rateType: finance.loan.rateType, termMonths: Number(finance.loan.months), periodMonths: Number(finance.loan.periodMonths), gracePeriods: Number(finance.loan.gracePeriods), upfrontFee: finance.loan.upfrontFee, recurringFee: finance.loan.recurringFee, }, breakEven: { fixedCosts: finance.breakEven.fixed, unitPrice: finance.breakEven.price, variableCostPerUnit: finance.breakEven.variable, }, funding: { totalInvestment: finance.funding.total, securedFunding: finance.funding.secured, }, incomes: finance.incomes.map((row) => row.amount), expenses: finance.expenses.map((row) => row.amount), }); const currency = String(draft.currency ?? '') || 'moeda não indicada'; const source = 'Estimativa matemática baseada em pressupostos declarados; não é oferta de crédito nem resultado oficial'; rows.push( [ 'Plano financeiro', 'Periodicidade do orçamento', finance.budgetPeriod, 'Declarada pelo utilizador', ], [ 'Empréstimo educativo', 'Capital pressuposto', `${finance.loan.principal} ${currency}`, 'Declarado pelo utilizador', ], [ 'Empréstimo educativo', 'Taxa anual pressuposta', `${finance.loan.rate}% (${finance.loan.rateType === 'nominal' ? 'nominal anual' : 'efectiva anual'})`, 'Declarada pelo utilizador; não é taxa de produto', ], [ 'Empréstimo educativo', 'Prazo pressuposto', `${finance.loan.months} meses`, 'Declarado pelo utilizador', ], [ 'Empréstimo educativo', 'Método e pressupostos substituídos', `Saldo decrescente; capital ${finance.loan.principal} ${currency}; taxa ${finance.loan.rate}% ${finance.loan.rateType === 'nominal' ? 'nominal anual (dividida pelos períodos do ano)' : 'efectiva anual (convertida para o período)'}; prazo ${finance.loan.months} meses; periodicidade ${finance.loan.periodMonths} meses; carência ${finance.loan.gracePeriods} períodos; comissão inicial ${finance.loan.upfrontFee} ${currency}; comissão periódica ${finance.loan.recurringFee} ${currency}. Montantes a duas casas decimais; juros e prestação arredondados à unidade monetária mínima, metade para cima; última prestação ajusta o saldo residual.`, 'Cronograma do simulador; taxa declarada, sem correspondência a produto', ], [ 'Empréstimo educativo', 'Prestação do primeiro período', calculation.loan.value?.schedule[0]?.totalPayment ? `${calculation.loan.value.schedule[0].totalPayment} ${currency}` : 'Indeterminado', source, ], [ 'Empréstimo educativo', 'Juros totais estimados', calculation.loan.value?.totalInterest ? `${calculation.loan.value.totalInterest} ${currency}` : 'Indeterminado', source, ], [ 'Empréstimo educativo', 'Estado/erro do cálculo', calculation.loan.error ?? (calculation.loan.value ? 'Calculado pelo cronograma do simulador' : 'Indeterminado'), source, ], [ 'Ponto de equilíbrio', 'Fórmula e valores substituídos', `${finance.breakEven.fixed} / (${finance.breakEven.price} − ${finance.breakEven.variable}); unidades arredondadas para cima`, 'Custos fixos ÷ margem de contribuição unitária; pressupostos declarados', ], [ 'Ponto de equilíbrio', 'Unidades estimadas', calculation.breakEven.value?.breakEvenUnits ?? 'Indeterminado', source, ], [ 'Ponto de equilíbrio', 'Vendas correspondentes', calculation.breakEven.value?.breakEvenRevenue ? `${calculation.breakEven.value.breakEvenRevenue} ${currency}` : 'Indeterminado', source, ], [ 'Ponto de equilíbrio', 'Estado/erro do cálculo', calculation.breakEven.error ?? (calculation.breakEven.value ? 'Calculado pelo simulador' : 'Indeterminado'), source, ], [ 'Financiamento', 'Investimento total pressuposto', `${finance.funding.total} ${currency}`, 'Declarado pelo utilizador', ], [ 'Financiamento', 'Montante já mobilizado pressuposto', `${finance.funding.secured} ${currency}`, 'Declarado pelo utilizador', ], [ 'Financiamento', 'Fórmula e valores substituídos', `${finance.funding.total} − ${finance.funding.secured}`, 'Investimento total declarado − financiamento mobilizado declarado', ], [ 'Financiamento', 'Diferença por financiar', calculation.funding.value?.gap ? `${calculation.funding.value.gap} ${currency}` : calculation.funding.value?.gap === '0.00' ? `0.00 ${currency}` : 'Indeterminado', source, ], [ 'Financiamento', 'Estado/erro do cálculo', calculation.funding.error ?? (calculation.funding.value ? 'Calculado pelo simulador' : 'Indeterminado'), source, ], [ 'Orçamento', 'Fórmula e valores substituídos', `${calculation.budget.value?.incomeTotal ?? 'Não informado'} − ${calculation.budget.value?.expenseTotal ?? 'Não informado'}`, 'Receitas declaradas − despesas declaradas; valores ausentes não são zero', ], [ 'Orçamento', 'Receitas declaradas', calculation.budget.value?.incomeTotal ? `${calculation.budget.value.incomeTotal} ${currency}` : 'Indeterminado', 'Declarado pelo utilizador', ], [ 'Orçamento', 'Despesas declaradas', calculation.budget.value?.expenseTotal ? `${calculation.budget.value.expenseTotal} ${currency}` : 'Indeterminado', 'Declarado pelo utilizador', ], [ 'Orçamento', 'Saldo operacional estimado', calculation.budget.value?.operatingBalance ? `${calculation.budget.value.operatingBalance} ${currency}` : 'Indeterminado', source, ], [ 'Orçamento', 'Estado/erro do cálculo', calculation.budget.error ?? (calculation.budget.value ? 'Calculado pelo simulador' : 'Indeterminado'), source, ], ); for (const income of finance.incomes) { rows.push([ 'Orçamento', `Receita declarada: ${income.label}`, `${income.amount} ${currency}`, 'Declarado pelo utilizador', ]); } for (const expense of finance.expenses) { rows.push([ 'Orçamento', `Despesa declarada: ${expense.label}`, `${expense.amount} ${currency}`, 'Declarado pelo utilizador', ]); } } const businessPlan = restoreBusinessPlanSnapshot( String(draft.business_plan_snapshot ?? ''), ); if (businessPlan) { try { const forecast = forecastBusinessPlan(businessPlan); const source = 'Estimativa calculada pelo plano financeiro a partir de pressupostos declarados; não é demonstração contabilística nem resultado confirmado'; rows.push( [ 'Plano financeiro', 'Moeda do plano', businessPlan.currency, 'Declarada pelo utilizador', ], [ 'Plano financeiro', 'Horizonte', `${businessPlan.periods ?? 12} meses`, 'Pressuposto declarado', ], [ 'Plano financeiro', 'Facturação no horizonte', `${forecast.totalRevenue} ${businessPlan.currency}`, source, ], [ 'Plano financeiro', 'Fórmula e valores substituídos', `Facturação: quantidade mensal × preço unitário por produto; caixa final: saldo de abertura + cobranças e fontes − pagamentos. Horizonte: ${businessPlan.periods ?? 12} meses; moeda: ${businessPlan.currency}.`, 'Métodos do plano financeiro; agendas e pressupostos declarados no snapshot', ], [ 'Plano financeiro', 'Cobranças no horizonte', `${forecast.totalCashCollections} ${businessPlan.currency}`, source, ], [ 'Plano financeiro', 'Pagamentos no horizonte', `${forecast.totalPayments} ${businessPlan.currency}`, source, ], [ 'Plano financeiro', 'Resultado antes de impostos', `${forecast.totalProfitBeforeTax} ${businessPlan.currency}`, source, ], [ 'Plano financeiro', 'Caixa final projectada', `${forecast.endingCash} ${businessPlan.currency}`, source, ], [ 'Plano financeiro', 'Reconciliação do financiamento', forecast.fundingStatus, 'Comparação calculada entre usos e fontes declarados', ], [ 'Plano financeiro', 'Diferença entre usos e fontes', `${forecast.fundingGap} ${businessPlan.currency}`, source, ], [ 'Plano financeiro', 'Taxa de câmbio de apresentação', businessPlan.fxRate ? `${businessPlan.fxRate}; ${businessPlan.fxDate ?? 'data não informada'}; ${businessPlan.fxSource ?? 'fonte não informada'}` : 'Não utilizada', businessPlan.fxRate ? 'Pressuposto de planeamento declarado pelo utilizador; não verificado' : 'Sem conversão', ], ); } catch { rows.push([ 'Plano financeiro', 'Estado do cálculo', 'Indeterminado', 'Snapshot inválido ou pressupostos incompatíveis; reveja o plano antes de exportar', ]); } } for (const [index, need] of readRows( String(draft.project_team_snapshot ?? ''), ['competency', 'state', 'action', 'estimated_cost', 'due_date'], ).entries()) { rows.push([ 'Equipa', `Necessidade ${index + 1}`, `${need.competency}; ${need.state}; resposta: ${need.action}; custo: ${need.estimated_cost}; prazo: ${need.due_date}`, 'Declarado pelo utilizador; sem certificação ou correspondência', ]); } for (const [index, milestone] of readRows( String(draft.project_execution_snapshot ?? ''), ['title', 'due_date', 'status', 'planned_amount', 'actual_amount'], ).entries()) { rows.push([ 'Execução', `Marco ${index + 1}`, `${milestone.title}; ${milestone.status}; prazo: ${milestone.due_date}; planeado: ${milestone.planned_amount}; realizado: ${milestone.actual_amount}`, 'Planeado/realizado declarado pelo utilizador', ]); } rows.push(...acquisitionReportRows(draft)); rows.push(...closureReportRows(draft)); rows.push(...substitutionReportRows(draft)); rows.push(...refundReportRows(draft)); rows.push(...beverageRestitutionReportRows(draft)); rows.push(...importExemptionReportRows(draft)); rows.push(...publicBaseReportRows(draft)); rows.push(...commissionBaseReportRows(draft)); rows.push(...actSaleReportRows(draft)); rows.push(...fuelBaseReportRows(draft)); rows.push(...noncashBaseReportRows(draft)); rows.push(...compositionReportRows(draft)); rows.push(...exclusionReportRows(draft)); rows.push(...valueReviewReportRows(draft)); rows.push(...serviceNormalReportRows(draft)); rows.push(...goodsLocationReportRows(draft)); rows.push(...serviceLocationReportRows(draft)); rows.push(...listedServiceReportRows(draft)); rows.push(...reducedBaseReportRows(draft)); rows.push(...ownNameReportRows(draft)); rows.push(...goodsAllocationReportRows(draft)); rows.push(...secondhandReportRows(draft)); rows.push(...goodsPresumptionReportRows(draft)); rows.push(...propertyTransferReportRows(draft)); rows.push(...currencyEventReportRows(draft)); rows.push(...foreignOperationReportRows(draft)); rows.push(...digitalReportRows(draft)); rows.push(...objectiveImportReportRows(draft)); rows.push(...minerReportRows(draft)); rows.push(...remissionReportRows(draft)); rows.push(...returneeReportRows(draft)); rows.push(...travelReportRows(draft)); rows.push(...travelEventReportRows(draft)); rows.push(...offerPreparationReportRows(draft)); rows.push(...ispcEligibilityReportRows(draft)); rows.push(...ispcQuarterReportRows(draft)); rows.push(...ispcWithholdingReportRows(draft)); rows.push(...ispcResidualReportRows(draft)); rows.push(...irpcReportRows(draft)); rows.push(...normalReportRows(draft)); rows.push(...annualReportRows(draft)); rows.push(...fiscalCalculationGapReportRows(draft)); rows.push(...l3FormPreparationReportRows(draft)); const operationRows = salesReportRows(draft); rows.push(...operationRows); if (draft.monthly_iva_snapshot) { const saved = readMonthlyIvaRecord(String(draft.monthly_iva_snapshot)); let current = false; try { current = !!saved && monthlyIvaKey( monthlyIvaInput( draft, readMonthlyIvaSettings( String(draft.monthly_iva_settings ?? ''), ), ), ) === monthlyIvaKey(saved.input); } catch { /* An old fiscal version is never silently made current. */ } rows.push([ 'IVA mensal', 'Estado da versão', current ? 'Entradas correspondentes à projecção guardada' : 'Histórico desactualizado face às vendas ou pressupostos actuais; não aplicar como resultado actual', 'Execução versionada no servidor; sem apuramento final', ]); if (saved?.execution.result_snapshot) for (const [label, value] of fiscalQaRows( saved.execution.result_snapshot, )) rows.push([ 'IVA mensal', operationRows.length ? `Execução geral separada — ${label}` : label, value, operationRows.length ? 'Snapshot geral conservado como execução separada; não somar nem substituir a classificação por operação L1. Sem recálculo na exportação.' : 'Snapshot fiscal preservado; sem recálculo na exportação', ]); else rows.push([ 'IVA mensal', 'Resultado', 'Indeterminado ou execução ainda não concluída', 'Sem substituição por zero', ]); try { for (const purchase of modeloAPurchaseReferences(draft)) rows.push([ 'IVA mensal', `Compras/encomendas — mês ${purchase.month}`, purchase.declared ?? 'Sem informação', 'Pressuposto actual do plano, não resultado fiscal histórico; base/IVA incluído por classificar; IVA suportado e dedutível indeterminados; não é data de factura ou pagamento', ]); } catch { rows.push([ 'IVA mensal', 'Aquisições projectadas', 'Não foi possível recuperar; sem substituição por zero', 'Pressupostos guardados preservados; não calcula deduções', ]); } } if (draft.cashflow_snapshot) { const saved = restoreCashflowDraftSnapshot( String(draft.cashflow_snapshot), ); const origin = 'Estimativa de planeamento; pressupostos guardados da ferramenta que podem incluir exemplos iniciais. Não confirma impostos nem recebimentos.'; try { if (!saved) throw new Error( 'Pressupostos de tesouraria não recuperáveis; não foram substituídos por valores alternativos.', ); const originCurrency = cashflowCurrency( saved, String(draft.currency ?? ''), ); const currency = originCurrency.currency; if (originCurrency.notice) rows.push([ 'Tesouraria', 'Divergência de moeda', originCurrency.notice, 'Moeda original preservada; sem conversão', ]); if (!currency) throw new Error( 'Indique uma moeda para interpretar os montantes de tesouraria.', ); const input = { ...saved.inputs, currency, ...(saved.taxMode === 'fixed' ? { monthlyTaxSchedule: undefined } : { monthlyTaxAssessed: '' }), }; const projection = projectMonthlyCashflow(input); const amount = (value: string | null) => value === null ? 'Indeterminado' : `${value} ${currency}`; rows.push([ 'Tesouraria', 'Pressupostos e período', `12 meses; moeda ${currency}; saldo inicial ${saved.inputs.openingCash}; receita mensal ${saved.inputs.monthlyRevenue}; custos mensais ${saved.inputs.monthlyOperatingCosts}; dívida mensal ${saved.inputs.monthlyDebtService}; imposto ${saved.taxMode === 'fixed' ? 'mensal fixo: ' + (saved.inputs.monthlyTaxAssessed || 'não informado') : 'variável por mês (vazio não é zero)'}. Crédito estimado ${saved.inputs.vatCreditEstimated}; pedido ${saved.inputs.vatRefundRequested}; reconhecido ${saved.inputs.vatRefundRecognized}; recebimento previsto ${saved.inputs.vatRefundExpectedReceipt}; mês previsto ${saved.inputs.vatRefundExpectedMonth || 'não informado'}.`, origin, ]); rows.push([ 'Tesouraria', 'Saldo final total', amount(projection.endingCash), origin, ]); rows.push([ 'Tesouraria', 'Caixa conhecida antes de imposto em falta', amount(projection.endingCashBeforeUnassessedTax), origin, ]); for (const row of projection.rows) rows.push([ 'Tesouraria', `Mês ${row.month}`, `Imposto assumido: ${amount(row.taxAssessed)}; recebimento de IVA previsto: ${amount(row.vatRefundExpectedReceipt)}; fluxo líquido: ${amount(row.netFlow)}; saldo final total: ${amount(row.closingCash)}.`, origin, ]); } catch (cause) { rows.push([ 'Tesouraria', 'Estado da tesouraria', cause instanceof Error ? cause.message : 'Tesouraria indeterminada; reveja os pressupostos.', origin, ]); } } if (draft.context_applicability_snapshot) { try { const context = JSON.parse( String(draft.context_applicability_snapshot), ); if (context.version === 1 && Array.isArray(context.complements)) for (const item of context.complements) { if ( typeof item?.title !== 'string' || typeof item?.origin?.title !== 'string' ) continue; rows.push([ 'Localização', 'Complemento escolhido', item.title, `Origem: ${item.origin.title}; ${item.kind === 'need' ? 'necessidade complementar em preparação' : item.kind === 'service' ? 'serviço/instrumento' : 'oportunidade cadastrada'}`, ]); if (typeof item.target?.title === 'string') rows.push([ 'Localização', 'Ficha complementar', item.target.title, item.target.href ?? 'Referência guardada', ]); } if ( context.version === 1 && Array.isArray(context.opportunity_choices) ) for (const choice of context.opportunity_choices) { if (typeof choice?.opportunity?.title !== 'string') continue; rows.push([ 'Localização', 'Oportunidade escolhida', choice.opportunity.title, 'Referência escolhida no mesmo dossier', ]); rows.push([ 'Localização', 'Localização da oportunidade', typeof choice.territory?.title === 'string' ? choice.territory.title : 'Por definir', choice.opportunity.title, ]); rows.push([ 'Localização', 'Zona/parque da oportunidade', typeof choice.park?.title === 'string' ? choice.park.title : 'Sem selecção', choice.opportunity.title + '; ' + choice.opportunity.key, ]); if (Array.isArray(choice.portfolio_origins)) for (const origin of choice.portfolio_origins) { if (typeof origin?.title !== 'string') continue; rows.push([ 'Localização', 'Associação escolhida na carteira', origin.title, choice.opportunity.title + '; referência documental consultada, sem inferir implantação ou benefícios', ]); } } } catch { /* An unreadable context remains indeterminate; do not reconstruct associations. */ } const land = investmentLandPreparation( draft.context_applicability_snapshot, ); rows.push([ 'Localização', 'Necessidade de terra', land.need === 'no' ? 'Não' : land.need === 'yes' ? 'Sim' : 'Por definir', land.reason || 'Decisão de preparação do dossier; não comprova direitos sobre a terra', ]); if (land.need === 'yes') rows.push([ 'Localização', 'Situação do terreno', String( draft.land_status_label ?? ( { owned: 'Próprio', duat: 'DUAT', leased: 'Arrendado', requested: 'Pedido em curso', unknown: 'Por determinar', } as Record )[String(draft.land_status)] ?? 'Informação em falta', ), 'Informação declarada; não comprova título ou disponibilidade', ]); else rows.push([ 'Localização', 'Preparação do terreno', land.need === 'no' ? 'Situação do terreno não aplicável à preparação actual; valores anteriores conservados.' : 'Definir se precisa de terra; pode continuar a preparar o investimento.', 'Sem inferência de dispensa legal', ]); } rows.push(...proactiveInvestmentRows(draft)); const lineageFields: Record = { Título: 'project_title', Resumo: 'project_summary', Sector: 'sector_id', Província: 'province_id', Distrito: 'district_id', 'Posto administrativo': 'admin_post_id', Localidade: 'locality_id', }; return rows.map((row) => { const key = lineageFields[row[1]]; return key && row[2] ? [ row[0], row[1], row[2], investmentFieldOrigin(draft, key, row[3]), ] : row; }); } export function exportProjectDossierCsv( draft: Record, ): string { const rows = buildProjectDossierRows(draft); // Semicolon is the active list separator in the target Excel locale (pt-MZ). return `\uFEFF${rows.map((row) => row.map((value) => csvCellWithDelimiter(value, ';')).join(';')).join('\r\n')}`; }