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
This commit is contained in:
parent
864a81065c
commit
f1889b3a70
17 changed files with 922 additions and 1004 deletions
293
stores/budget.ts
293
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<string, number> = {};
|
||||
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<string, number> = {};
|
||||
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<string, number> = {};
|
||||
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<string, number> = {};
|
||||
const oncostMonthlyValues: Record<string, number> = {};
|
||||
// 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<string, number> = {};
|
||||
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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue