From f1889b3a70cc882f9110e66ba5cec1eac853bb73 Mon Sep 17 00:00:00 2001 From: Jennie Robinson Faber Date: Wed, 10 Sep 2025 07:42:56 +0100 Subject: [PATCH] refactor: remove CashFlowChart and UnifiedCashFlowDashboard components, update routing paths in app.vue, and enhance budget page with cumulative balance calculations and payroll explanation modal for improved user experience --- app.vue | 3 +- components/CashFlowChart.vue | 295 ---------------- components/CoopBuilderSubnav.vue | 11 +- components/ProjectBudgetEstimate.vue | 263 ++++++++++++++ components/UnifiedCashFlowDashboard.vue | 255 -------------- components/WizardCostsStep.vue | 8 +- components/dashboard/MemberCoveragePanel.vue | 39 ++- composables/useStorSync.ts | 341 +++++++++++-------- pages/budget.vue | 112 +++++- pages/cash-flow.vue | 30 -- pages/dashboard-simple.vue | 2 +- pages/dashboard.vue | 166 --------- pages/index.vue | 5 +- pages/project-budget.vue | 84 +++++ stores/budget.ts | 293 +++++++++++----- stores/coopBuilder.ts | 15 +- stores/policies.ts | 4 - 17 files changed, 922 insertions(+), 1004 deletions(-) delete mode 100644 components/CashFlowChart.vue create mode 100644 components/ProjectBudgetEstimate.vue delete mode 100644 components/UnifiedCashFlowDashboard.vue delete mode 100644 pages/cash-flow.vue delete mode 100644 pages/dashboard.vue create mode 100644 pages/project-budget.vue diff --git a/app.vue b/app.vue index e8aa8ad..c6d274c 100644 --- a/app.vue +++ b/app.vue @@ -88,10 +88,9 @@ const isCoopBuilderSection = computed( route.path === "/coop-planner" || route.path === "/coop-builder" || route.path === "/" || - route.path === "/dashboard" || route.path === "/mix" || route.path === "/budget" || - route.path === "/cash-flow" || + route.path === "/project-budget" || route.path === "/settings" || route.path === "/glossary" ); diff --git a/components/CashFlowChart.vue b/components/CashFlowChart.vue deleted file mode 100644 index e50f452..0000000 --- a/components/CashFlowChart.vue +++ /dev/null @@ -1,295 +0,0 @@ - - - \ No newline at end of file diff --git a/components/CoopBuilderSubnav.vue b/components/CoopBuilderSubnav.vue index c44f5c1..2abc11c 100644 --- a/components/CoopBuilderSubnav.vue +++ b/components/CoopBuilderSubnav.vue @@ -31,20 +31,15 @@ const coopBuilderItems = [ name: "Setup Wizard", path: "/coop-builder", }, - { - id: "dashboard", - name: "Compensation", - path: "/dashboard", - }, { id: "budget", name: "Budget", path: "/budget", }, { - id: "cash-flow", - name: "Cash Flow", - path: "/cash-flow", + id: "project-budget", + name: "Project Budget", + path: "/project-budget", }, ]; diff --git a/components/ProjectBudgetEstimate.vue b/components/ProjectBudgetEstimate.vue new file mode 100644 index 0000000..9c373bd --- /dev/null +++ b/components/ProjectBudgetEstimate.vue @@ -0,0 +1,263 @@ + + + + + \ No newline at end of file diff --git a/components/UnifiedCashFlowDashboard.vue b/components/UnifiedCashFlowDashboard.vue deleted file mode 100644 index 7be6e35..0000000 --- a/components/UnifiedCashFlowDashboard.vue +++ /dev/null @@ -1,255 +0,0 @@ - - - \ No newline at end of file diff --git a/components/WizardCostsStep.vue b/components/WizardCostsStep.vue index 7e17674..e6299e4 100644 --- a/components/WizardCostsStep.vue +++ b/components/WizardCostsStep.vue @@ -166,13 +166,7 @@ const overheadCosts = computed(() => })) ); -// Operating mode toggle -const useTargetMode = ref(coop.operatingMode.value === "target"); - -function updateOperatingMode(value: boolean) { - coop.setOperatingMode(value ? "target" : "min"); - emit("save-status", "saved"); -} +// Operating mode removed - always use target mode // Category options const categoryOptions = [ diff --git a/components/dashboard/MemberCoveragePanel.vue b/components/dashboard/MemberCoveragePanel.vue index f3b9fdb..96b9b47 100644 --- a/components/dashboard/MemberCoveragePanel.vue +++ b/components/dashboard/MemberCoveragePanel.vue @@ -21,11 +21,11 @@
{{ member.displayName || member.name || 'Unnamed Member' }} - {{ Math.round(coverage(member).coveragePct || 0) }}% covered + {{ Math.round(calculateCoverage(member)) }}% covered
@@ -49,11 +49,11 @@
-
+
@@ -95,7 +95,9 @@
- Total payroll: {{ formatCurrency(totalPayroll) }} + + Total sustainable payroll: {{ formatCurrency(totalPayroll) }} +
@@ -140,7 +142,11 @@ \ No newline at end of file diff --git a/pages/dashboard-simple.vue b/pages/dashboard-simple.vue index a471215..8bcb4f6 100644 --- a/pages/dashboard-simple.vue +++ b/pages/dashboard-simple.vue @@ -64,7 +64,7 @@ onMounted(async () => { const policiesStore = usePoliciesStore() // Update reactive values - currentMode.value = policiesStore.operatingMode || 'minimum' + currentMode.value = 'target' // Simplified - always use target mode memberCount.value = membersStore.members?.length || 0 streamCount.value = streamsStore.streams?.length || 0 diff --git a/pages/dashboard.vue b/pages/dashboard.vue deleted file mode 100644 index b53dccf..0000000 --- a/pages/dashboard.vue +++ /dev/null @@ -1,166 +0,0 @@ - - - \ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue index 5a32d80..b72b8bb 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -4,9 +4,6 @@

Compensation

- - {{ policiesStore.operatingMode === 'target' ? 'Target Mode' : 'Min Mode' }} - Runway: {{ Math.round(metrics.runway) }}mo @@ -224,7 +221,7 @@ const metrics = computed(() => { ); // Use integrated runway calculations that respect operating mode - const currentMode = policiesStore.operatingMode || 'minimum'; + const currentMode = 'target'; // Always target mode now const monthlyBurn = getMonthlyBurn(currentMode); // Use actual cash store values with fallback diff --git a/pages/project-budget.vue b/pages/project-budget.vue new file mode 100644 index 0000000..ac6ceef --- /dev/null +++ b/pages/project-budget.vue @@ -0,0 +1,84 @@ + + + \ No newline at end of file diff --git a/stores/budget.ts b/stores/budget.ts index 4ec7850..c1f5a0a 100644 --- a/stores/budget.ts +++ b/stores/budget.ts @@ -206,6 +206,28 @@ export const useBudgetStore = defineStore( return totals; }); + // Cumulative balance computation (running cash balance) + const cumulativeBalances = computed(() => { + const balances: Record = {}; + let runningBalance = 0; // Assuming starting balance of 0 - could be configurable + + // Generate month keys for next 12 months in order + const today = new Date(); + for (let i = 0; i < 12; i++) { + const date = new Date(today.getFullYear(), today.getMonth() + i, 1); + const monthKey = `${date.getFullYear()}-${String( + date.getMonth() + 1 + ).padStart(2, "0")}`; + + // Add this month's net income to running balance + const monthlyNet = monthlyTotals.value[monthKey]?.net || 0; + runningBalance += monthlyNet; + balances[monthKey] = runningBalance; + } + + return balances; + }); + // LEGACY: Keep for backward compatibility const currentDate = new Date(); const currentYear = currentDate.getFullYear(); @@ -242,6 +264,9 @@ export const useBudgetStore = defineStore( budgetLines.value[period].revenueByStream[streamId] = {}; } budgetLines.value[period].revenueByStream[streamId][type] = amount; + + // Refresh payroll to account for revenue changes + refreshPayrollInBudget(); } // Wizard-required actions @@ -316,7 +341,11 @@ export const useBudgetStore = defineStore( // Refresh payroll in budget when policy or operating mode changes function refreshPayrollInBudget() { - if (!isInitialized.value) return; + console.log("=== REFRESH PAYROLL CALLED ==="); + if (!isInitialized.value) { + console.log("Not initialized, skipping payroll refresh"); + return; + } const coopStore = useCoopBuilderStore(); const basePayrollIndex = budgetWorksheet.value.expenses.findIndex(item => item.id === "expense-payroll-base"); @@ -330,89 +359,163 @@ export const useBudgetStore = defineStore( const totalHours = coopStore.members.reduce((sum, m) => sum + (m.hoursPerMonth || 0), 0); const hourlyWage = coopStore.equalHourlyWage || 0; const oncostPct = coopStore.payrollOncostPct || 0; - const basePayrollBudget = totalHours * hourlyWage; - // Declare today once for the entire function - const today = new Date(); + // Keep theoretical maximum for reference + const theoreticalMaxPayroll = totalHours * hourlyWage; - if (basePayrollBudget > 0 && coopStore.members.length > 0) { - // Use policy-driven allocation - const payPolicy = { - relationship: coopStore.policy.relationship, - roleBands: coopStore.policy.roleBands - }; + // Policy for allocation + const payPolicy = { + relationship: coopStore.policy.relationship, + roleBands: coopStore.policy.roleBands + }; + + const membersForAllocation = coopStore.members.map(m => ({ + ...m, + displayName: m.name, + monthlyPayPlanned: m.monthlyPayPlanned || 0, + minMonthlyNeeds: m.minMonthlyNeeds || 0, + hoursPerMonth: m.hoursPerMonth || 0 + })); + + // Calculate payroll for each month individually using cumulative balance approach + const refreshToday = new Date(); + let totalAnnualPayroll = 0; + let totalAnnualOncosts = 0; + let runningBalance = 0; // Track cumulative cash balance + + for (let i = 0; i < 12; i++) { + const date = new Date(refreshToday.getFullYear(), refreshToday.getMonth() + i, 1); + const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`; - const membersForAllocation = coopStore.members.map(m => ({ - ...m, - displayName: m.name, - monthlyPayPlanned: m.monthlyPayPlanned || 0, - minMonthlyNeeds: m.minMonthlyNeeds || 0, - hoursPerMonth: m.hoursPerMonth || 0 - })); - - const allocatedMembers = allocatePayroll(membersForAllocation, payPolicy, basePayrollBudget); - - // Sum the allocated payroll amounts - const totalAllocatedPayroll = allocatedMembers.reduce((sum, m) => { - return sum + (m.monthlyPayPlanned || 0); + // Get revenue for this specific month + const monthRevenue = budgetWorksheet.value.revenue.reduce((sum, item) => { + return sum + (item.monthlyValues?.[monthKey] || 0); }, 0); - // Update monthly values for base payroll + // Get non-payroll expenses for this specific month + const nonPayrollExpenses = budgetWorksheet.value.expenses.reduce((sum, item) => { + // Exclude payroll items from the calculation + if (item.id === "expense-payroll-base" || item.id === "expense-payroll-oncosts" || item.id === "expense-payroll") { + return sum; + } + return sum + (item.monthlyValues?.[monthKey] || 0); + }, 0); - if (basePayrollIndex !== -1) { - // Update base payroll entry - for (let i = 0; i < 12; i++) { - const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`; - budgetWorksheet.value.expenses[basePayrollIndex].monthlyValues[monthKey] = totalAllocatedPayroll; + // Calculate normal payroll based on policy and wage (theoretical maximum) + const theoreticalPayrollBudget = totalHours * hourlyWage; + + console.log(`Month ${monthKey}: Revenue=${monthRevenue}, NonPayrollExp=${nonPayrollExpenses}, TheoreticalPayroll=${theoreticalPayrollBudget}, RunningBalance=${runningBalance}`); + + // Only allocate payroll if members exist + if (coopStore.members.length > 0) { + // First, calculate payroll using normal policy allocation + const allocatedMembers = allocatePayroll(membersForAllocation, payPolicy, theoreticalPayrollBudget); + + // Sum the allocated payroll amounts for this month + let monthlyAllocatedPayroll = allocatedMembers.reduce((sum, m) => { + return sum + (m.monthlyPayPlanned || 0); + }, 0); + + let monthlyOncostAmount = monthlyAllocatedPayroll * (oncostPct / 100); + + // Calculate projected balance after this month's expenses and payroll + const totalExpensesWithPayroll = nonPayrollExpenses + monthlyAllocatedPayroll + monthlyOncostAmount; + const monthlyNetIncome = monthRevenue - totalExpensesWithPayroll; + const projectedBalance = runningBalance + monthlyNetIncome; + + // Check if this payroll would push cumulative balance below minimum threshold + const minThreshold = coopStore.minCashThreshold || 0; + if (projectedBalance < minThreshold) { + // Calculate maximum sustainable payroll to maintain minimum cash threshold + const targetNetIncome = minThreshold - runningBalance; + const availableForExpenses = monthRevenue - targetNetIncome; + const maxSustainablePayroll = Math.max(0, (availableForExpenses - nonPayrollExpenses) / (1 + oncostPct / 100)); + + console.log(`Month ${monthKey}: Reducing payroll from ${monthlyAllocatedPayroll} to ${maxSustainablePayroll} to maintain minimum cash threshold of ${minThreshold}`); + + // Proportionally reduce all member allocations + if (monthlyAllocatedPayroll > 0) { + const reductionRatio = maxSustainablePayroll / monthlyAllocatedPayroll; + allocatedMembers.forEach(m => { + m.monthlyPayPlanned = (m.monthlyPayPlanned || 0) * reductionRatio; + }); + } + + monthlyAllocatedPayroll = maxSustainablePayroll; + monthlyOncostAmount = monthlyAllocatedPayroll * (oncostPct / 100); } - // Update annual values for base payroll - budgetWorksheet.value.expenses[basePayrollIndex].values = { - year1: { best: totalAllocatedPayroll * 12, worst: totalAllocatedPayroll * 8, mostLikely: totalAllocatedPayroll * 12 }, - year2: { best: totalAllocatedPayroll * 14, worst: totalAllocatedPayroll * 10, mostLikely: totalAllocatedPayroll * 13 }, - year3: { best: totalAllocatedPayroll * 16, worst: totalAllocatedPayroll * 12, mostLikely: totalAllocatedPayroll * 15 } - }; - } - - if (oncostIndex !== -1) { - // Update oncost entry - const oncostAmount = totalAllocatedPayroll * (oncostPct / 100); + // Update running balance with actual net income after payroll adjustments + const actualNetIncome = monthRevenue - (nonPayrollExpenses + monthlyAllocatedPayroll + monthlyOncostAmount); + runningBalance += actualNetIncome; - for (let i = 0; i < 12; i++) { - const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`; - budgetWorksheet.value.expenses[oncostIndex].monthlyValues[monthKey] = oncostAmount; + // Update this specific month's payroll values + if (basePayrollIndex !== -1) { + budgetWorksheet.value.expenses[basePayrollIndex].monthlyValues[monthKey] = monthlyAllocatedPayroll; } - // Update name with current percentage - budgetWorksheet.value.expenses[oncostIndex].name = `Payroll Taxes & Benefits (${oncostPct}%)`; - - // Update annual values for oncosts - budgetWorksheet.value.expenses[oncostIndex].values = { - year1: { best: oncostAmount * 12, worst: oncostAmount * 8, mostLikely: oncostAmount * 12 }, - year2: { best: oncostAmount * 14, worst: oncostAmount * 10, mostLikely: oncostAmount * 13 }, - year3: { best: oncostAmount * 16, worst: oncostAmount * 12, mostLikely: oncostAmount * 15 } - }; - } - - // Handle legacy single payroll entry (update to combined amount for backwards compatibility) - if (legacyIndex !== -1 && basePayrollIndex === -1) { - const monthlyPayroll = totalAllocatedPayroll * (1 + oncostPct / 100); - - for (let i = 0; i < 12; i++) { - const date = new Date(today.getFullYear(), today.getMonth() + i, 1); - const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`; - budgetWorksheet.value.expenses[legacyIndex].monthlyValues[monthKey] = monthlyPayroll; + if (oncostIndex !== -1) { + budgetWorksheet.value.expenses[oncostIndex].monthlyValues[monthKey] = monthlyOncostAmount; } - budgetWorksheet.value.expenses[legacyIndex].values = { - year1: { best: monthlyPayroll * 12, worst: monthlyPayroll * 8, mostLikely: monthlyPayroll * 12 }, - year2: { best: monthlyPayroll * 14, worst: monthlyPayroll * 10, mostLikely: monthlyPayroll * 13 }, - year3: { best: monthlyPayroll * 16, worst: monthlyPayroll * 12, mostLikely: monthlyPayroll * 15 } - }; + // Handle legacy single payroll entry + if (legacyIndex !== -1 && basePayrollIndex === -1) { + const combinedPayroll = monthlyAllocatedPayroll * (1 + oncostPct / 100); + budgetWorksheet.value.expenses[legacyIndex].monthlyValues[monthKey] = combinedPayroll; + } + + // Accumulate for annual totals + totalAnnualPayroll += monthlyAllocatedPayroll; + totalAnnualOncosts += monthlyOncostAmount; + } else { + // No members or theoretical payroll is 0 - set payroll to 0 + if (basePayrollIndex !== -1) { + budgetWorksheet.value.expenses[basePayrollIndex].monthlyValues[monthKey] = 0; + } + + if (oncostIndex !== -1) { + budgetWorksheet.value.expenses[oncostIndex].monthlyValues[monthKey] = 0; + } + + if (legacyIndex !== -1 && basePayrollIndex === -1) { + budgetWorksheet.value.expenses[legacyIndex].monthlyValues[monthKey] = 0; + } + + // Update running balance with net income (revenue - non-payroll expenses) + const actualNetIncome = monthRevenue - nonPayrollExpenses; + runningBalance += actualNetIncome; } } + + // Update annual values based on actual totals + if (basePayrollIndex !== -1) { + budgetWorksheet.value.expenses[basePayrollIndex].values = { + year1: { best: totalAnnualPayroll * 1.2, worst: totalAnnualPayroll * 0.8, mostLikely: totalAnnualPayroll }, + year2: { best: totalAnnualPayroll * 1.3, worst: totalAnnualPayroll * 0.9, mostLikely: totalAnnualPayroll * 1.1 }, + year3: { best: totalAnnualPayroll * 1.5, worst: totalAnnualPayroll, mostLikely: totalAnnualPayroll * 1.25 } + }; + } + + if (oncostIndex !== -1) { + // Update name with current percentage + budgetWorksheet.value.expenses[oncostIndex].name = `Payroll Taxes & Benefits (${oncostPct}%)`; + + budgetWorksheet.value.expenses[oncostIndex].values = { + year1: { best: totalAnnualOncosts * 1.2, worst: totalAnnualOncosts * 0.8, mostLikely: totalAnnualOncosts }, + year2: { best: totalAnnualOncosts * 1.3, worst: totalAnnualOncosts * 0.9, mostLikely: totalAnnualOncosts * 1.1 }, + year3: { best: totalAnnualOncosts * 1.5, worst: totalAnnualOncosts, mostLikely: totalAnnualOncosts * 1.25 } + }; + } + + // Handle legacy single payroll entry annual values + if (legacyIndex !== -1 && basePayrollIndex === -1) { + const totalCombined = totalAnnualPayroll + totalAnnualOncosts; + budgetWorksheet.value.expenses[legacyIndex].values = { + year1: { best: totalCombined * 1.2, worst: totalCombined * 0.8, mostLikely: totalCombined }, + year2: { best: totalCombined * 1.3, worst: totalCombined * 0.9, mostLikely: totalCombined * 1.1 }, + year3: { best: totalCombined * 1.5, worst: totalCombined, mostLikely: totalCombined * 1.25 } + }; + } } // Force reinitialize - always reload from wizard data @@ -456,8 +559,8 @@ export const useBudgetStore = defineStore( budgetWorksheet.value.revenue = []; budgetWorksheet.value.expenses = []; - // Declare today once for the entire function - const today = new Date(); + // Declare date once for the entire function + const initDate = new Date(); // Add revenue streams from wizard (but don't auto-load fixtures) // Note: We don't auto-load fixtures anymore, but wizard data should still work @@ -486,7 +589,7 @@ export const useBudgetStore = defineStore( // Create monthly values - split the annual target evenly across 12 months const monthlyValues: Record = {}; for (let i = 0; i < 12; i++) { - const date = new Date(today.getFullYear(), today.getMonth() + i, 1); + const date = new Date(initDate.getFullYear(), initDate.getMonth() + i, 1); const monthKey = `${date.getFullYear()}-${String( date.getMonth() + 1 ).padStart(2, "0")}`; @@ -530,17 +633,28 @@ export const useBudgetStore = defineStore( const totalHours = coopStore.members.reduce((sum, m) => sum + (m.hoursPerMonth || 0), 0); const hourlyWage = coopStore.equalHourlyWage || 0; const oncostPct = coopStore.payrollOncostPct || 0; + + // Use revenue-constrained payroll budget (same logic as useCoopBuilder) + const totalRevenue = coopStore.streams.reduce((sum, s) => sum + (s.monthly || 0), 0); + const overheadCosts = coopStore.overheadCosts.reduce((sum, c) => sum + (c.amount || 0), 0); + const availableForPayroll = Math.max(0, totalRevenue - overheadCosts); + + // Keep theoretical maximum for reference + const theoreticalMaxPayroll = totalHours * hourlyWage; console.log("=== PAYROLL CALCULATION DEBUG ==="); console.log("Total hours:", totalHours); console.log("Hourly wage:", hourlyWage); console.log("Oncost %:", oncostPct); - console.log("Operating mode:", coopStore.operatingMode); console.log("Policy relationship:", coopStore.policy.relationship); + console.log("Total revenue:", totalRevenue); + console.log("Overhead costs:", overheadCosts); + console.log("Available for payroll:", availableForPayroll); + console.log("Theoretical max payroll:", theoreticalMaxPayroll); - // Calculate total payroll budget using policy allocation - const basePayrollBudget = totalHours * hourlyWage; - console.log("Base payroll budget:", basePayrollBudget); + // Use revenue-constrained budget + const basePayrollBudget = availableForPayroll; + console.log("Using payroll budget:", basePayrollBudget); if (basePayrollBudget > 0 && coopStore.members.length > 0) { // Use policy-driven allocation to get actual member pay amounts @@ -579,7 +693,7 @@ export const useBudgetStore = defineStore( // Create monthly values for payroll const monthlyValues: Record = {}; for (let i = 0; i < 12; i++) { - const date = new Date(today.getFullYear(), today.getMonth() + i, 1); + const date = new Date(initDate.getFullYear(), initDate.getMonth() + i, 1); const monthKey = `${date.getFullYear()}-${String( date.getMonth() + 1 ).padStart(2, "0")}`; @@ -591,9 +705,9 @@ export const useBudgetStore = defineStore( // Create base payroll monthly values (without oncosts) const baseMonthlyValues: Record = {}; const oncostMonthlyValues: Record = {}; - // Reuse the today variable from above + // Reuse the initDate variable from above for (let i = 0; i < 12; i++) { - const date = new Date(today.getFullYear(), today.getMonth() + i, 1); + const date = new Date(initDate.getFullYear(), initDate.getMonth() + i, 1); const monthKey = `${date.getFullYear()}-${String( date.getMonth() + 1 ).padStart(2, "0")}`; @@ -677,7 +791,7 @@ export const useBudgetStore = defineStore( // Create monthly values for overhead costs const monthlyValues: Record = {}; for (let i = 0; i < 12; i++) { - const date = new Date(today.getFullYear(), today.getMonth() + i, 1); + const date = new Date(initDate.getFullYear(), initDate.getMonth() + i, 1); const monthKey = `${date.getFullYear()}-${String( date.getMonth() + 1 ).padStart(2, "0")}`; @@ -809,7 +923,7 @@ export const useBudgetStore = defineStore( console.log("Migrating item to monthly values:", item.name); item.monthlyValues = {}; for (let i = 0; i < 12; i++) { - const date = new Date(today.getFullYear(), today.getMonth() + i, 1); + const date = new Date(initDate.getFullYear(), initDate.getMonth() + i, 1); const monthKey = `${date.getFullYear()}-${String( date.getMonth() + 1 ).padStart(2, "0")}`; @@ -834,6 +948,10 @@ export const useBudgetStore = defineStore( ); isInitialized.value = true; + + // Trigger payroll refresh after initialization + console.log("Triggering initial payroll refresh after initialization"); + refreshPayrollInBudget(); } catch (error) { console.error("Error initializing budget from wizard data:", error); @@ -868,6 +986,14 @@ export const useBudgetStore = defineStore( console.log('Updated item.monthlyValues:', item.monthlyValues); console.log('Item updated:', item.name); + + // Refresh payroll when any budget item changes (except payroll items themselves) + if (!itemId.includes('payroll')) { + console.log('Triggering payroll refresh for non-payroll item:', itemId); + refreshPayrollInBudget(); + } else { + console.log('Skipping payroll refresh for payroll item:', itemId); + } } else { console.error('Item not found:', { category, itemId, availableItems: items.map(i => ({id: i.id, name: i.name})) }); } @@ -878,9 +1004,9 @@ export const useBudgetStore = defineStore( // Create empty monthly values for next 12 months const monthlyValues = {}; - const today = new Date(); + const addDate = new Date(); for (let i = 0; i < 12; i++) { - const date = new Date(today.getFullYear(), today.getMonth() + i, 1); + const date = new Date(addDate.getFullYear(), addDate.getMonth() + i, 1); const monthKey = `${date.getFullYear()}-${String( date.getMonth() + 1 ).padStart(2, "0")}`; @@ -969,6 +1095,7 @@ export const useBudgetStore = defineStore( budgetWorksheet, budgetTotals, monthlyTotals, + cumulativeBalances, revenueCategories, expenseCategories, revenueSubcategories, diff --git a/stores/coopBuilder.ts b/stores/coopBuilder.ts index 00bb36f..89c424a 100644 --- a/stores/coopBuilder.ts +++ b/stores/coopBuilder.ts @@ -2,8 +2,6 @@ import { defineStore } from "pinia"; export const useCoopBuilderStore = defineStore("coop", { state: () => ({ - operatingMode: "min" as "min" | "target", - // Currency preference currency: "EUR" as string, @@ -51,6 +49,9 @@ export const useCoopBuilderStore = defineStore("coop", { }, equalHourlyWage: 50, payrollOncostPct: 25, + + // Cash flow management + minCashThreshold: 5000, // Minimum cash balance to maintain savingsTargetMonths: 6, minCashCushion: 10000, @@ -152,11 +153,6 @@ export const useCoopBuilderStore = defineStore("coop", { this.milestones = this.milestones.filter((m) => m.id !== id); }, - // Operating mode - setOperatingMode(mode: "min" | "target") { - this.operatingMode = mode; - }, - // Scenario setScenario( scenario: "current" | "start-production" | "custom" @@ -182,6 +178,10 @@ export const useCoopBuilderStore = defineStore("coop", { this.payrollOncostPct = pct; }, + setMinCashThreshold(amount: number) { + this.minCashThreshold = amount; + }, + setCurrency(currency: string) { this.currency = currency; }, @@ -245,7 +245,6 @@ export const useCoopBuilderStore = defineStore("coop", { clearAll() { // Reset ALL state to initial empty values this._wasCleared = true; - this.operatingMode = "min"; this.currency = "EUR"; this.members = []; this.streams = []; diff --git a/stores/policies.ts b/stores/policies.ts index e77c692..aa94596 100644 --- a/stores/policies.ts +++ b/stores/policies.ts @@ -11,7 +11,6 @@ export const usePoliciesStore = defineStore( const payrollOncostPct = ref(0); const savingsTargetMonths = ref(0); const minCashCushionAmount = ref(0); - const operatingMode = ref<'minimum' | 'target'>('minimum'); // Pay policy for member needs coverage const payPolicy = ref({ @@ -134,7 +133,6 @@ export const usePoliciesStore = defineStore( payrollOncostPct.value = 0; savingsTargetMonths.value = 0; minCashCushionAmount.value = 0; - operatingMode.value = 'minimum'; payPolicy.value = { relationship: 'equal-pay', roleBands: [] }; deferredCapHoursPerQtr.value = 0; deferredSunsetMonths.value = 0; @@ -155,7 +153,6 @@ export const usePoliciesStore = defineStore( payrollOncostPct, savingsTargetMonths, minCashCushionAmount, - operatingMode, payPolicy, deferredCapHoursPerQtr, deferredSunsetMonths, @@ -190,7 +187,6 @@ export const usePoliciesStore = defineStore( "payrollOncostPct", "savingsTargetMonths", "minCashCushionAmount", - "operatingMode", "payPolicy", "deferredCapHoursPerQtr", "deferredSunsetMonths",