Fix directors forecast report counting stale pickup for past days

The report endpoint's totalPickup/totalRooms summed every day's
pickup override regardless of whether the day had already passed,
even though a past day's actual accommodation revenue already
supersedes it (fAccomm ignores pickup for those days). This
overstated Probable Pickup and the month's rooms estimate. Also
fixed pickup.accomm to sum each day's own rate instead of
multiplying the total by a flat session rate.
This commit is contained in:
jtricerolph 2026-07-27 16:38:53 +00:00
parent 9e9e1d7400
commit 932302ebe2

View file

@ -104,8 +104,10 @@ export async function directorsForecastRoutes(fastify) {
const actualDry = isPast ? (rev.dry?.otb ?? null) : null
const actualWet = isPast ? (rev.wet?.otb ?? null) : null
// User overrides
const pickupRooms = ovr.pickup_rooms != null ? parseInt(ovr.pickup_rooms) : 0
// User overrides — once a day is past and has an actual, a leftover
// pickup override from before the day arrived is stale and shouldn't count.
const pickupApplies = !(isPast && actualAccomm != null)
const pickupRooms = pickupApplies && ovr.pickup_rooms != null ? parseInt(ovr.pickup_rooms) : 0
const dayRate = ovr.pickup_avg_rate != null ? parseFloat(ovr.pickup_avg_rate) : rate
// Forecast values
@ -209,7 +211,7 @@ export async function directorsForecastRoutes(fastify) {
'29end': { accomm: 0, dry: 0, wet: 0, rooms: 0 },
}
let totalAvailable = 0, totalOtbRooms = 0, totalOtbNetRev = 0, totalPickup = 0
let totalAvailable = 0, totalOtbRooms = 0, totalOtbNetRev = 0, totalPickup = 0, totalPickupRevenue = 0
let totalAccomm = 0, totalDry = 0, totalWet = 0
let totalBudgetAccomm = 0, totalBudgetDry = 0, totalBudgetWet = 0
let totalLyAccomm = 0, totalLyDry = 0, totalLyWet = 0, totalLyRooms = 0
@ -224,14 +226,19 @@ export async function directorsForecastRoutes(fastify) {
const available = rm.available_rooms ?? 0
const otbRooms = rm.otb_rooms ?? 0
const otbNetRev = rev.accom?.otb ?? 0
const pickupRooms = ovr.pickup_rooms != null ? parseInt(ovr.pickup_rooms) : 0
const dayRate = ovr.pickup_avg_rate != null ? parseFloat(ovr.pickup_avg_rate) : sessionRate
const totalRooms = otbRooms + pickupRooms
const actualAccomm = isPast ? (rev.accom?.otb ?? null) : null
const actualDry = isPast ? (rev.dry?.otb ?? null) : null
const actualWet = isPast ? (rev.wet?.otb ?? null) : null
// Once a day is past and has an actual, it's realised — any leftover
// pickup override from before the day arrived is stale and must not
// still count as "probable" pickup (it never contributed to fAccomm below).
const pickupApplies = !(isPast && actualAccomm != null)
const pickupRooms = pickupApplies && ovr.pickup_rooms != null ? parseInt(ovr.pickup_rooms) : 0
const dayRate = ovr.pickup_avg_rate != null ? parseFloat(ovr.pickup_avg_rate) : sessionRate
const totalRooms = otbRooms + pickupRooms
const fAccomm = (isPast && actualAccomm != null) ? actualAccomm : otbNetRev + pickupRooms * dayRate
const fDry = ovr.forecast_dry != null ? parseFloat(ovr.forecast_dry)
: ovr.dry_override != null ? parseFloat(ovr.dry_override)
@ -244,6 +251,7 @@ export async function directorsForecastRoutes(fastify) {
totalOtbRooms += otbRooms
totalOtbNetRev += otbNetRev
totalPickup += pickupRooms
totalPickupRevenue += pickupRooms * dayRate
totalAccomm += fAccomm
totalDry += fDry
totalWet += fWet
@ -312,7 +320,7 @@ export async function directorsForecastRoutes(fastify) {
arr: totalOtbRooms > 0 ? totalOtbNetRev / totalOtbRooms : null,
revpar: totalAvailable > 0 ? totalOtbNetRev / totalAvailable : null,
},
pickup: { rooms: totalPickup, accomm: totalPickup * sessionRate, avg_rate: sessionRate },
pickup: { rooms: totalPickup, accomm: totalPickupRevenue, avg_rate: totalPickup > 0 ? totalPickupRevenue / totalPickup : sessionRate },
weekly: Object.entries(weekBands).map(([label, v]) => ({ label, ...v })),
snapshots: snapshotsRes.rows,
utilities,