Release a pencilled-in hold
/api/enquiries/{id}/convertDeletes the reservation this enquiry was pencilled in on and hands the enquiry back as one with no live booking, ready to be pencilled in somewhere else. The group went elsewhere, or the date moved.
Delete, not cancel, and that is the point of it existing. Cancelling enqueues reservation_cancelled, which emails the guest that their booking is off, for a hold they were never told about. Deleting frees the tables silently, cascades book_reservation_tables (0035), and lets 0039's on delete set null (reservation_id) clear the link.
Refuses anything a guest has been told about or has paid for: only a pending or already-cancelled reservation with no payment intent. A confirmed booking is cancelled from the Reservations screen, where the guest is notified and the deposit is resolved.
Authorization
sessionCookie The dashboard's Supabase Auth session cookie, set at sign-in. Large sessions are split across
numbered chunks (…auth-token.0, .1), so treat this as a cookie family rather than one name.
Every request re-validates it against the Auth server (getUser()), never by decoding the cookie
locally: a JWT nothing has checked is not a credential. Tenancy is then read from the verified
app_metadata.company_id claim and enforced by row-level security; it is never read from request
input, on any route, ever.
role (admin / staff) is deliberately not in RLS. It gates specific actions in route code,
the operations marked admin below, so hiding a button in the UI is cosmetic only, and a route's
own check is the enforcement.
In: cookie
Path Parameters
Enquiry id.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
curl -X DELETE "https://example.com/api/enquiries/string/convert"{ "ok": true}Pencil in or book a big-group enquiry POST
Creates a reservation from the enquiry, links the two (`book_group_enquiries.reservation_id`, migration 0038), and moves the enquiry along. Its own route rather than the client calling POST /api/reservations and then PATCHing the enquiry: those are two writes with a gap, and a failure in the gap leaves a booking nobody can reach from the thread it came from. The enquiry is claimed with `where reservation_id is null`, so two staff converting at once produces one booking and a 409; the partial unique index is the backstop, not the button being hidden. **Two stages, chosen by `status`.** A big group is agreed over days of back-and-forth, and the thing a venue needs on message two is not a confirmed booking but the tables taken off the market while the conversation happens. - `pending` pencils it in. It holds its tables against the exclusion constraint exactly like a confirmed sitting (0011 releases only `cancelled` and `no_show`), sets the enquiry to `open` rather than `won`, and posts NOTHING into the thread, because the guest has not been promised anything yet. Undo it with the DELETE below, or finish it with POST /confirm. - `confirmed` (the default) is the one-shot, for a group that agreed everything in one reply. Sets the enquiry to `won` and posts the "Booked" line. **`tableIds` is ordered and may hold up to 20.** The first becomes the reservation's own `table_id`; the rest are written to `book_reservation_tables` (0035) so the whole combination is inside the double-booking constraint. This is the normal case here, not an edge one: a party over the venue's online cap rarely fits on a single table, and a venue will hand a big group a whole room. Twenty rather than the allocator's four: that four bounds a combinatorial subset search on an anonymous request, while here a signed-in host names the tables and each one costs a single insert. Seat limits (`staffSeatLimitEnforced`, 0036) are measured across the whole combination, not the primary alone. The guest is resolved into a client by email, so a returning one lands on the row they already have. `partySize` may be corrected on the way through, and is bounded by the same 500 the public enquiry form accepts, not by the 30 that bounds the public booking path. Otherwise an enquiry too big to book online would be too big to book at all.
Confirm a pencilled-in big-group booking POST
The second half of the two-stage conversion: the hold becomes a booking the venue has actually promised. Flips the reservation from `pending` to `confirmed` (the trigger in 0035 carries every companion table with it), sets the enquiry to `won`, and posts the "Booked" line into the thread. Three writes that must agree, which is why this is one route and not three fetches from the browser. A client could PATCH the reservation and then PATCH the enquiry; what it could not do is guarantee the second happens, and the failure that leaves behind is a confirmed booking whose conversation never mentions it, the exact thing this feature exists to prevent. Compare-and-set on `where status = 'pending'`, so two hosts hitting Confirm at once produce one confirmation and one 409, not two "Booked" lines. **No notification is sent, deliberately**, matching the one-shot conversion. Confirming a big group ends a conversation the staff are already having in this thread; the reply they are about to type is the confirmation.