89 lines
2.8 KiB
TypeScript
89 lines
2.8 KiB
TypeScript
/**
|
||
* Calculates deferred balance and ratio vs monthly payroll
|
||
* Formula: total deferred wage liability ÷ one month of payroll
|
||
*/
|
||
export const useDeferredMetrics = () => {
|
||
const calculateDeferredBalance = (
|
||
members: Array<{ deferredHours: number }>,
|
||
hourlyWage: number
|
||
): number => {
|
||
const totalDeferredHours = members.reduce((sum, member) => sum + (member.deferredHours || 0), 0)
|
||
return totalDeferredHours * hourlyWage
|
||
}
|
||
|
||
const calculateMonthlyPayroll = (
|
||
members: Array<{ targetHours: number }>,
|
||
hourlyWage: number,
|
||
oncostPct: number
|
||
): number => {
|
||
const totalTargetHours = members.reduce((sum, member) => sum + (member.targetHours || 0), 0)
|
||
const grossPayroll = totalTargetHours * hourlyWage
|
||
return Math.round(grossPayroll * (1 + oncostPct / 100))
|
||
}
|
||
|
||
const calculateDeferredRatio = (
|
||
deferredBalance: number,
|
||
monthlyPayroll: number
|
||
): number => {
|
||
if (monthlyPayroll <= 0) return 0
|
||
return deferredBalance / monthlyPayroll
|
||
}
|
||
|
||
const getDeferredRatioStatus = (ratio: number): 'green' | 'yellow' | 'red' => {
|
||
if (ratio > 1.5) return 'red' // Flag red if >1.5× monthly payroll
|
||
if (ratio > 1.0) return 'yellow'
|
||
return 'green'
|
||
}
|
||
|
||
const getDeferredRatioMessage = (status: 'green' | 'yellow' | 'red'): string => {
|
||
switch (status) {
|
||
case 'red':
|
||
return 'Deferred balance is high. Consider repaying or reducing scope.'
|
||
case 'yellow':
|
||
return 'Deferred balance is building up. Monitor closely.'
|
||
case 'green':
|
||
return 'Deferred balance is manageable.'
|
||
}
|
||
}
|
||
|
||
const checkDeferredCap = (
|
||
memberHours: number,
|
||
capHoursPerQtr: number,
|
||
quarterProgress: number
|
||
): { withinCap: boolean; remainingHours: number } => {
|
||
const quarterlyLimit = capHoursPerQtr * quarterProgress
|
||
const withinCap = memberHours <= quarterlyLimit
|
||
const remainingHours = Math.max(0, quarterlyLimit - memberHours)
|
||
|
||
return { withinCap, remainingHours }
|
||
}
|
||
|
||
const analyzeDeferredMetrics = (
|
||
members: Array<{ deferredHours?: number; targetHours?: number }>,
|
||
hourlyWage: number,
|
||
oncostPct: number
|
||
) => {
|
||
const deferredBalance = calculateDeferredBalance(members, hourlyWage)
|
||
const monthlyPayroll = calculateMonthlyPayroll(members, hourlyWage, oncostPct)
|
||
const ratio = calculateDeferredRatio(deferredBalance, monthlyPayroll)
|
||
const status = getDeferredRatioStatus(ratio)
|
||
|
||
return {
|
||
deferredBalance,
|
||
monthlyPayroll,
|
||
ratio: Number(ratio.toFixed(2)),
|
||
status,
|
||
message: getDeferredRatioMessage(status)
|
||
}
|
||
}
|
||
|
||
return {
|
||
calculateDeferredBalance,
|
||
calculateMonthlyPayroll,
|
||
calculateDeferredRatio,
|
||
getDeferredRatioStatus,
|
||
getDeferredRatioMessage,
|
||
checkDeferredCap,
|
||
analyzeDeferredMetrics
|
||
}
|
||
}
|