Add a Departing state to the Rooms Accessible View
A guest checking out today but not yet gone was previously lumped in with ordinary stayovers — fully greyed out, no way to tell "the guest is gone in 20 minutes" apart from "the guest isn't leaving at all". Split that out into its own bucket: Backend: classifyBookingStatus() now also recognises 'depart'/'checkedout' statuses (already checked out — room's free regardless of the date window). fetchRoomOccupancy() returns occupied/departing/arriving; an in-house booking whose departure date is today lands in 'departing' instead of 'occupied'. The ?unoccupied=true task filter and /api/occupancy both updated for the new three-way split. Frontend: departing rooms get a light dim (opacity only, no greyscale) instead of the hard grey-out, plus a purple 'Departing' badge — matching the booked=green/occupied=blue/departing=purple colour language hvac already uses for NewBook room states. Chip label breakdown now reads "X free • Y arriving • Z departing • W occupied". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
96674c58c8
commit
8ddb99d2d7
3 changed files with 54 additions and 25 deletions
|
|
@ -33,13 +33,16 @@ function stripCosts(task) {
|
|||
}
|
||||
|
||||
// Returns guests physically in the room right now vs those booked to arrive today.
|
||||
// NewBook status strings vary by account; we normalise and check for 'arriv' / 'inhouse'
|
||||
// / 'checkedin' to identify in-room guests. Everything else in the 'staying' list is
|
||||
// treated as "arriving today" — confirmed but not yet checked in.
|
||||
// NewBook status strings vary by account; we normalise and check for 'depart' /
|
||||
// 'checkedout' (already checked out — room's free, whatever the date window says),
|
||||
// then 'arriv' / 'inhouse' / 'checkedin' to identify in-room guests. Everything else
|
||||
// in the 'staying' list is treated as "arriving today" — confirmed but not yet
|
||||
// checked in.
|
||||
function classifyBookingStatus(rawStatus) {
|
||||
const s = String(rawStatus ?? '').toLowerCase().replace(/[\s_\-]/g, '')
|
||||
if (s.includes('arriv') || s.includes('inhouse') || s.includes('checkedin')) return 'occupied'
|
||||
return 'arriving'
|
||||
if (s.includes('depart') || s.includes('checkedout')) return 'departed'
|
||||
if (s.includes('arriv') || s.includes('inhouse') || s.includes('checkedin')) return 'inhouse'
|
||||
return 'confirmed'
|
||||
}
|
||||
|
||||
async function fetchRoomOccupancy() {
|
||||
|
|
@ -52,23 +55,35 @@ async function fetchRoomOccupancy() {
|
|||
// filter locally against each booking's own arrival/departure dates instead.
|
||||
const bookings = await fetchBookings(yesterday, today)
|
||||
const occupied = new Set()
|
||||
const departing = new Set()
|
||||
const arriving = new Set()
|
||||
for (const b of bookings) {
|
||||
const siteId = b.booking_site_id ?? b.site_id
|
||||
if (siteId == null) continue
|
||||
const arrival = String(b.booking_arrival ?? '').slice(0, 10)
|
||||
const departure = String(b.booking_departure ?? '').slice(0, 10)
|
||||
// Only bookings whose stay actually covers today belong in either bucket — the
|
||||
// Only bookings whose stay actually covers today belong in any bucket — the
|
||||
// wider window also pulls back yesterday's now-departed booking, which shouldn't
|
||||
// count towards either set.
|
||||
// count towards any of them.
|
||||
if (arrival > today || departure < today) continue
|
||||
const id = String(siteId)
|
||||
if (classifyBookingStatus(b.booking_status) === 'occupied') occupied.add(id)
|
||||
else arriving.add(id)
|
||||
const kind = classifyBookingStatus(b.booking_status)
|
||||
if (kind === 'departed') continue // already checked out — room's free now
|
||||
if (kind === 'inhouse') {
|
||||
// In the room right now: still a stayover if checking out later, or
|
||||
// "departing" (checking out today but not yet gone) if this is their last day.
|
||||
if (departure === today) departing.add(id)
|
||||
else occupied.add(id)
|
||||
} else {
|
||||
arriving.add(id)
|
||||
}
|
||||
}
|
||||
// A site that is both (e.g. multi-booking day) should be treated as occupied
|
||||
// A site can have more than one booking on a turnover day (e.g. a departing guest
|
||||
// plus a same-day incoming one) — physically-present-right-now always wins over a
|
||||
// booking that's merely confirmed to arrive later.
|
||||
for (const id of departing) { occupied.delete(id); arriving.delete(id) }
|
||||
for (const id of occupied) arriving.delete(id)
|
||||
return { occupied, arriving }
|
||||
return { occupied, departing, arriving }
|
||||
}
|
||||
|
||||
export async function taskRoutes(app) {
|
||||
|
|
@ -106,13 +121,16 @@ export async function taskRoutes(app) {
|
|||
// "Unoccupied rooms only": keep only rooms-category tasks whose NewBook room
|
||||
// has no in-house (arrived) booking right now.
|
||||
if (q.unoccupied === 'true') {
|
||||
let occupied
|
||||
let occupied, departing
|
||||
try {
|
||||
({ occupied } = await fetchRoomOccupancy())
|
||||
({ occupied, departing } = await fetchRoomOccupancy())
|
||||
} catch (err) {
|
||||
return reply.status(502).send({ error: `NewBook error: ${err.message}` })
|
||||
}
|
||||
rows = rows.filter(t => t.is_rooms && t.newbook_site_id && !occupied.has(t.newbook_site_id))
|
||||
// A "departing today" room is still guest-occupied right now, so it counts as
|
||||
// occupied for this filter even though the Rooms Accessible View shows it
|
||||
// separately.
|
||||
rows = rows.filter(t => t.is_rooms && t.newbook_site_id && !occupied.has(t.newbook_site_id) && !departing.has(t.newbook_site_id))
|
||||
}
|
||||
|
||||
if (!hasCap(req, 'costs')) rows = rows.map(stripCosts)
|
||||
|
|
@ -334,13 +352,15 @@ export async function taskRoutes(app) {
|
|||
})
|
||||
|
||||
// GET /api/occupancy — room state from NewBook for the Rooms Accessible View
|
||||
// Returns two lists: occupied (guest in room) and arriving (booked today, not yet checked in)
|
||||
// Returns three lists: occupied (staying, guest in room), departing (guest in room
|
||||
// but checking out today — accessible later) and arriving (booked today, not yet checked in)
|
||||
app.get('/api/occupancy', { preHandler: requireCap('view') }, async (req, reply) => {
|
||||
try {
|
||||
const { occupied, arriving } = await fetchRoomOccupancy()
|
||||
const { occupied, departing, arriving } = await fetchRoomOccupancy()
|
||||
return {
|
||||
date: new Date().toISOString().slice(0, 10),
|
||||
occupied_site_ids: [...occupied],
|
||||
departing_site_ids: [...departing],
|
||||
arriving_site_ids: [...arriving],
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue