Almost everything operational in the app is scoped to a location: the floor, the check-in queue, open tabs, the day-part menu and the 86 list all change when the picker in the header changes. Inside a location, the things you seat people at and sell from are service points.
Locations
A location is a location record. The app lists them with POST /repository/find/location and remembers the choice per device (location_selected_id).
When the app asks for open tabs, the queue or service points it passes the location's data.name slug — for example garden-bar-brooklyn — as businessLocationId, because that is what the server records point at. Renaming a location in a way that changes its slug orphans every record that references the old one.
A location with no service points is not broken; the Floor screen simply reports "No service points configured". A location with no open tabs shows an empty Tabs list. Both are normal for a new site.
Service points
A service point is one thing a party can be assigned to: a table, a seat, a booth, a bay, a room, a counter. They form a tree, so a floor can contain sections, a section tables, and a table seats.
| Field | Meaning |
|---|---|
name | The unique key. Orders and tabs reference a service point by this name, not by record id. |
displayName | What staff see — "Seat 8", "Patio 2". |
kind | Free text: seat, table, booth, bay, room, section, chair… The app offers a curated list but accepts anything. |
businessLocationId | The location slug it belongs to. |
parentName | The service point above it in the tree (floor → section → table → seat). |
level, sortOrder | Depth and ordering within a parent. |
capacity, minCapacity, maxCapacity | Party sizes it suits. |
x, y, width, height, rotation, color, shape, backgroundImage | Geometry for the floor plan. |
bookable | Whether reservations may target it. Default true. |
isActive | Whether it is in service. Default true. |
features[], images[] | Descriptive extras. |
Live state lives on the same record and is written by the flows below: status, currentReservationId, currentTabId, currentServerId, currentPartySize, seatedAt, estimatedEndTime, lastStatusChangeAt, lastStatusReason.
Creating and editing from the app
Floor → + opens the service point form. It covers name, display name, kind (chips plus free text), parent, capacity, the Bookable and Active toggles, and geometry. The form preserves the live fields on edit so a rename does not detach a seated party.
/crm/reservations/service-point/get/:id?CUSTOMER/crm/reservations/service-point/createCUSTOMER/crm/reservations/service-point/updateCUSTOMER/crm/reservations/service-point/delete/:idCUSTOMERThe floor plan itself is read-only on mobile: it draws points at their authored x/y and lets you pan and zoom. Points without geometry appear in a row underneath. Dragging and resizing is done in the desktop floor editor.
Statuses and who sets them
The status vocabulary in the schema is:
| Status | Meaning |
|---|---|
available | Free to seat. Default. |
occupied | A party is at it. |
reserved | Held for an upcoming booking. |
dirty | Needs turning before the next party. |
blocked | Taken out of service by staff. |
closed | Not in service today. |
Three different flows write it, and it matters which one you use:
| Flow | What it writes | Endpoint |
|---|---|---|
| Check-in → Assign | occupied, plus currentReservationId, currentPartySize, seatedAt; the reservation becomes used | POST /checkin/:taskId/assign |
| POS → assign a tab | Links currentTabId; can mark occupied; several tabs may share a point (joined parties) | POST /storefront/pos/tab/:id/assign-service-point |
| POS → release a tab | dirty by default, available if asked; clears the occupant only if that tab owned it | POST /storefront/pos/tab/:id/release-service-point |
| Check-in → Clear | dirty or available | POST /checkin/service-point/:spId/clear |
| Floor → long-press | A manual status — dirty, clear, blocked | POST /storefront/service-point/:id/status |
The manual status call is deliberate: it changes status and lastStatusReason only, and never touches currentTabId or currentReservationId. An earlier build wrote the whole record and erased the table-to-tab link; that is why the app uses the dedicated endpoint.
The manual status endpoint accepts any string — the operator owns the vocabulary. Check-in's Assign is the one place that refuses a point, and it refuses exactly occupied and reserved. A custom status such as taken would slip past that guard, so keep to the six values above unless you have a reason not to.
Seating rules worth knowing
- Assign from the check-in queue rejects a point that is
occupiedorreserved, and rejects a task that is already done. - Assigning a tab to a point does not reject occupied points. That is how a second tab joins an existing party. If the tab has no customer and the point has a seated reservation, the tab inherits that guest, so their spend lands on their record.
- A tab cannot be assigned across locations, and a finalized order (paid, completed, cancelled, refunded, returned) cannot be moved.
- Releasing a tab marks the point
dirtyunless the release says otherwise. Someone has to clear it before the queue will seat there again.