This commit is contained in:
Jennie Robinson Faber 2025-09-04 10:42:03 +01:00
parent fc2d9ed56b
commit 983aeca2dc
32 changed files with 1570 additions and 27266 deletions

108
composables/useBudget.ts Normal file
View file

@ -0,0 +1,108 @@
import type { BudgetLine, OrgYearSettings } from '~/types/budget'
export function useBudget(orgId: string = 'default', year: number = new Date().getFullYear()) {
const budgetStore = useBudgetStore()
const cashStore = useCashStore()
const { analyzeConcentration } = useConcentration()
// Initialize budget from wizard data if needed
const initialized = ref(false)
onMounted(async () => {
if (!budgetStore.isInitialized) {
await budgetStore.initializeFromWizardData()
}
initialized.value = true
})
// Get starting cash from cash store
const startingCash = computed(() => {
const cash = cashStore.currentCash || 0
const savings = cashStore.currentSavings || 0
return cash + savings
})
// Build revenue and expense arrays for the 12 months
const revenuePlanned = computed(() => {
const revenue = Array(12).fill(0)
if (!budgetStore.budgetWorksheet?.revenue) {
return revenue
}
budgetStore.budgetWorksheet.revenue.forEach(item => {
// Get monthly values for the current year
const today = new Date()
for (let i = 0; i < 12; i++) {
const date = new Date(year, i, 1)
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
revenue[i] += item.monthlyValues?.[monthKey] || 0
}
})
return revenue
})
const expensePlanned = computed(() => {
const expenses = Array(12).fill(0)
if (!budgetStore.budgetWorksheet?.expenses) {
return expenses
}
budgetStore.budgetWorksheet.expenses.forEach(item => {
// Get monthly values for the current year
const today = new Date()
for (let i = 0; i < 12; i++) {
const date = new Date(year, i, 1)
const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`
expenses[i] += item.monthlyValues?.[monthKey] || 0
}
})
return expenses
})
// Calculate diversification percentage by category
const diversification = computed(() => {
const revTotalsByCat: Record<string, number> = {}
if (!budgetStore.budgetWorksheet?.revenue) {
return {
byCategoryPct: {},
guidance: 'No revenue data available'
}
}
// Sum annual revenue by category
budgetStore.budgetWorksheet.revenue.forEach(item => {
const category = item.mainCategory || 'Other'
const annualAmount = Object.values(item.monthlyValues || {}).reduce((sum, val) => sum + val, 0)
revTotalsByCat[category] = (revTotalsByCat[category] || 0) + annualAmount
})
const totalRev = Object.values(revTotalsByCat).reduce((a, b) => a + b, 0) || 1
const byCategoryPct = Object.fromEntries(
Object.entries(revTotalsByCat).map(([k, v]) => [k, (v / totalRev) * 100])
)
// Use existing concentration analysis
const revenueStreams = Object.entries(byCategoryPct).map(([category, pct]) => ({
targetPct: pct
}))
const analysis = analyzeConcentration(revenueStreams)
return {
byCategoryPct,
guidance: analysis.message
}
})
return {
startingCash,
revenuePlanned,
expensePlanned,
diversification
}
}

View file

@ -1,4 +1,5 @@
import { allocatePayroll as allocatePayrollImpl, monthlyPayroll, type Member, type PayPolicy } from '~/types/members'
import { useCoopBuilderStore } from '~/stores/coopBuilder'
export function useCoopBuilder() {
// Use the centralized Pinia store
@ -323,6 +324,30 @@ export function useCoopBuilder() {
store.loadDefaultData()
}
// Watch for policy and operating mode changes to refresh budget payroll
// This needs to be after computed values are defined
if (typeof window !== 'undefined') {
const budgetStore = useBudgetStore()
// Watch for policy changes
watch(() => [policy.value.relationship, policy.value.roleBands, operatingMode.value, store.equalHourlyWage, store.payrollOncostPct], () => {
if (budgetStore.isInitialized) {
nextTick(() => {
budgetStore.refreshPayrollInBudget()
})
}
}, { deep: true })
// Watch for member changes
watch(() => store.members, () => {
if (budgetStore.isInitialized) {
nextTick(() => {
budgetStore.refreshPayrollInBudget()
})
}
}, { deep: true })
}
return {
// State
members,