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 actualDry = isPast ? (rev.dry?.otb ?? null) : null
const actualWet = isPast ? (rev.wet?.otb ?? null) : null const actualWet = isPast ? (rev.wet?.otb ?? null) : null
// User overrides // User overrides — once a day is past and has an actual, a leftover
const pickupRooms = ovr.pickup_rooms != null ? parseInt(ovr.pickup_rooms) : 0 // 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 const dayRate = ovr.pickup_avg_rate != null ? parseFloat(ovr.pickup_avg_rate) : rate
// Forecast values // Forecast values
@ -209,7 +211,7 @@ export async function directorsForecastRoutes(fastify) {
'29end': { accomm: 0, dry: 0, wet: 0, rooms: 0 }, '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 totalAccomm = 0, totalDry = 0, totalWet = 0
let totalBudgetAccomm = 0, totalBudgetDry = 0, totalBudgetWet = 0 let totalBudgetAccomm = 0, totalBudgetDry = 0, totalBudgetWet = 0
let totalLyAccomm = 0, totalLyDry = 0, totalLyWet = 0, totalLyRooms = 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 available = rm.available_rooms ?? 0
const otbRooms = rm.otb_rooms ?? 0 const otbRooms = rm.otb_rooms ?? 0
const otbNetRev = rev.accom?.otb ?? 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 actualAccomm = isPast ? (rev.accom?.otb ?? null) : null
const actualDry = isPast ? (rev.dry?.otb ?? null) : null const actualDry = isPast ? (rev.dry?.otb ?? null) : null
const actualWet = isPast ? (rev.wet?.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 fAccomm = (isPast && actualAccomm != null) ? actualAccomm : otbNetRev + pickupRooms * dayRate
const fDry = ovr.forecast_dry != null ? parseFloat(ovr.forecast_dry) const fDry = ovr.forecast_dry != null ? parseFloat(ovr.forecast_dry)
: ovr.dry_override != null ? parseFloat(ovr.dry_override) : ovr.dry_override != null ? parseFloat(ovr.dry_override)
@ -244,6 +251,7 @@ export async function directorsForecastRoutes(fastify) {
totalOtbRooms += otbRooms totalOtbRooms += otbRooms
totalOtbNetRev += otbNetRev totalOtbNetRev += otbNetRev
totalPickup += pickupRooms totalPickup += pickupRooms
totalPickupRevenue += pickupRooms * dayRate
totalAccomm += fAccomm totalAccomm += fAccomm
totalDry += fDry totalDry += fDry
totalWet += fWet totalWet += fWet
@ -312,7 +320,7 @@ export async function directorsForecastRoutes(fastify) {
arr: totalOtbRooms > 0 ? totalOtbNetRev / totalOtbRooms : null, arr: totalOtbRooms > 0 ? totalOtbNetRev / totalOtbRooms : null,
revpar: totalAvailable > 0 ? totalOtbNetRev / totalAvailable : 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 })), weekly: Object.entries(weekBands).map(([label, v]) => ({ label, ...v })),
snapshots: snapshotsRes.rows, snapshots: snapshotsRes.rows,
utilities, utilities,