Fix stale employee rows in wage breakdown after shift edits/reassignment

syncActuals() recomputes the full date range from Workforce on every run
but only ever upserted rows, so a shift that moved department or was
edited/deleted since the last sync left its old wage_actuals_detail row
in place — showing a "ghost" employee in the dept breakdown whose cost
no longer matched the (correctly overwritten) department total.

Now deletes existing wage_actuals/wage_actuals_detail rows for the
synced date range before reinserting, so removed/reassigned shifts
don't linger.
This commit is contained in:
jtricerolph 2026-07-24 12:23:58 +00:00
parent a1a8900196
commit 158d8f1676

View file

@ -180,16 +180,17 @@ export async function syncActuals(from, to) {
} }
} }
// syncActuals recomputes the entire [from, to] range from the API on every call, so any
// shift that vanished or moved department since the last sync (edited, deleted, reassigned)
// must have its old row cleared here — otherwise it lingers as a stale orphan (department
// totals get overwritten to the new figure, but the old per-employee detail row never does).
await pool.query(`DELETE FROM wage_actuals WHERE date >= $1 AND date <= $2`, [from, to])
await pool.query(`DELETE FROM wage_actuals_detail WHERE date >= $1 AND date <= $2`, [from, to])
for (const row of Object.values(byDateDept)) { for (const row of Object.values(byDateDept)) {
await pool.query( await pool.query(
`INSERT INTO wage_actuals (date, department_id, department_name, base_cost, total_cost, shift_count, cached_at) `INSERT INTO wage_actuals (date, department_id, department_name, base_cost, total_cost, shift_count, cached_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW()) VALUES ($1, $2, $3, $4, $5, $6, NOW())`,
ON CONFLICT (date, department_id) DO UPDATE SET
department_name = EXCLUDED.department_name,
base_cost = EXCLUDED.base_cost,
total_cost = EXCLUDED.total_cost,
shift_count = EXCLUDED.shift_count,
cached_at = NOW()`,
[row.date, row.department_id, row.department_name, [row.date, row.department_id, row.department_name,
row.base_cost.toFixed(2), row.total_cost.toFixed(2), row.shift_count] row.base_cost.toFixed(2), row.total_cost.toFixed(2), row.shift_count]
) )
@ -198,13 +199,7 @@ export async function syncActuals(from, to) {
for (const row of Object.values(byDateDeptEmp)) { for (const row of Object.values(byDateDeptEmp)) {
await pool.query( await pool.query(
`INSERT INTO wage_actuals_detail (date, department_id, employee_id, employee_name, base_cost, total_cost, shift_count, cached_at) `INSERT INTO wage_actuals_detail (date, department_id, employee_id, employee_name, base_cost, total_cost, shift_count, cached_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW()) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())`,
ON CONFLICT (date, department_id, employee_id) DO UPDATE SET
employee_name = EXCLUDED.employee_name,
base_cost = EXCLUDED.base_cost,
total_cost = EXCLUDED.total_cost,
shift_count = EXCLUDED.shift_count,
cached_at = NOW()`,
[row.date, row.department_id, row.employee_id, row.employee_name, [row.date, row.department_id, row.employee_id, row.employee_name,
row.base_cost.toFixed(2), row.total_cost.toFixed(2), row.shift_count] row.base_cost.toFixed(2), row.total_cost.toFixed(2), row.shift_count]
) )