92 lines
No EOL
2.8 KiB
TypeScript
92 lines
No EOL
2.8 KiB
TypeScript
import { monthlyPayroll } from '~/types/members'
|
|
|
|
export function useCushionForecast() {
|
|
const cashStore = useCashStore()
|
|
const membersStore = useMembersStore()
|
|
const policiesStore = usePoliciesStore()
|
|
const budgetStore = useBudgetStore()
|
|
|
|
// Savings progress calculation
|
|
const savingsProgress = computed(() => {
|
|
const current = cashStore.currentSavings || 0
|
|
const targetMonths = policiesStore.savingsTargetMonths || 3
|
|
const monthlyBurn = getMonthlyBurn()
|
|
const target = targetMonths * monthlyBurn
|
|
|
|
return {
|
|
current,
|
|
target,
|
|
targetMonths,
|
|
progressPct: target > 0 ? Math.min(100, (current / target) * 100) : 0,
|
|
gap: Math.max(0, target - current),
|
|
status: getProgressStatus(current, target)
|
|
}
|
|
})
|
|
|
|
// 13-week cushion breach forecast
|
|
const cushionForecast = computed(() => {
|
|
const minCushion = policiesStore.minCashCushionAmount || 5000
|
|
const currentBalance = cashStore.currentCash || 0
|
|
const monthlyBurn = getMonthlyBurn()
|
|
const weeklyBurn = monthlyBurn / 4.33 // Convert monthly to weekly
|
|
|
|
const weeks = []
|
|
let runningBalance = currentBalance
|
|
|
|
for (let week = 1; week <= 13; week++) {
|
|
// Simple projection: subtract weekly burn
|
|
runningBalance -= weeklyBurn
|
|
|
|
const breachesCushion = runningBalance < minCushion
|
|
|
|
weeks.push({
|
|
week,
|
|
balance: runningBalance,
|
|
breachesCushion,
|
|
cushionAmount: minCushion
|
|
})
|
|
}
|
|
|
|
const firstBreachWeek = weeks.find(w => w.breachesCushion)?.week
|
|
const breachesWithin13Weeks = Boolean(firstBreachWeek)
|
|
|
|
return {
|
|
weeks,
|
|
firstBreachWeek,
|
|
breachesWithin13Weeks,
|
|
minCushion,
|
|
weeksUntilBreach: firstBreachWeek || null
|
|
}
|
|
})
|
|
|
|
function getMonthlyBurn() {
|
|
const operatingMode = policiesStore.operatingMode || 'minimum'
|
|
const payrollCost = monthlyPayroll(membersStore.members, operatingMode)
|
|
const oncostPct = policiesStore.payrollOncostPct || 0
|
|
const totalPayroll = payrollCost * (1 + oncostPct / 100)
|
|
const overheadCost = budgetStore.overheadCosts.reduce((sum, cost) => sum + (cost.amount || 0), 0)
|
|
|
|
return totalPayroll + overheadCost
|
|
}
|
|
|
|
function getProgressStatus(current: number, target: number): 'green' | 'yellow' | 'red' {
|
|
if (target === 0) return 'green'
|
|
const pct = (current / target) * 100
|
|
if (pct >= 80) return 'green'
|
|
if (pct >= 50) return 'yellow'
|
|
return 'red'
|
|
}
|
|
|
|
// Alert conditions (matching CLAUDE.md)
|
|
const alerts = computed(() => ({
|
|
savingsBelowTarget: savingsProgress.value.current < savingsProgress.value.target,
|
|
cushionBreach: cushionForecast.value.breachesWithin13Weeks,
|
|
}))
|
|
|
|
return {
|
|
savingsProgress,
|
|
cushionForecast,
|
|
alerts,
|
|
getMonthlyBurn
|
|
}
|
|
} |