diff --git a/backend/src/lib/workforce.js b/backend/src/lib/workforce.js index 8930edb..ab251bd 100644 --- a/backend/src/lib/workforce.js +++ b/backend/src/lib/workforce.js @@ -264,7 +264,7 @@ export async function runBackfill(onProgress, signal) { endDate.setDate(endDate.getDate() - 1) const startDate = new Date(today) - startDate.setMonth(startDate.getMonth() - 13) + startDate.setMonth(startDate.getMonth() - 25) const totalDays = Math.max(1, Math.ceil((endDate - startDate) / 86_400_000)) let processedDays = 0 diff --git a/frontend/src/pages/Monthly.tsx b/frontend/src/pages/Monthly.tsx index 82d3057..d84e9b8 100644 --- a/frontend/src/pages/Monthly.tsx +++ b/frontend/src/pages/Monthly.tsx @@ -31,7 +31,8 @@ export default function Monthly() { const [netSales, setNetSales] = useState(0) const [pySales, setPySales] = useState(0) const [pyWages, setPyWages] = useState(null) - const [pyDepts, setPyDepts] = useState<{id: string; name: string; cost: number}[]>([]) + const [pyWagesFull, setPyWagesFull] = useState(null) + const [pyDepts, setPyDepts] = useState<{id: string; name: string; cost: number; costFull: number}[]>([]) const [pyTableOpen, setPyTableOpen] = useState(false) const [budget, setBudget] = useState(null) const [deptPcts, setDeptPcts] = useState>({}) @@ -49,12 +50,17 @@ export default function Monthly() { const isCurrentMonth = year === todayYear && month === todayMonth const salesTo = isCurrentMonth ? todayStr : toStr const schedFrom = todayStr < toStr ? todayStr : toStr - // Prior-year equivalent period + // Current Mon–Sun: only use rota within this window; beyond it use prior-week actuals + const currentWeekEndStr = (() => { + const d = new Date(todayStr + 'T00:00:00') + const day = d.getDay() + d.setDate(d.getDate() + (day === 0 ? 0 : 7 - day)) + return fmt(d) + })() + // Prior-year: always fetch full month so we can show PY EOM alongside PY MTD const pyDim = daysInMonth(year - 1, month) const pyFromStr = `${year - 1}-${String(month).padStart(2, '0')}-01` - const pyToStr = isCurrentMonth - ? `${year - 1}-${String(month).padStart(2, '0')}-${String(Math.min(parseInt(todayStr.slice(8)), pyDim)).padStart(2, '0')}` - : `${year - 1}-${String(month).padStart(2, '0')}-${String(pyDim).padStart(2, '0')}` + const pyToStr = `${year - 1}-${String(month).padStart(2, '0')}-${String(pyDim).padStart(2, '0')}` const load = useCallback(async () => { setLoading(true); setError(null) @@ -82,14 +88,21 @@ export default function Monthly() { setNetSales(salesRes.days.reduce((s, d) => s + d.net_sales, 0)) setPySales(salesRes.days.reduce((s, d) => s + d.py_sales, 0)) - const pyDeptList = pyActRes.departments.map(dep => ({ - id: dep.department_id, - name: dep.department_name, - cost: Object.values(dep.days).reduce((s, d) => s + d.cost, 0), - })).filter(d => d.cost > 0).sort((a, b) => b.cost - a.cost) + // PY MTD limit: cap at same day number as today (or month end for past months) + const todayDay = parseInt(todayStr.slice(8)) + const pyMtdLimit = isCurrentMonth + ? `${year - 1}-${String(month).padStart(2, '0')}-${String(Math.min(todayDay, pyDim)).padStart(2, '0')}` + : pyToStr + const pyDeptList = pyActRes.departments.map(dep => { + const mtdCost = Object.entries(dep.days).filter(([d]) => d <= pyMtdLimit).reduce((s, [, v]) => s + v.cost, 0) + const fullCost = Object.values(dep.days).reduce((s, v) => s + v.cost, 0) + return { id: dep.department_id, name: dep.department_name, cost: mtdCost, costFull: fullCost } + }).filter(d => d.cost > 0 || d.costFull > 0).sort((a, b) => b.costFull - a.costFull) setPyDepts(pyDeptList) - const pyTotal = pyDeptList.reduce((s, d) => s + d.cost, 0) - setPyWages(pyTotal > 0 ? pyTotal : null) + const pyTotalMTD = pyDeptList.reduce((s, d) => s + d.cost, 0) + const pyTotalFull = pyDeptList.reduce((s, d) => s + d.costFull, 0) + setPyWages(pyTotalMTD > 0 ? pyTotalMTD : null) + setPyWagesFull(pyTotalFull > 0 ? pyTotalFull : null) const bRow = budRes.budgets.find((b: WageBudget) => b.month === fromStr) setBudget(bRow ? bRow.budget_amount : null) @@ -129,8 +142,11 @@ export default function Monthly() { for (let day = 1; day <= dim; day++) { const dateStr = `${monthStr}-${String(day).padStart(2, '0')}` if (dateStr <= todayStr) continue - const rota = getSchCost(dep.department_id, dateStr) - if (rota != null) { forecastRem += rota; continue } + // Only trust rota for current Mon–Sun week; beyond that use prior-week actuals + if (dateStr <= currentWeekEndStr) { + const rota = getSchCost(dep.department_id, dateStr) + if (rota != null) { forecastRem += rota; continue } + } const priorStr = fmt(addDays(new Date(dateStr + 'T00:00:00'), -7)) const priorCost = dep.days[priorStr]?.cost if (priorCost != null) forecastRem += priorCost @@ -178,7 +194,7 @@ export default function Monthly() { if (dateStr <= todayStr) { deptCost += srcDep?.days[dateStr]?.cost ?? 0 } else { - const rota = getSchCost(dep.department_id, dateStr) + const rota = dateStr <= currentWeekEndStr ? getSchCost(dep.department_id, dateStr) : null if (rota != null) { deptCost += rota } else { @@ -219,7 +235,11 @@ export default function Monthly() {
Forecast EOM
{fmtMoney(totalForecast)}
-
rota + prior-week actual
+
+ {pyWagesFull != null + ? `PY ${fmtMoney(pyWagesFull)}${pyPct(totalForecast, pyWagesFull)}` + : 'rota + prior-week actual'} +
)}
@@ -228,10 +248,8 @@ export default function Monthly() {
% Budget {isCurrentMonth ? '(Forecast)' : ''}
-
- {pctBudget != null - ? {fmtDelta(pctBudget)} - : '—'} +
+ {pctBudget != null ? fmtDelta(pctBudget) : '—'}
{variance != null && (
0 ? 'variance-over' : 'variance-under'}`}> diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index af97677..5cb23c9 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -97,7 +97,7 @@ export default function SettingsPage() { } const handleBackfill = async () => { - if (!confirm('Start deep backfill? This will fetch ~13 months of Workforce data and may take a few minutes.')) return + if (!confirm('Start deep backfill? This will fetch ~25 months of Workforce data and may take several minutes.')) return setBackfillProg({ processed: 0, total: 1, current: '…' }) setError(null) @@ -240,7 +240,7 @@ export default function SettingsPage() { {backfillProg && (
% vs Budget
-
- {pctBudget != null - ? {fmtDelta(pctBudget)} - : '—'} +
+ {pctBudget != null ? fmtDelta(pctBudget) : '—'}