Save computed forecast dry/wet on worksheet save; report reads saved values

Worksheet save now persists forecast_dry/forecast_wet per day to day_overrides
so the report page always shows the same totals the user saw on the worksheet,
rather than making a second independent forecasting API call that can return
slightly different values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-21 19:07:29 +00:00
parent 9530125509
commit 2f335c99c5
3 changed files with 36 additions and 14 deletions

View file

@ -60,6 +60,15 @@ export async function initDb() {
occ_pct DECIMAL(5,2)
);
CREATE INDEX IF NOT EXISTS idx_forecast_snapshots_session ON forecast_snapshots(session_id);
`)
// Add saved forecast columns — idempotent for existing deployments
await pool.query(`
ALTER TABLE day_overrides ADD COLUMN IF NOT EXISTS forecast_dry DECIMAL(12,2);
ALTER TABLE day_overrides ADD COLUMN IF NOT EXISTS forecast_wet DECIMAL(12,2);
`)
await pool.query(`
CREATE TABLE IF NOT EXISTS app_settings (
key TEXT PRIMARY KEY,

View file

@ -31,7 +31,7 @@ async function getSessionAndOverrides(year, month) {
const session = sessionRes.rows[0]
const overridesRes = await pool.query(
`SELECT date::text, pickup_rooms, pickup_avg_rate, dry_override, wet_override, actual_accomm
`SELECT date::text, pickup_rooms, pickup_avg_rate, dry_override, wet_override, actual_accomm, forecast_dry, forecast_wet
FROM day_overrides WHERE session_id = $1`,
[session.id]
)
@ -165,17 +165,20 @@ export async function directorsForecastRoutes(fastify) {
for (const o of overrides) {
if (!o.date) continue
await pool.query(
`INSERT INTO day_overrides (session_id, date, pickup_rooms, pickup_avg_rate, dry_override, wet_override, actual_accomm)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`INSERT INTO day_overrides (session_id, date, pickup_rooms, pickup_avg_rate, dry_override, wet_override, actual_accomm, forecast_dry, forecast_wet)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
ON CONFLICT (session_id, date) DO UPDATE
SET pickup_rooms = EXCLUDED.pickup_rooms,
pickup_avg_rate = EXCLUDED.pickup_avg_rate,
dry_override = EXCLUDED.dry_override,
wet_override = EXCLUDED.wet_override,
actual_accomm = EXCLUDED.actual_accomm`,
actual_accomm = EXCLUDED.actual_accomm,
forecast_dry = EXCLUDED.forecast_dry,
forecast_wet = EXCLUDED.forecast_wet`,
[session.id, o.date,
o.pickup_rooms ?? null, o.pickup_avg_rate ?? null,
o.dry_override ?? null, o.wet_override ?? null, o.actual_accomm ?? null]
o.dry_override ?? null, o.wet_override ?? null, o.actual_accomm ?? null,
o.forecast_dry ?? null, o.forecast_wet ?? null]
)
}
}
@ -229,8 +232,12 @@ export async function directorsForecastRoutes(fastify) {
const actualWet = isPast ? (rev.wet?.otb ?? null) : null
const fAccomm = (isPast && actualAccomm != null) ? actualAccomm : otbNetRev + pickupRooms * dayRate
const fDry = ovr.dry_override != null ? parseFloat(ovr.dry_override) : (rev.dry?.forecast ?? 0)
const fWet = ovr.wet_override != null ? parseFloat(ovr.wet_override) : (rev.wet?.forecast ?? 0)
const fDry = ovr.forecast_dry != null ? parseFloat(ovr.forecast_dry)
: ovr.dry_override != null ? parseFloat(ovr.dry_override)
: (rev.dry?.forecast ?? 0)
const fWet = ovr.forecast_wet != null ? parseFloat(ovr.forecast_wet)
: ovr.wet_override != null ? parseFloat(ovr.wet_override)
: (rev.wet?.forecast ?? 0)
totalAvailable += available
totalOtbRooms += otbRooms