From 73d2c713d4c0ba0d1853fd3986baafd59709ef7a Mon Sep 17 00:00:00 2001 From: jtricerolph Date: Mon, 3 Aug 2026 09:31:08 +0000 Subject: [PATCH] Fix F&B last-7-days fill to span month boundary "Fill F&B from last 7 days" only drew from the current month's elapsed days, so early in a month (e.g. the 3rd) it only had 1-2 actual days to repeat instead of a true trailing 7-day window. Add a dedicated last7-fnb endpoint that fetches a real 7-day window ending yesterday, reaching back into the prior month when needed. --- backend/src/routes/directors-forecast.js | 29 ++++++++++++++++++++++++ frontend/src/api.ts | 12 ++++++++++ frontend/src/pages/DirectorsForecast.tsx | 15 ++++++++---- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/backend/src/routes/directors-forecast.js b/backend/src/routes/directors-forecast.js index cf6b901e..3c6016bb 100644 --- a/backend/src/routes/directors-forecast.js +++ b/backend/src/routes/directors-forecast.js @@ -57,9 +57,38 @@ async function fetchMonthFromApi(year, month, lastDayNum, dowAlign = true) { return { revMap, roomsMap } } +async function fetchLast7ActualsFnB() { + const today = new Date() + const end = new Date(today) + end.setDate(end.getDate() - 1) + const start = new Date(end) + start.setDate(start.getDate() - 6) + const startDate = start.toISOString().split('T')[0] + + const revData = await fcFetch(`/forecast/revenue?start_date=${startDate}&days=7&type=all`) + return revData.data + .slice() + .sort((a, b) => a.date.localeCompare(b.date)) + .map(d => ({ + date: d.date, + actual_dry: d.dry?.otb ?? null, + actual_wet: d.wet?.otb ?? null, + forecast_dry: d.dry?.forecast ?? null, + forecast_wet: d.wet?.forecast ?? null, + })) +} + export async function directorsForecastRoutes(fastify) { fastify.addHook('preHandler', requireAuth) + // GET /api/directors-forecast/last7-fnb — trailing 7 actual F&B days ending + // yesterday, spanning a month boundary. Used by "fill F&B from last 7 days", + // which otherwise only has the current month's elapsed days to draw from. + fastify.get('/api/directors-forecast/last7-fnb', async (request, reply) => { + const days = await fetchLast7ActualsFnB() + return { days } + }) + // GET /api/directors-forecast/worksheet/:year/:month fastify.get('/api/directors-forecast/worksheet/:year/:month', async (request, reply) => { const year = parseInt(request.params.year) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 01393c94..9101ddd0 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -52,6 +52,18 @@ export function dfGetReport(year: number, month: number, dowAlign = true): Promi return request(`/directors-forecast/report/${year}/${month}?dow_align=${dowAlign}`) } +export type Last7FnBDay = { + date: string + actual_dry: number | null + actual_wet: number | null + forecast_dry: number | null + forecast_wet: number | null +} + +export function dfGetLast7Fnb(): Promise<{ days: Last7FnBDay[] }> { + return request<{ days: Last7FnBDay[] }>('/directors-forecast/last7-fnb') +} + export function dfSaveSnapshot(year: number, month: number, data: object) { return request(`/directors-forecast/report/${year}/${month}/snapshot`, { method: 'POST', body: JSON.stringify(data), diff --git a/frontend/src/pages/DirectorsForecast.tsx b/frontend/src/pages/DirectorsForecast.tsx index 973d53f2..9037e314 100644 --- a/frontend/src/pages/DirectorsForecast.tsx +++ b/frontend/src/pages/DirectorsForecast.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react' import { useFrameViewport } from '../hooks/useFrameViewport' import { Save, RefreshCw, Camera, Trash2, Printer, Repeat2 } from 'lucide-react' import { useAuth } from '../components/AuthGate' -import { dfGetWorksheet, dfSaveWorksheet, dfGetReport, dfSaveSnapshot, dfDeleteSnapshot } from '../api' +import { dfGetWorksheet, dfSaveWorksheet, dfGetReport, dfSaveSnapshot, dfDeleteSnapshot, dfGetLast7Fnb } from '../api' import type { WorksheetDay, WorksheetData, ForecastReportData, ForecastSnapshot } from '../types' import { can, DOW_NAMES, MONTH_NAMES } from '../types' @@ -82,11 +82,16 @@ function WorksheetTab({ year, month, dowAlign }: { year: number; month: number; setDirty(true) } - function fillFnBFromLast7() { + async function fillFnBFromLast7() { if (!data) return - const last7 = data.days - .filter(d => d.is_past && (d.actual_dry != null || d.actual_wet != null)) - .slice(-7) + let last7 + try { + const res = await dfGetLast7Fnb() + last7 = res.days.filter(d => d.actual_dry != null || d.actual_wet != null) + } catch { + setError('Failed to load last 7 days of actuals.') + return + } if (!last7.length) return setOverrides(prev => { const next = { ...prev }