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:
parent
9530125509
commit
2f335c99c5
3 changed files with 36 additions and 14 deletions
|
|
@ -60,6 +60,15 @@ export async function initDb() {
|
||||||
occ_pct DECIMAL(5,2)
|
occ_pct DECIMAL(5,2)
|
||||||
);
|
);
|
||||||
CREATE INDEX IF NOT EXISTS idx_forecast_snapshots_session ON forecast_snapshots(session_id);
|
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 (
|
CREATE TABLE IF NOT EXISTS app_settings (
|
||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ async function getSessionAndOverrides(year, month) {
|
||||||
const session = sessionRes.rows[0]
|
const session = sessionRes.rows[0]
|
||||||
|
|
||||||
const overridesRes = await pool.query(
|
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`,
|
FROM day_overrides WHERE session_id = $1`,
|
||||||
[session.id]
|
[session.id]
|
||||||
)
|
)
|
||||||
|
|
@ -165,17 +165,20 @@ export async function directorsForecastRoutes(fastify) {
|
||||||
for (const o of overrides) {
|
for (const o of overrides) {
|
||||||
if (!o.date) continue
|
if (!o.date) continue
|
||||||
await pool.query(
|
await pool.query(
|
||||||
`INSERT INTO day_overrides (session_id, date, pickup_rooms, pickup_avg_rate, dry_override, wet_override, actual_accomm)
|
`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)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
ON CONFLICT (session_id, date) DO UPDATE
|
ON CONFLICT (session_id, date) DO UPDATE
|
||||||
SET pickup_rooms = EXCLUDED.pickup_rooms,
|
SET pickup_rooms = EXCLUDED.pickup_rooms,
|
||||||
pickup_avg_rate = EXCLUDED.pickup_avg_rate,
|
pickup_avg_rate = EXCLUDED.pickup_avg_rate,
|
||||||
dry_override = EXCLUDED.dry_override,
|
dry_override = EXCLUDED.dry_override,
|
||||||
wet_override = EXCLUDED.wet_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,
|
[session.id, o.date,
|
||||||
o.pickup_rooms ?? null, o.pickup_avg_rate ?? null,
|
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 actualWet = isPast ? (rev.wet?.otb ?? null) : null
|
||||||
|
|
||||||
const fAccomm = (isPast && actualAccomm != null) ? actualAccomm : otbNetRev + pickupRooms * dayRate
|
const fAccomm = (isPast && actualAccomm != null) ? actualAccomm : otbNetRev + pickupRooms * dayRate
|
||||||
const fDry = ovr.dry_override != null ? parseFloat(ovr.dry_override) : (rev.dry?.forecast ?? 0)
|
const fDry = ovr.forecast_dry != null ? parseFloat(ovr.forecast_dry)
|
||||||
const fWet = ovr.wet_override != null ? parseFloat(ovr.wet_override) : (rev.wet?.forecast ?? 0)
|
: 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
|
totalAvailable += available
|
||||||
totalOtbRooms += otbRooms
|
totalOtbRooms += otbRooms
|
||||||
|
|
|
||||||
|
|
@ -108,13 +108,19 @@ function WorksheetTab({ year, month, dowAlign }: { year: number; month: number;
|
||||||
if (!canEdit) return
|
if (!canEdit) return
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
const ovrList = Object.entries(overrides).map(([date, o]) => ({
|
const dayMap = new Map(data!.days.map(d => [d.date, d]))
|
||||||
|
const ovrList = Object.entries(overrides).map(([date, o]) => {
|
||||||
|
const computed = computedDay(dayMap.get(date)!)
|
||||||
|
return {
|
||||||
date,
|
date,
|
||||||
pickup_rooms: o.pickup_rooms !== '' ? parseInt(o.pickup_rooms) : null,
|
pickup_rooms: o.pickup_rooms !== '' ? parseInt(o.pickup_rooms) : null,
|
||||||
pickup_avg_rate: o.pickup_avg_rate !== '' ? parseFloat(o.pickup_avg_rate) : null,
|
pickup_avg_rate: o.pickup_avg_rate !== '' ? parseFloat(o.pickup_avg_rate) : null,
|
||||||
dry_override: o.dry_override !== '' ? parseFloat(o.dry_override) : null,
|
dry_override: o.dry_override !== '' ? parseFloat(o.dry_override) : null,
|
||||||
wet_override: o.wet_override !== '' ? parseFloat(o.wet_override) : null,
|
wet_override: o.wet_override !== '' ? parseFloat(o.wet_override) : null,
|
||||||
}))
|
forecast_dry: computed.dry,
|
||||||
|
forecast_wet: computed.wet,
|
||||||
|
}
|
||||||
|
})
|
||||||
await dfSaveWorksheet(year, month, { pickup_avg_rate: parseFloat(sessionRate) || 135, overrides: ovrList })
|
await dfSaveWorksheet(year, month, { pickup_avg_rate: parseFloat(sessionRate) || 135, overrides: ovrList })
|
||||||
setDirty(false)
|
setDirty(false)
|
||||||
await load()
|
await load()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue