Yesterday cutoff for actuals; two-row monthly summary cards

Both Weekly and Monthly now cut off actuals at yesterday (not today) to avoid
partial clockins and open timesheets skewing the WTD/MTD figures and % net sales.
PY comparison periods also cap at the equivalent yesterday for fair comparison.

Monthly rota forecast removed — all future days use prior-week same-day actuals
from yesterday back, which is more reliable than partially-published future rotas.

Monthly summary cards split into two rows for current month:
  Row 1 (MTD to yesterday): Actual MTD · Budget pro-rata · % Budget MTD · % Net Sales MTD
  Row 2 (Full month):       Forecast EOM · Monthly Budget · % Budget (Forecast) · % Net Sales (OTB)
Net sales fetched for full month so row 2 % net sales uses OTB forecast revenue.
Past months show one row (full month actuals only).

Weekly: budget pro-rata now counts days Monday to yesterday for current week;
dept totals filtered to yesterday for current week.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-23 12:36:42 +00:00
parent d04cd1e82c
commit 5b243b3ef8
3 changed files with 204 additions and 136 deletions

View file

@ -51,11 +51,13 @@ export default function Weekly() {
const toStr = addDaysStr(fromStr, 6)
const todayStr = localStr(new Date())
const yesterday = new Date(); yesterday.setDate(yesterday.getDate() - 1)
const yesterdayStr = localStr(yesterday)
const isCurrentWeek = fromStr === mondayOf(new Date())
const pyFromStr = addDaysStr(fromStr, -364)
// For current (partial) week cap PY at equivalent elapsed day — fair WTD comparison
// Cut-off at yesterday: avoids partial clockins/open timesheets skewing today's figures
const daysElapsed = isCurrentWeek
? Math.round((new Date(todayStr + 'T00:00:00').getTime() - new Date(fromStr + 'T00:00:00').getTime()) / 86_400_000)
? Math.round((new Date(yesterdayStr + 'T00:00:00').getTime() - new Date(fromStr + 'T00:00:00').getTime()) / 86_400_000)
: 6
const pyToStr = addDaysStr(fromStr, -364 + daysElapsed)
@ -87,12 +89,16 @@ 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))
// For current week only sum PY sales for elapsed days (WTD = apples to apples)
// Current week cut-off at yesterday to avoid partial clockins
const isCurrentWk = fromStr === mondayOf(new Date())
setNetSales(
isCurrentWk
? salesRes.days.filter(d => d.date <= yesterdayStr).reduce((s, d) => s + d.net_sales, 0)
: salesRes.days.reduce((s, d) => s + d.net_sales, 0)
)
setPySales(
isCurrentWk
? salesRes.days.filter(d => d.date <= todayStr).reduce((s, d) => s + d.py_sales, 0)
? salesRes.days.filter(d => d.date <= yesterdayStr).reduce((s, d) => s + d.py_sales, 0)
: salesRes.days.reduce((s, d) => s + d.py_sales, 0)
)
@ -110,10 +116,10 @@ export default function Weekly() {
const bRow = (budgetRes.budgets as WageBudget[]).find(b => b.month === monthKey)
if (bRow) {
const dim = daysInMonthFor(fromStr)
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
const cutoff = isCurrentWk ? yesterdayStr : toStr
const effectiveTo = cutoff < toStr ? cutoff : toStr
const weekDays = effectiveTo >= fromStr
? Math.round((new Date(effectiveTo + 'T00:00:00').getTime() - new Date(fromStr + 'T00:00:00').getTime()) / 86_400_000) + 1
: 7
const ratio = weekDays / dim
setMonthlyBudg(bRow.budget_amount)
@ -151,7 +157,9 @@ export default function Weekly() {
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),
cost: Object.entries(dep.days)
.filter(([d]) => !isCurrentWeek || d <= yesterdayStr)
.reduce((s, [, v]) => s + v.cost, 0),
})).sort((a, b) => b.cost - a.cost)
const totalWages = deptTotals.reduce((s, d) => s + d.cost, 0)