Skip to content

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.

CoversWhether a guest can book a given experience at a given time, and how many places there are
Checked againstems-backend, ems-apis
Date checked5 August 2026
Ownerengineering 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 typeWhat it means
UnlimitedNo cap. Always shown as available.
Limited by bookingsA maximum number of bookings. Each booking uses one place, regardless of party size.
Limited by seatsA maximum number of people. A party of four uses four places.
Needs approvalThe 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 taken

Confirmed 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:

  1. It is unlimited or needs approval — in which case it is always shown, and nothing below is checked; or
  2. Places remaining is above zero, and
  3. 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 bookingAvailability checkedPrice checkedApproval
Guest, on the vendor's own pageyesyesas the slot says
Vendor, signed in to the dashboardnonoalways automatic
Hotel, through the partner routeyesyesas the slot says
Conciergeseparate route, not yet traced
Partner reselleravailability validated, then confirmedalways 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 codeems-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 codeems-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 in ems-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 codeems-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 codeems-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

GapWhy it mattersWho can settle it
Whether a slot is held while someone is payingDecides whether two guests can take the last place during checkoutengineering
Cut-off times, lead times, how far ahead people can bookStandard booking-system settings; not yet located in the codeengineering
Blackout dates and one-off overrides to a recurring scheduleVendors certainly use these; not yet tracedengineering
Time zones — whose day is itA 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 whenFollows from the booking status rules but has not been tracedengineering
Why the availability check was switched offThe comment says "as per request" and names nobodywhoever asked