import { computed, ref } from "vue"; import type { Transaction, RevenueOpportunity, CashFlowProjection, RunwayScenarios, CashPosition, TransactionForecast, CombinedCashFlow, AffordabilityCheck, } from "~/types/cashflow"; export const useCashFlow = () => { const currentBalance = ref(0); const accountBalances = ref(null); const transactions = ref([]); const revenueOpportunities = ref([]); // Exchange rates for balance calculations - updated from Wise API const exchangeRates = ref({ CAD: 1.0, EUR: 1.45, // Will be updated from Wise API USD: 1.35, // Fallback rate GBP: 1.65 // Fallback rate }); // All transactions are now personal const filteredTransactions = computed(() => transactions.value); // Balance tracking const currentBalanceWithTransactions = computed(() => { const transactionTotal = transactions.value .reduce((sum, t) => sum + t.amount * t.probability, 0); return currentBalance.value + transactionTotal; }); // Starting balance (without transaction projections) const startingBalance = computed(() => { if (!accountBalances.value) { return currentBalance.value; } const manual = accountBalances.value.manual || {}; const wise = accountBalances.value.wise || { jennie: [], henry: [] }; // Personal accounts: RBC + TD + Millennium + Wise let totalBalance = 0; totalBalance += manual.rbc_cad || 0; totalBalance += manual.td_cad || 0; totalBalance += (manual.millennium_eur || 0) * exchangeRates.value.EUR; // Add Wise balances [...(wise.jennie || []), ...(wise.henry || [])].forEach(balance => { const rate = exchangeRates.value[balance.currency] || 1; totalBalance += balance.value.value * rate; }); return totalBalance; }); // Fetch real-time exchange rates for balance calculations const fetchExchangeRates = async () => { try { // Fetch EUR to CAD rate (most common conversion) const eurResponse = await fetch('/api/wise/exchange-rates?source=EUR&target=CAD'); if (eurResponse.ok) { const eurData = await eurResponse.json(); exchangeRates.value.EUR = eurData.rate; } } catch (error) { console.error('Failed to update exchange rates:', error); // Keep using fallback rates } }; // Load data from MongoDB on initialization const loadData = async () => { try { // Load transactions const transactionsResponse = await fetch("/api/transactions"); if (transactionsResponse.ok) { const transactionsData = await transactionsResponse.json(); transactions.value = transactionsData.map((t: any) => ({ ...t, date: new Date(t.date), endDate: t.endDate ? new Date(t.endDate) : undefined, // Migration: set accountType to 'personal' if missing accountType: t.accountType || "personal", })); } // Load balance const balanceResponse = await fetch("/api/balances"); if (balanceResponse.ok) { const balanceData = await balanceResponse.json(); currentBalance.value = balanceData.currentBalance || 0; accountBalances.value = balanceData.accountBalances || null; // Fetch current exchange rates for accurate balance calculations await fetchExchangeRates(); } } catch (error) { console.error("Failed to load data from MongoDB:", error); // Try to migrate from localStorage as fallback await tryMigrateFromLocalStorage(); } }; // Attempt to migrate from localStorage if MongoDB fails const tryMigrateFromLocalStorage = async () => { if (typeof window !== "undefined") { const savedBalance = localStorage.getItem("cashflow_balance"); const savedTransactions = localStorage.getItem("cashflow_transactions"); if (savedBalance || savedTransactions) { try { const migrationData = { balance: savedBalance ? parseFloat(savedBalance) : 0, transactions: savedTransactions ? JSON.parse(savedTransactions) : [], }; const response = await fetch("/api/migrate/localStorage", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(migrationData), }); if (response.ok) { console.log("Successfully migrated localStorage data to MongoDB"); // Clear localStorage after successful migration localStorage.removeItem("cashflow_balance"); localStorage.removeItem("cashflow_transactions"); localStorage.removeItem("account_balances"); // Reload data from MongoDB await loadData(); } } catch (error) { console.error("Migration failed:", error); // Fall back to localStorage data currentBalance.value = savedBalance ? parseFloat(savedBalance) : 0; const parsedTransactions = savedTransactions ? JSON.parse(savedTransactions) : []; transactions.value = parsedTransactions.map((t: any) => ({ ...t, })); } } } }; // Initialize data loading if (typeof window !== "undefined") { loadData(); } // Save to MongoDB helper const saveToDatabase = async () => { try { // Save transactions const response = await fetch("/api/transactions/bulk", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(transactions.value), }); if (!response.ok) { throw new Error("Failed to save transactions"); } console.log("✅ Transactions saved to MongoDB"); } catch (error) { console.error("Failed to save to MongoDB:", error); // Fallback to localStorage if MongoDB fails if (typeof window !== "undefined") { localStorage.setItem( "cashflow_balance", currentBalance.value.toString() ); localStorage.setItem( "cashflow_transactions", JSON.stringify(transactions.value) ); } } }; // Update data methods const updateTransactions = async (newTransactions: any[]) => { console.log( "🔄 updateTransactions called with:", newTransactions.length, "transactions" ); transactions.value = newTransactions.map((t: any) => ({ ...t, type: t.type || (t.isRecurring ? "recurring" : "one-time"), status: t.status || "committed", frequency: t.frequency || (t.isRecurring || t.type === "recurring" ? "monthly" : undefined), probability: t.isConfirmed ? 1 : typeof t.probability === "number" && t.probability >= 0 && t.probability <= 1 ? t.probability : 0.5, date: new Date(t.date), endDate: t.endDate ? new Date(t.endDate) : undefined, })); console.log( "✅ Transactions updated. Internal count:", transactions.value.length ); // Save to MongoDB await saveToDatabase(); }; const updateRevenueOpportunities = (newOpportunities: any[]) => { revenueOpportunities.value = newOpportunities.map((o: any) => ({ ...o, targetDate: new Date(o.targetDate), })); }; // Calculate current cash position including recurring projections const currentCashPosition = computed((): CashPosition => { const now = new Date(); const historicalInflow = filteredTransactions.value .filter((t) => { const transactionDate = new Date(t.date); const isHistorical = t.status === "actual" || transactionDate <= now; return t.amount > 0 && isHistorical; }) .reduce((sum, t) => sum + t.amount * t.probability, 0); const historicalOutflow = filteredTransactions.value .filter((t) => { const transactionDate = new Date(t.date); const isHistorical = t.status === "actual" || transactionDate <= now; return t.amount < 0 && isHistorical; }) .reduce((sum, t) => sum + Math.abs(t.amount * t.probability), 0); const currentMonthRecurringInflow = calculateRecurringInflowForAccount(now); const currentMonthRecurringOutflow = calculateRecurringOutflowForAccount(now); return { balance: currentBalanceWithTransactions.value, totalInflow: historicalInflow, totalOutflow: historicalOutflow, netFlow: currentMonthRecurringInflow - currentMonthRecurringOutflow, }; }); // Calculate monthly burn rate including recurring transactions const monthlyBurnRate = computed(() => { const now = new Date(); const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1); const thisMonth = new Date(now.getFullYear(), now.getMonth(), 1); const historicalExpenses = filteredTransactions.value .filter((t) => { const transactionDate = new Date(t.date); return ( transactionDate >= lastMonth && transactionDate < thisMonth && t.amount < 0 && t.type === "one-time" ); }) .reduce((sum, t) => sum + Math.abs(t.amount * t.probability), 0); const currentRecurringOutflow = calculateRecurringOutflowForAccount(now); const futureRecurringOutflow = filteredTransactions.value .filter((t) => { if (t.type !== "recurring" || t.amount >= 0) return false; const startDate = new Date(t.date); const endDate = t.endDate ? new Date(t.endDate) : new Date("2099-12-31"); return startDate > now && startDate <= endDate; }) .reduce( (sum, t) => sum + Math.abs(getMonthlyAmount(t) * t.probability), 0 ); return ( historicalExpenses + currentRecurringOutflow + futureRecurringOutflow ); }); // Enhanced runway calculation with multiple scenarios const calculateRunwayScenarios = ( programBudgets?: Record ): RunwayScenarios => { const totalContingency = programBudgets ? Object.values(programBudgets).reduce( (sum, budget: any) => sum + (budget.contingency?.amount || 0), 0 ) : 0; return { conservative: calculateRunwayFromTransactions( transactions.value.filter((t) => t.probability === 1.0), startingBalance.value - totalContingency ), realistic: calculateRunwayFromTransactions( transactions.value, startingBalance.value - totalContingency * 0.5 ), optimistic: calculateRunwayFromTransactions( transactions.value.map((t) => ({ ...t, probability: 1.0 })), startingBalance.value ), cashOnly: (() => { const dailyBurn = monthlyBurnRate.value / 30; if (dailyBurn <= 0) return Infinity; return Math.floor(startingBalance.value / dailyBurn); })(), }; }; // Calculate runway from transactions chronologically const calculateRunwayFromTransactions = ( transactionList: Transaction[], startingBalance: number ) => { let runningBalance = startingBalance; let dayCounter = 0; const now = new Date(); // Use all transactions (personal only) const filteredList = transactionList; const futureTransactions = getFutureTransactionsWithOccurrences( filteredList, now ); const sortedTransactions = futureTransactions.sort( (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() ); for (const transaction of sortedTransactions) { const daysSinceStart = Math.floor( (new Date(transaction.date).getTime() - now.getTime()) / (1000 * 60 * 60 * 24) ); const weightedAmount = transaction.amount * transaction.probability; if (transaction.probability < 0.2) continue; runningBalance += weightedAmount; dayCounter = daysSinceStart; if (runningBalance <= 0) { const prevTransaction = sortedTransactions[sortedTransactions.indexOf(transaction) - 1]; if (prevTransaction) { const prevBalance = runningBalance - weightedAmount; const interpolatedDay = dayCounter - 1 + prevBalance / (prevBalance - runningBalance); return Math.max(0, Math.round(interpolatedDay)); } return Math.max(0, dayCounter); } } if (runningBalance > 0) { const monthlyBurn = monthlyBurnRate.value; if (monthlyBurn <= 0) return Infinity; const additionalMonths = runningBalance / monthlyBurn; const additionalDays = additionalMonths * 30; return Math.round(dayCounter + additionalDays); } return dayCounter; }; // Get future transactions including recurring occurrences const getFutureTransactionsWithOccurrences = ( transactionList: Transaction[], fromDate: Date ) => { const futureTransactions: Transaction[] = []; const projectionPeriod = 365; for (const transaction of transactionList) { if (transaction.isTest) continue; if (transaction.type === "one-time") { const transactionDate = new Date(transaction.date); if (transactionDate > fromDate) { futureTransactions.push(transaction); } } else if (transaction.type === "recurring") { const startDate = new Date(transaction.date); const endDate = transaction.endDate ? new Date(transaction.endDate) : new Date("2099-12-31"); if (endDate > fromDate) { const occurrences = generateRecurringOccurrences( transaction, fromDate, projectionPeriod ); futureTransactions.push(...occurrences); } } } for (const opportunity of revenueOpportunities.value) { const opportunityDate = new Date(opportunity.targetDate); if (opportunityDate > fromDate) { futureTransactions.push({ id: opportunity._id || opportunity.id, date: opportunityDate, description: `Pipeline: ${opportunity.source}`, amount: opportunity.amount, category: "Income", type: "one-time", probability: opportunity.probability, isConfirmed: false, status: "projected", notes: `Stage: ${opportunity.stage}`, }); } } return futureTransactions; }; // Generate recurring transaction occurrences const generateRecurringOccurrences = ( transaction: Transaction, fromDate: Date, days: number ) => { const occurrences: Transaction[] = []; const endDate = new Date(fromDate); endDate.setDate(endDate.getDate() + days); const transactionEndDate = transaction.endDate ? new Date(transaction.endDate) : endDate; const actualEndDate = transactionEndDate < endDate ? transactionEndDate : endDate; let currentDate = new Date( Math.max(fromDate.getTime(), new Date(transaction.date).getTime()) ); while (currentDate <= actualEndDate) { const frequency = transaction.frequency || "monthly"; let nextDate: Date; switch (frequency) { case "weekly": nextDate = new Date(currentDate); nextDate.setDate(nextDate.getDate() + 7); break; case "biweekly": nextDate = new Date(currentDate); nextDate.setDate(nextDate.getDate() + 14); break; case "monthly": nextDate = new Date(currentDate); nextDate.setMonth(nextDate.getMonth() + 1); break; case "quarterly": nextDate = new Date(currentDate); nextDate.setMonth(nextDate.getMonth() + 3); break; case "custom": nextDate = new Date(currentDate); nextDate.setMonth( nextDate.getMonth() + (transaction.customMonths || 1) ); break; default: nextDate = new Date(currentDate); nextDate.setMonth(nextDate.getMonth() + 1); } if (currentDate > fromDate && currentDate <= actualEndDate) { occurrences.push({ ...transaction, date: new Date(currentDate), id: `${transaction.id}-${currentDate.getTime()}`, }); } currentDate = nextDate; if (occurrences.length > 1000) break; } return occurrences; }; // Calculate runway in days (now uses realistic scenario) const runwayDays = computed(() => { const scenarios = calculateRunwayScenarios(); return scenarios.realistic; }); // Calculate risk level const riskLevel = computed(() => { const runway = runwayDays.value; const balance = currentBalance.value; if (runway < 30 || balance < 25000) return "critical"; if (runway < 60 || balance < 50000) return "high"; if (runway < 90 || balance < 75000) return "medium"; return "low"; }); // Calculate available cash (unrestricted funds only) const availableCash = computed(() => { return currentBalance.value; }); // Calculate restricted cash const restrictedCash = computed(() => { return 0; }); // Calculate total cash const totalCash = computed(() => { return availableCash.value + restrictedCash.value; }); // Calculate available runway based on the standard runway calculation const availableRunwayDays = computed(() => { return runwayDays.value; }); // Available cash risk level const availableRiskLevel = computed(() => { const runway = availableRunwayDays.value; const balance = availableCash.value; if (runway < 14 || balance < 10000) return "critical"; if (runway < 28 || balance < 25000) return "high"; if (runway < 42 || balance < 40000) return "medium"; return "low"; }); // Generate cash flow projections const generateProjections = ( months: number = 12, timeContext?: { startDate?: Date; endDate?: Date } ) => { const projections: CashFlowProjection[] = []; let runningBalance = currentBalance.value; if (timeContext && timeContext.startDate && timeContext.endDate) { const startDate = new Date(timeContext.startDate); const endDate = new Date(timeContext.endDate); const monthsDiff = Math.round( (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24 * 30) ); months = Math.max(1, Math.min(monthsDiff, 24)); } for (let i = 0; i < months; i++) { const projectionDate = new Date(); projectionDate.setMonth(projectionDate.getMonth() + i); const monthlyInflow = calculateRecurringInflow(projectionDate); const monthlyOutflow = calculateRecurringOutflow(projectionDate); const oneTimeTransactions = getOneTimeTransactionsForMonth(projectionDate); let oneTimeInflow = 0; let oneTimeOutflow = 0; oneTimeTransactions.forEach((t) => { const amount = t.amount * t.probability; if (amount > 0) { if (t.status === "actual" || t.status === "committed") { oneTimeInflow += amount; } } else { oneTimeOutflow += Math.abs(amount); } }); const opportunityInflow = 0; const totalInflow = monthlyInflow + oneTimeInflow + opportunityInflow; const totalOutflow = monthlyOutflow + oneTimeOutflow; const netFlow = totalInflow - totalOutflow; runningBalance += netFlow; projections.push({ date: new Date(projectionDate), balance: runningBalance, inflow: totalInflow, outflow: totalOutflow, netFlow: netFlow, projectedBalance: runningBalance, }); } return projections; }; // Generate weekly cash flow projections const generateWeeklyProjections = (weeks: number = 13) => { const projections: CashFlowProjection[] = []; let runningBalance = currentBalance.value; for (let i = 0; i < weeks; i++) { const weekStart = new Date(); weekStart.setDate(weekStart.getDate() + i * 7); const weekEnd = new Date(weekStart); weekEnd.setDate(weekEnd.getDate() + 6); weekEnd.setHours(23, 59, 59, 999); let weeklyInflow = 0; let weeklyOutflow = 0; transactions.value .filter((t) => !t.isTest) .forEach((transaction) => { const amount = transaction.amount * transaction.probability; if (transaction.type === "one-time") { const txnDate = new Date(transaction.date); if (txnDate >= weekStart && txnDate <= weekEnd) { if (amount > 0) { if ( transaction.status === "actual" || transaction.status === "committed" ) { weeklyInflow += amount; } } else { weeklyOutflow += Math.abs(amount); } } } else if (transaction.type === "recurring") { const startDate = new Date(transaction.date); const endDate = transaction.endDate ? new Date(transaction.endDate) : new Date("2099-12-31"); if (weekStart <= endDate && weekEnd >= startDate) { const weeklyAmount = getWeeklyAmountForTransaction(transaction); if (weeklyAmount > 0) { weeklyInflow += weeklyAmount; } else { weeklyOutflow += Math.abs(weeklyAmount); } } } }); revenueOpportunities.value.forEach((opportunity) => { const oppDate = new Date(opportunity.targetDate); if (oppDate >= weekStart && oppDate <= weekEnd) { weeklyInflow += opportunity.amount * opportunity.probability; } }); const netFlow = weeklyInflow - weeklyOutflow; runningBalance += netFlow; projections.push({ date: new Date(weekStart), balance: runningBalance, inflow: weeklyInflow, outflow: weeklyOutflow, netFlow, projectedBalance: runningBalance, }); } return projections; }; // Helper function to calculate weekly amount for recurring transactions const getWeeklyAmountForTransaction = (transaction: Transaction): number => { if (transaction.type !== "recurring") return 0; const frequency = transaction.frequency || "monthly"; const baseAmount = transaction.amount * transaction.probability; switch (frequency) { case "weekly": return baseAmount; case "biweekly": return baseAmount / 2; case "monthly": return baseAmount / 4.33; case "quarterly": return baseAmount / 13; case "custom": if (transaction.customMonths) { return baseAmount / (transaction.customMonths * 4.33); } return baseAmount / 4.33; default: return baseAmount / 4.33; } }; // Helper functions const calculateRecurringInflow = (date: Date): number => { return transactions.value .filter( (t) => t.type === "recurring" && t.amount > 0 && (t.status === "actual" || t.status === "committed") && isTransactionActiveInMonth(t, date) ) .reduce((sum, t) => sum + getMonthlyAmount(t) * t.probability, 0); }; const calculateRecurringOutflow = (date: Date): number => { return transactions.value .filter( (t) => t.type === "recurring" && t.amount < 0 && (t.status === "actual" || t.status === "committed") && isTransactionActiveInMonth(t, date) ) .reduce( (sum, t) => sum + Math.abs(getMonthlyAmount(t) * t.probability), 0 ); }; // Account-specific helper functions const calculateRecurringInflowForAccount = (date: Date): number => { return filteredTransactions.value .filter( (t) => t.type === "recurring" && t.amount > 0 && (t.status === "actual" || t.status === "committed") && isTransactionActiveInMonth(t, date) ) .reduce((sum, t) => sum + getMonthlyAmount(t) * t.probability, 0); }; const calculateRecurringOutflowForAccount = (date: Date): number => { return filteredTransactions.value .filter( (t) => t.type === "recurring" && t.amount < 0 && (t.status === "actual" || t.status === "committed") && isTransactionActiveInMonth(t, date) ) .reduce( (sum, t) => sum + Math.abs(getMonthlyAmount(t) * t.probability), 0 ); }; const getOneTimeTransactionsForMonth = (date: Date): Transaction[] => { return transactions.value.filter((t) => { if (t.type !== "one-time") return false; const transactionDate = new Date(t.date); return ( transactionDate.getMonth() === date.getMonth() && transactionDate.getFullYear() === date.getFullYear() ); }); }; const isTransactionActiveInMonth = ( transaction: Transaction, date: Date ): boolean => { const startDate = new Date(transaction.date); const endDate = transaction.endDate ? new Date(transaction.endDate) : new Date("2099-12-31"); const monthStart = new Date(date.getFullYear(), date.getMonth(), 1); const monthEnd = new Date( date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999 ); return monthEnd >= startDate && monthStart <= endDate; }; const getMonthlyAmount = (transaction: Transaction): number => { if (transaction.type !== "recurring") return 0; const frequencyMultiplier = { weekly: 4.33, biweekly: 2.17, monthly: 1, quarterly: 0.33, custom: transaction.customMonths ? 12 / transaction.customMonths : 1, }; return ( transaction.amount * (frequencyMultiplier[transaction.frequency || "monthly"] || 1) ); }; const setCurrentBalance = async (balance: number) => { currentBalance.value = balance; try { // Get existing balance data first to preserve account balances const existingResponse = await fetch("/api/balances"); let existingData = null; if (existingResponse.ok) { existingData = await existingResponse.json(); } // Only update the total balance, preserve existing account balances const response = await fetch("/api/balances", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ currentBalance: balance, accountBalances: existingData?.accountBalances || { manual: { rbc_cad: 0, td_cad: 0, millennium_eur: 0 }, wise: { jennie: [], henry: [] }, }, }), }); if (!response.ok) { throw new Error("Failed to save balance"); } // Update local accountBalances accountBalances.value = existingData?.accountBalances || null; console.log("✅ Balance saved to MongoDB"); } catch (error) { console.error("Failed to save balance to MongoDB:", error); // Fallback to localStorage if (typeof window !== "undefined") { localStorage.setItem("cashflow_balance", balance.toString()); } } }; // Affordability checker functions const canAfford = ( amount: number, targetDate?: Date, fundType: "restricted" | "unrestricted" = "unrestricted" ): AffordabilityCheck => { const testDate = targetDate || new Date(); if (fundType === "restricted") { const availableRestricted = restrictedCash.value; if (Math.abs(amount) > availableRestricted) { return { canAfford: false, reason: "Insufficient restricted funds", availableAmount: availableRestricted, shortfall: Math.abs(amount) - availableRestricted, }; } } const projectedAvailableCash = getAvailableCashAtDate(testDate); const newBalance = projectedAvailableCash + amount; const weeklyBurn = getWeeklyBurnRate(); const newRunwayWeeks = weeklyBurn > 0 ? newBalance / weeklyBurn : Infinity; return { canAfford: newRunwayWeeks >= 4, currentAvailable: projectedAvailableCash, newBalance, currentRunwayWeeks: weeklyBurn > 0 ? projectedAvailableCash / weeklyBurn : Infinity, newRunwayWeeks, riskLevel: getRiskLevelForRunway(newRunwayWeeks), recommendation: getAffordabilityRecommendation(newRunwayWeeks, amount), }; }; const getAvailableCashAtDate = (date: Date): number => { let projectedBalance = availableCash.value; const now = new Date(); const futureTxns = transactions.value.filter((t) => { if (t.isTest) return false; if (t.fundType === "restricted") return false; const txnDate = new Date(t.date); return txnDate > now && txnDate <= date; }); futureTxns.forEach((t) => { projectedBalance += t.amount * t.probability; }); return projectedBalance; }; const getWeeklyBurnRate = (): number => { return monthlyBurnRate.value / 4.33; }; const getRiskLevelForRunway = (runwayWeeks: number): string => { if (runwayWeeks < 2) return "critical"; if (runwayWeeks < 4) return "high"; if (runwayWeeks < 6) return "medium"; return "low"; }; const getAffordabilityRecommendation = ( runwayWeeks: number, amount: number ): string => { if (runwayWeeks >= 6) return "Safe to proceed"; if (runwayWeeks >= 4) return "Proceed with caution - monitor cash flow"; if (runwayWeeks >= 2) return "High risk - consider delaying or reducing amount"; return "Do not proceed - insufficient runway"; }; // Test transaction management const createTestTransaction = ( description: string, amount: number, date: Date, program?: string, fundType: "restricted" | "unrestricted" = "unrestricted" ): Transaction => { const testTxn: Transaction = { id: `test-${Date.now()}`, date, description: `[TEST] ${description}`, amount, category: "Expense: Want", type: "one-time", program, status: "projected", fundType, probability: 1.0, isConfirmed: false, isTest: true, notes: "Test transaction for affordability checking", }; transactions.value.push(testTxn); return testTxn; }; const removeTestTransaction = (transactionId: string) => { transactions.value = transactions.value.filter( (t) => !(t.isTest && t.id === transactionId) ); }; const removeAllTestTransactions = () => { transactions.value = transactions.value.filter((t) => !t.isTest); }; // Delete transaction method const deleteTransaction = async (transactionId: string) => { try { // Call API to delete from database const response = await fetch(`/api/transactions/${transactionId}`, { method: "DELETE", }); if (!response.ok) { throw new Error("Failed to delete transaction from database"); } // Remove from local state transactions.value = transactions.value.filter( (t) => t.id !== transactionId ); console.log("✅ Transaction deleted successfully"); return { success: true }; } catch (error) { console.error("Failed to delete transaction:", error); throw error; } }; return { // State currentBalance, transactions, revenueOpportunities, // Computed - Original currentCashPosition, monthlyBurnRate, runwayDays, riskLevel, // Computed - Fund Tracking availableCash, restrictedCash, totalCash, availableRunwayDays, availableRiskLevel, // Computed filteredTransactions, currentBalanceWithTransactions, startingBalance, startingBalanceForActive: startingBalance, // Alias for compatibility // Methods - Original generateProjections, generateWeeklyProjections, updateTransactions, updateRevenueOpportunities, setCurrentBalance, // Methods - Affordability & Testing canAfford, getAvailableCashAtDate, createTestTransaction, removeTestTransaction, removeAllTestTransactions, deleteTransaction, // Enhanced runway calculations calculateRunwayScenarios, }; };