-
Notifications
You must be signed in to change notification settings - Fork 1
Booking and Queue
This page describes what happens when a user books an asset, what "joining the queue" actually does, and all the rules that gate it.
Navigate to any floor via Sidebar → Buildings → building → floor. Roomer renders the floor plan image with every bookable asset overlaid as a draggable-shaped marker.
The top of the floor page has a date picker. The availability calculation is
always relative to a specific date — by default, today. Users can pick any
date up to a configurable ceiling (ADVANCE_BOOKING_DAYS, defaults to 14).
Each marker is coloured by its primary zone. Hovering (or tapping on mobile) shows a tooltip with the asset's status for the selected date:
| Status | Marker appearance | Meaning |
|---|---|---|
available |
Normal zone colour | You can book it. |
mine |
Green border | You already have a confirmed booking covering part of the day. |
booked |
Faded + "booked" badge | Someone else has a confirmed booking covering part of the day. |
queued |
Orange "waitlist" badge | You're on the waitlist for this asset. |
promoted |
Orange "claim now!" badge | A slot opened for you. You have 2 hours to claim it. |
restricted |
Padlock icon |
RESTRICTED asset, and you're not on its allow list. |
assigned |
"assigned to X" label |
ASSIGNED asset. You can't book unless an availability window covers your slot. |
disabled |
Greyed, X icon |
DISABLED asset. Not bookable by anyone. |
zone_conflict |
Red "in-use" badge | You already have a booking on this floor for an asset in the same zone group overlapping the same time. |
The computation lives in GET /api/v1/floors/:id/availability?date=YYYY-MM-DD.
- Click an available asset.
- A dialog opens showing the asset name, zone, amenities and a time picker pre-filled with the selected date's working hours.
- Adjust the start and end time. Defaults to the working-hour block.
- Optionally add a note (e.g. "training session, keep chairs"). The note is visible to admins via the booking report and to the booker on My Bookings.
- Click Book.
On success the marker flips to mine, a booking-confirmation email goes out
via the notification queue (see Email Notifications), and the entry
appears under Sidebar → My Bookings.
POST /api/v1/bookings rejects the request if any of the following apply:
| Check | Error code |
|---|---|
| Asset doesn't exist | NOT_FOUND |
Asset has isBookable = false
|
ASSET_NOT_BOOKABLE |
Asset bookingStatus = DISABLED
|
ASSET_DISABLED |
Asset bookingStatus = RESTRICTED and user is not on the allow list |
NOT_ON_ALLOW_LIST |
Asset bookingStatus = ASSIGNED to someone else and no availability window covers the slot |
ASSET_ASSIGNED |
| The user's group access doesn't include the building or floor | GROUP_ACCESS_DENIED |
| Another confirmed booking already overlaps this slot | ASSET_CONFLICT |
| The user already has a booking in the same zone group at this time | ZONE_GROUP_CONFLICT |
SUPER_ADMINs bypass the allow-list, assignment, and group-access checks —
they can still hit ASSET_CONFLICT and ZONE_GROUP_CONFLICT.
PATCH /api/v1/bookings/:id lets the owner (or a SUPER_ADMIN) change the
startsAt, endsAt, or notes of a confirmed booking. The same conflict
checks re-run. A booking that is already CANCELLED or COMPLETED cannot be
edited.
From My Bookings, click Cancel. From an asset's detail page (as admin or floor manager), click the booking then Cancel.
When a booking is cancelled:
-
statusflips toCANCELLED. - The booker receives a cancellation email (
BOOKING_CANCELLEDif they cancelled themselves,BOOKING_CANCELLED_BY_ADMINif someone with floor- manager or admin rights cancelled on their behalf). - If a queue entry is waiting for an overlapping slot on the same asset, it is promoted (see below).
Floor managers can cancel any booking on their floors. SUPER_ADMINs can cancel any booking system-wide. Regular users can only cancel their own.
Sidebar → My Bookings. Filters:
-
Upcoming (default) —
status = CONFIRMEDandendsAt >= now. -
Past —
endsAt < now. - All — every booking ever.
When an asset is already booked for the time you want, the booking dialog
changes from Book to Join Queue. This records a QueueEntry with:
- The asset and the wanted time range.
- An
expiresAt— how long you're willing to wait. Past this point the entry auto-expires. - A
position— your spot in line for that asset + overlapping slot. Lower position claims first.
- Click a
bookedasset. - The dialog shows the existing booker (admins) or just "currently booked" (regular users), with a Join Queue section.
- Pick how long you want to wait — the
expiresAt. Useful values: "until the booking ends", "until end of day", "until end of week". - Submit. You receive a Queue joined email and the marker flips to
queued.
You can only have one active queue entry per overlapping slot per asset. The
API returns ALREADY_QUEUED if you try to stack.
When the blocking booking is cancelled (or when a PROMOTED slot ahead of
you expires without being claimed):
- Roomer finds the lowest-position
WAITINGentry for the asset that overlaps the freed slot. - Flips it to
PROMOTEDand stampsclaimDeadline = now + 2h. - Sends a
QUEUE_PROMOTEDemail.
Under My Queue the entry now shows a big Claim now button and a countdown. You have exactly 2 hours.
Click Claim on the promoted entry. Under the hood:
- The API re-checks that nothing has snuck in front of you (e.g., a
SUPER_ADMIN bypassing the queue). If so, returns
ASSET_CONFLICT— the entry is leftPROMOTEDand you can try again. - A new
Bookingis created with the queued time range. - The queue entry flips to
CLAIMED. - You get a booking-confirmation email.
After claiming, the booking behaves like any normal booking — modifiable, cancellable, visible under My Bookings.
If you do nothing for 2 hours after promotion, a background worker
(expire-claim-deadlines, runs every 5 minutes) will:
- Flip your entry to
EXPIREDand send aQUEUE_EXPIREDemail. - Promote the next
WAITINGentry for the slot, starting a fresh 2-hour clock for them.
A second worker (expire-queue-entries, runs every 15 minutes) handles
expiresAt — the ceiling you set when joining. Any WAITING entry whose
expiresAt is in the past is flipped to EXPIRED with a QUEUE_EXPIRED
email, and removed from promotion consideration.
From My Queue, click Cancel on any WAITING or PROMOTED entry. The
entry status flips to CANCELLED. The next overlapping waiter is not
auto-promoted in this case — promotion only happens when a booking is
cancelled or a claim lapses.
WAITING ──(booking cancelled)──▶ PROMOTED ──(claim within 2h)──▶ CLAIMED
│ │
│ └──(2h passes)──▶ EXPIRED
│
├──(expiresAt passes)──▶ EXPIRED
│
└──(user cancels)───────▶ CANCELLED
Roomer stops a single user from holding two overlapping bookings in the same
zone group. The check is done per-booking on create/modify, and surfaces in
the floor view as the zone_conflict marker status.
-
Same asset, overlapping times — always blocked (
ASSET_CONFLICT). -
Different assets, same zone group, overlapping times — blocked
(
ZONE_GROUP_CONFLICT). - Different assets, different zone groups, overlapping times — allowed.
- Different assets in the same zone (no zone group configured) — allowed.
If you want to prevent users from booking two desks concurrently in the same zone, set a zone group on the zone. See Zones and Assets for setup.
There is no separate "book for someone else" UI. A SUPER_ADMIN can achieve
this today by creating the booking directly against the user via
POST /api/v1/bookings with the user's session — or by temporarily
elevating a service account. If you need this workflow routinely, open an
issue — it's a common request that isn't implemented as a first-class flow.
Two env vars control the booking horizon:
| Variable | Default | Effect |
|---|---|---|
WORK_DAY_START_HOUR |
9 |
Default start of the time-picker (24h clock). |
WORK_DAY_END_HOUR |
17 |
Default end of the time-picker. |
ADVANCE_BOOKING_DAYS |
14 |
How many days ahead the date picker allows. |
Users can still book outside working hours by adjusting the picker — these are defaults, not hard bounds.
Users can subscribe to a floor (and optionally specific zones within it) to
receive a FLOOR_AVAILABLE email whenever a desk on that floor becomes free
on a given date. This is distinct from the per-asset queue: subscriptions are
floor-wide, not tied to a specific booking or time slot.
Open a floor plan and click Subscribe (bell icon in the floor header).
- Floor-only — get notified whenever any desk on the floor is freed.
- Zone-filtered — optionally pick one or more zones; notifications are only sent when a desk in one of those zones becomes free.
A 30-minute cooldown applies per subscription: if multiple desks free up in a short burst, you receive at most one email per 30 minutes. This prevents notification storms when, for example, a block booking is cancelled.
FLOOR_AVAILABLE is enqueued when:
- A booking is cancelled (user-initiated or admin-initiated) and the freed slot is on the same date as the cancellation.
- A promoted queue entry expires without being claimed — the now-free slot fans out to floor subscribers.
- An asset is explicitly made available by an admin
(
POST /api/v1/assets/:id/make-available).
The fan-out respects zone filters: if your subscription includes zones, you only get notified when the freed asset is in one of those zones. If your subscription has no zone filter, any freed desk on the floor triggers a notification.
- Sidebar → My Subscriptions lists all your active floor subscriptions. You can remove any of them here.
- Deleting a floor or zone removes related subscriptions automatically (cascade delete).
| Method & path | Purpose |
|---|---|
GET /api/v1/subscriptions |
List the current user's floor subscriptions. |
POST /api/v1/subscriptions |
Subscribe to a floor ({ floorId, zoneIds? }). |
DELETE /api/v1/subscriptions/:id |
Unsubscribe. |
| Method & path | Purpose |
|---|---|
GET /api/v1/floors/:id/availability?date=YYYY-MM-DD |
Render the floor view. |
POST /api/v1/bookings |
Create a booking. |
GET /api/v1/bookings |
List current user's bookings (`status=upcoming |
GET /api/v1/bookings/:id |
Single booking (owner or admin). |
PATCH /api/v1/bookings/:id |
Modify a confirmed booking. |
DELETE /api/v1/bookings/:id |
Cancel a booking. Triggers queue promotion. |
GET /api/v1/queue |
Current user's WAITING and PROMOTED entries. |
POST /api/v1/queue |
Join the queue for an asset/slot. |
DELETE /api/v1/queue/:id |
Leave the queue. |
POST /api/v1/queue/:id/claim |
Claim a promoted asset (becomes a booking). |
GET /api/v1/subscriptions |
List floor subscriptions. |
POST /api/v1/subscriptions |
Subscribe to a floor. |
DELETE /api/v1/subscriptions/:id |
Unsubscribe from a floor. |
GET /api/v1/bookings/report |
Admin report (see Reports and Leases). |
All endpoints enforce JWT authentication; floor availability is public to any authenticated user but filtered by group access.
Install & Run
- Getting Started
- Development Setup
- Production Deployment
- Kubernetes Deployment
- Backup and Recovery
- TLS Configuration
- Configuration Reference
Using Roomer
- Buildings and Floors
- Zones and Assets
- Users, Groups and Permissions
- Booking and Queue
- Bulk CSV Import
- Enterprise Auth
- Email Notifications
- Webhooks
- Reports and Leases
Developer