Appearance
When things are bookable
Page summary: Understand both what looks bookable and what EMS will actually accept.
Use this when: Use this when diagnosing missing dates, approval behaviour, capacity, or overbooking risk.
- “Shown as available” and “booking will be accepted” are different questions.
- Capacity type changes how remaining space is counted.
- Different booking channels skip or apply different checks.
Draft. Read in the code. Not yet checked in a running environment.
| Covers | Whether a guest can book a given experience at a given time, and how many places there are |
| Checked against | ems-backend, ems-apis |
| Date checked | 5 August 2026 |
| Owner | engineering for the rules, product for the intent |
Start here: two different questions
People say "is it available?" and mean one of two things. EMS answers them separately, and the answers can differ.
Is it shown as available? — what a guest sees on the booking page. There is a proper set of rules for this, below, and they work.
Will the booking be accepted? — whether EMS refuses a booking for a full slot. It does not. On the main route the check is switched off; on the partner route it is loose enough to let a full item through; and there is no rule in the database to catch it either. See availability is not actually enforced when a booking is created (AVL-006) and the partner route checks, but one free place lets the whole booking through (AVL-008). This is the most important thing on this page.
How capacity works
Every bookable slot has a rule type. There are four, and they behave completely differently.
| Rule type | What it means |
|---|---|
| Unlimited | No cap. Always shown as available. |
| Limited by bookings | A maximum number of bookings. Each booking uses one place, regardless of party size. |
| Limited by seats | A maximum number of people. A party of four uses four places. |
| Needs approval | The vendor decides case by case. Always shown as available, whatever the numbers say. |
Confirmed in code. ems-apis/constants/capacity_type.go:5-11
Places remaining is worked out as:
limited by bookings: remaining = capacity − bookings taken
limited by seats: remaining = capacity − seats takenConfirmed in code. ems-backend/tests/booking_seat_guard/integration_test.go:170-178, mirroring the availability logic
What a guest is shown
A slot is shown to a guest when all of these hold:
- It is unlimited or needs approval — in which case it is always shown, and nothing below is checked; or
- Places remaining is above zero, and
- If it is limited by seats, places remaining is at least the minimum party size the vendor set.
Anyone looking from inside the dashboard sees every slot, regardless.
Confirmed in code. ems-backend/tests/booking_seat_guard/integration_test.go:180-196
Two consequences worth stating plainly.
An approval slot is always shown as available, even when it is full. That is deliberate — the vendor decides. But it means "shown as available" and "there is room" are different things.
The dashboard shows slots that guests cannot see. A vendor looking at their own calendar sees more than a guest does.
Whether a booking needs approval
Approval is decided by the slot, not by the experience as a whole.
- If any item in the booking sits on a slot that needs approval, the whole booking needs approval.
- If any add-on on the booking needs approval, the whole booking needs approval.
- Otherwise the booking is approved automatically.
Confirmed in code. ems-backend/internal/booking/usecases/booking_create.usecase.go:230-234, 265, 272-278
Who skips the checks
Not every booking goes through the same route.
| Who is booking | Availability checked | Price checked | Approval |
|---|---|---|---|
| Guest, on the vendor's own page | yes | yes | as the slot says |
| Vendor, signed in to the dashboard | no | no | always automatic |
| Hotel, through the partner route | yes | yes | as the slot says |
| Concierge | separate route, not yet traced | — | — |
| Partner reseller | availability validated, then confirmed | — | always automatic |
Confirmed in code. ems-backend/internal/booking/handler/v2/booking.controller.go:31-49, ems-backend/internal/booking/usecases/booking_create.usecase.go:302-305
A vendor booking from the dashboard skips availability entirely, sets its own price, and confirms itself. That is presumably deliberate — it is how a walk-in gets recorded — but it means a vendor can book into a slot that is already full without any warning.
Note for the partner boundary: hotel bookings and reseller bookings take different routes with different rules. Hotel bookings respect approval; reseller bookings confirm immediately. Both are called "partner" in conversation. See Who owns what across systems.
Rules
There are four kinds of capacity, and two of them ignore numbers AVL-001
What happens today: Unlimited and needs-approval slots are always shown as available. Only the two limited kinds are checked against how many places are left.
When it applies: Every availability check.
Applies to: One vendor, set per slot.
Expected behaviour: Not established.
Evidence and test details
- Checked Confirmed in code —
ems-apis/constants/capacity_type.go:5-11, 5 Aug 2026.
A booking counts differently depending on the kind AVL-002
What happens today: Under limit-by-bookings, one booking uses one place however many people are in it. Under limit-by-seats, it uses one place per person.
When it applies: Working out how many places are left.
Applies to: One vendor, set per slot.
Expected behaviour: Exactly this.
Why it matters: A vendor who picks the wrong kind will either sell out too early or badly overbook.
Evidence and test details
- Checked Confirmed in code — availability adjustment logic, 5 Aug 2026.
A seat-limited slot hides itself once it cannot fit a minimum party AVL-003
What happens today: If the vendor set a minimum party size, the slot disappears from the booking page once fewer places remain than that minimum.
When it applies: Public booking page only, seat-limited slots only.
Applies to: One vendor.
Expected behaviour: Not established.
Evidence and test details
- Checked Confirmed in code — seat guard logic, 5 Aug 2026.
The dashboard sees slots guests cannot AVL-004
What happens today: Availability filtering only applies to public callers. From inside the dashboard, every slot is returned regardless of how full it is.
When it applies: Always.
Applies to: Everyone.
Expected behaviour: Presumably this, so vendors can see their own full slots.
Why it matters: A vendor being told "the guest cannot see any dates" will look at their own calendar, see dates, and reasonably conclude the guest is wrong.
Evidence and test details
- Checked Confirmed in code — seat guard logic, 5 Aug 2026.
Approval is decided per slot, and any one slot infects the booking AVL-005
What happens today: If any item or any add-on in a booking sits on a slot that needs approval, the whole booking needs approval.
When it applies: Every booking that goes through the checking route.
Applies to: One vendor, set per slot.
Expected behaviour: Exactly this.
Evidence and test details
- Checked Confirmed in code —
ems-backend/internal/booking/usecases/booking_create.usecase.go:230-234, 272-278, 5 Aug 2026.
Availability is not actually enforced when a booking is created AVL-006
What happens today: The main booking route asks for availability and uses the answer to work out the price and whether approval is needed. The step that would reject a booking for a full slot is present in the code but commented out, marked "disabled as per request". The add-on version of the same check is also commented out. Nothing else stops it: there is no rule in the database preventing more bookings than places.
When it applies: The main booking creation route — guests booking on a vendor's own page, and hotel bookings.
Exceptions: The partner reseller route does check, but only loosely. See below.
Applies to: Everyone.
Expected behaviour: Not established. Someone asked for it to be switched off and the reason is not recorded anywhere.
Why it matters: Two guests can take the last place at the same time. Nothing on this route prevents it.
Evidence and test details
- Checked Confirmed in code — the disabled checks at
ems-backend/internal/booking/usecases/booking_create.usecase.go:258-263, 267-270; the database schema inems-backend/migrations/contains no capacity rule; the only other guard is the partner route below. Searched 5 Aug 2026. Still not reproduced against a running system, but there is now no remaining place a check could be hiding.
The partner route checks, but one free place lets the whole booking through AVL-008
What happens today: The partner reseller route does check availability. It keeps a single yes-or-no answer for the whole booking rather than one per item. Each item overwrites it. An item that is full does not set the answer to no — it is simply skipped. So a booking of two items, one available and one full, comes out as available.
When it applies: Partner reseller bookings with more than one item.
Exceptions: An item on a slot that needs approval does set the answer to no, and the whole booking is refused with "items are not available for booking".
Applies to: One sales channel.
Expected behaviour: Every item checked, and any full item refusing the booking.
Why it matters: This is the one route that was supposed to enforce capacity.
Evidence and test details
- Checked Confirmed in code —
ems-backend/internal/booking/usecases/booking_internal.usecase.go:154-240, 5 Aug 2026.
Vendor bookings skip every check AVL-007
What happens today: A booking made by a signed-in vendor from the dashboard skips the availability check, skips the price check, and is approved automatically.
When it applies: Vendor-created bookings only.
Applies to: One sales channel.
Expected behaviour: Presumably this, for walk-ins and phone bookings.
Why it matters: A vendor can fill a slot past its capacity without being told, and the slot's own approval setting is ignored.
Evidence and test details
- Checked Confirmed in code —
ems-backend/internal/booking/handler/v2/booking.controller.go:31-37,ems-backend/internal/booking/usecases/booking_create.usecase.go:302-305, 5 Aug 2026.
What is not established yet
| Gap | Why it matters | Who can settle it |
|---|---|---|
| Whether a slot is held while someone is paying | Decides whether two guests can take the last place during checkout | engineering |
| Cut-off times, lead times, how far ahead people can book | Standard booking-system settings; not yet located in the code | engineering |
| Blackout dates and one-off overrides to a recurring schedule | Vendors certainly use these; not yet traced | engineering |
| Time zones — whose day is it | A slot appearing on the wrong date is a classic fault, and one dev booking is literally named "time zone san francisco" | engineering |
| What a cancellation gives back, and when | Follows from the booking status rules but has not been traced | engineering |
| Why the availability check was switched off | The comment says "as per request" and names nobody | whoever asked |