Add department budget % allocation
- Budgets page: new dept split section with % inputs, equal-split button, save - Weekly/Monthly: per-dept budget column using dept pcts from actuals response - Backend: dept_budget_pcts in ALLOWED_KEYS; actuals returns dept_pcts map; budgets route adds GET /dept-config + PUT /dept-pcts endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f97772cb72
commit
3e1851fa6d
8 changed files with 204 additions and 33 deletions
|
|
@ -47,6 +47,8 @@ export default function Weekly() {
|
|||
const [netSales, setNetSales] = useState(0)
|
||||
const [pySales, setPySales] = useState(0)
|
||||
const [budget, setBudget] = useState<number | null>(null)
|
||||
const [monthlyBudg, setMonthlyBudg] = useState<number | null>(null)
|
||||
const [deptPcts, setDeptPcts] = useState<Record<string, number>>({})
|
||||
const [showOncosts, setShowOncosts] = useState(true)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
|
@ -61,6 +63,7 @@ export default function Weekly() {
|
|||
])
|
||||
setDepts(actRes.departments)
|
||||
setShowOncosts(actRes.show_oncosts)
|
||||
setDeptPcts(actRes.dept_pcts)
|
||||
setNetSales(salesRes.days.reduce((s, d) => s + d.net_sales, 0))
|
||||
setPySales(salesRes.days.reduce((s, d) => s + d.py_sales, 0))
|
||||
|
||||
|
|
@ -69,14 +72,16 @@ export default function Weekly() {
|
|||
const bRow = (budgetRes.budgets as WageBudget[]).find(b => b.month === monthKey)
|
||||
if (bRow) {
|
||||
const dim = daysInMonthFor(fromStr)
|
||||
// Pro-rata: how many days of this week are <= today
|
||||
const effectiveTo = todayStr < toStr ? todayStr : toStr
|
||||
const effectiveFrom = fromStr > todayStr ? todayStr : fromStr
|
||||
const weekDays = effectiveTo >= effectiveFrom
|
||||
? Math.round((new Date(effectiveTo + 'T00:00:00').getTime() - new Date(effectiveFrom + 'T00:00:00').getTime()) / 86_400_000) + 1
|
||||
: 7
|
||||
setBudget(bRow.budget_amount * (weekDays / dim))
|
||||
const ratio = weekDays / dim
|
||||
setMonthlyBudg(bRow.budget_amount)
|
||||
setBudget(bRow.budget_amount * ratio)
|
||||
} else {
|
||||
setMonthlyBudg(null)
|
||||
setBudget(null)
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
|
|
@ -92,7 +97,14 @@ export default function Weekly() {
|
|||
const next = () => setFromStr(s => addDaysStr(s, 7))
|
||||
const isCurrentWeek = fromStr === mondayOf(new Date())
|
||||
|
||||
// dept_pct × (weekly pro-rata) = dept weekly budget
|
||||
const deptWeekBudget = (deptId: string) =>
|
||||
budget != null && monthlyBudg != null && (deptPcts[deptId] ?? 0) > 0
|
||||
? budget * (deptPcts[deptId] / 100)
|
||||
: null
|
||||
|
||||
const deptTotals = depts.map(dep => ({
|
||||
department_id: dep.department_id,
|
||||
department_name: dep.department_name,
|
||||
cost: Object.values(dep.days).reduce((s, d) => s + d.cost, 0),
|
||||
})).sort((a, b) => b.cost - a.cost)
|
||||
|
|
@ -166,13 +178,14 @@ export default function Weekly() {
|
|||
</thead>
|
||||
<tbody>
|
||||
{deptTotals.map(dep => {
|
||||
const depPct = budget != null && budget > 0 ? (dep.cost / budget) * 100 : null
|
||||
const depBudg = deptWeekBudget(dep.department_id)
|
||||
const depPct = depBudg != null && depBudg > 0 ? (dep.cost / depBudg) * 100 : null
|
||||
const depSPct = netSales > 0 ? (dep.cost / netSales) * 100 : null
|
||||
return (
|
||||
<tr key={dep.department_name}>
|
||||
<td>{dep.department_name}</td>
|
||||
<td className="right">{fmtMoney(dep.cost)}</td>
|
||||
<td className="right">—</td>
|
||||
<td className="right">{depBudg != null ? fmtMoney(depBudg) : '—'}</td>
|
||||
<td className="right">
|
||||
{depPct != null
|
||||
? <span className={`pct-badge ${pctClass(depPct)}`}>{depPct.toFixed(1)}%</span>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue