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
|
|
@ -8,21 +8,23 @@ export async function actualsRoutes(fastify) {
|
|||
const { from, to } = request.query
|
||||
if (!from || !to) return reply.status(400).send({ error: 'from and to required' })
|
||||
|
||||
const showOncosts = (await getConfig('show_oncosts')) !== 'false'
|
||||
const costCol = showOncosts ? 'total_cost' : 'base_cost'
|
||||
const [showOncostsRaw, pctsRaw, dbRes] = await Promise.all([
|
||||
getConfig('show_oncosts'),
|
||||
getConfig('dept_budget_pcts'),
|
||||
pool.query(
|
||||
`SELECT date, department_id, department_name,
|
||||
base_cost, total_cost, shift_count
|
||||
FROM wage_actuals
|
||||
WHERE date >= $1 AND date <= $2
|
||||
ORDER BY date, department_name`,
|
||||
[from, to]
|
||||
),
|
||||
])
|
||||
|
||||
const res = await pool.query(
|
||||
`SELECT date, department_id, department_name,
|
||||
base_cost, total_cost, shift_count
|
||||
FROM wage_actuals
|
||||
WHERE date >= $1 AND date <= $2
|
||||
ORDER BY date, department_name`,
|
||||
[from, to]
|
||||
)
|
||||
const showOncosts = showOncostsRaw !== 'false'
|
||||
|
||||
// Group by department, emit { dept_id, dept_name, days: { 'YYYY-MM-DD': cost } }
|
||||
const deptMap = {}
|
||||
for (const row of res.rows) {
|
||||
for (const row of dbRes.rows) {
|
||||
const d = row.date.toISOString().slice(0, 10)
|
||||
if (!deptMap[row.department_id]) {
|
||||
deptMap[row.department_id] = {
|
||||
|
|
@ -39,6 +41,11 @@ export async function actualsRoutes(fastify) {
|
|||
}
|
||||
}
|
||||
|
||||
return { departments: Object.values(deptMap), show_oncosts: showOncosts }
|
||||
const dept_pcts = {}
|
||||
if (pctsRaw) {
|
||||
try { for (const item of JSON.parse(pctsRaw)) dept_pcts[item.id] = item.pct } catch {}
|
||||
}
|
||||
|
||||
return { departments: Object.values(deptMap), show_oncosts: showOncosts, dept_pcts }
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { requireAuth, requireCap } from '../auth.js'
|
||||
import { pool } from '../db.js'
|
||||
import { pool, getConfig, setConfig } from '../db.js'
|
||||
|
||||
export async function budgetsRoutes(fastify) {
|
||||
fastify.addHook('preHandler', requireAuth)
|
||||
|
|
@ -25,7 +25,6 @@ export async function budgetsRoutes(fastify) {
|
|||
return reply.status(400).send({ error: 'budget_amount must be a non-negative number' })
|
||||
}
|
||||
|
||||
// Store as first day of month
|
||||
const monthDate = `${month}-01`
|
||||
await pool.query(
|
||||
`INSERT INTO wage_budgets (month, budget_amount, updated_at) VALUES ($1, $2, NOW())
|
||||
|
|
@ -34,4 +33,31 @@ export async function budgetsRoutes(fastify) {
|
|||
)
|
||||
return { ok: true }
|
||||
})
|
||||
|
||||
// Dept list + saved pcts — used by Budgets page (view cap)
|
||||
fastify.get('/api/budgets/dept-config', { preHandler: requireCap('view') }, async () => {
|
||||
const [deptsRaw, pctsRaw] = await Promise.all([
|
||||
getConfig('departments'),
|
||||
getConfig('dept_budget_pcts'),
|
||||
])
|
||||
|
||||
let depts = []
|
||||
if (deptsRaw) {
|
||||
try { depts = JSON.parse(deptsRaw).filter(d => d.enabled !== false) } catch {}
|
||||
}
|
||||
|
||||
const pcts = {}
|
||||
if (pctsRaw) {
|
||||
try { for (const item of JSON.parse(pctsRaw)) pcts[item.id] = item.pct } catch {}
|
||||
}
|
||||
|
||||
return { depts: depts.map(d => ({ id: d.id, name: d.name, pct: pcts[d.id] ?? 0 })) }
|
||||
})
|
||||
|
||||
fastify.put('/api/budgets/dept-pcts', { preHandler: requireCap('budget') }, async (request, reply) => {
|
||||
const { pcts } = request.body || {}
|
||||
if (!Array.isArray(pcts)) return reply.status(400).send({ error: 'pcts must be an array' })
|
||||
await setConfig('dept_budget_pcts', JSON.stringify(pcts))
|
||||
return { ok: true }
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { requireAuth, requireCap } from '../auth.js'
|
|||
import { pool, getConfig, setConfig } from '../db.js'
|
||||
|
||||
const ALLOWED_KEYS = new Set([
|
||||
'forecasting_url', 'forecasting_api_key', 'show_oncosts', 'departments',
|
||||
'forecasting_url', 'forecasting_api_key', 'show_oncosts', 'departments', 'dept_budget_pcts',
|
||||
])
|
||||
|
||||
export async function settingsRoutes(fastify) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue