VOYAGE
Voyage / TECHNICAL / Booking Appointments

Booking Appointments

The booking subsystem is a Cloudflare Worker that exposes public hold, checkout, booking, check-in, and question routes plus admin booking, question, and customer routes (workers/booking/src/index.ts:24-33).

Booking Appointments

Current Shape

The booking subsystem is a Cloudflare Worker that exposes public hold, checkout, booking, check-in, and question routes plus admin booking, question, and customer routes (workers/booking/src/index.ts:24-33).

The Worker is bound to tenant config and tenant ops D1 databases, and it also declares the BOOKING_PRESENCE Durable Object used by the admin presence socket (workers/booking/wrangler.toml:5-22).

The edge gateway sends public bookings, holds, checkout, and customers routes to the booking worker and sends admin booking routes through the admin router branch (workers/edge-gateway/src/router.ts:15-51).

State Machine

The current booking state package defines ten statuses: pending, held, confirmed, pending_cancellation, checked_in, being_served, completed, no_show, cancelled, and waitlisted (packages/booking-state/src/index.ts:6-17).

Terminal booking states are completed, no_show, and cancelled, and the helper API exposes transition validation plus valid-target lookup (packages/booking-state/src/index.ts:19-40).

Cancellation and no-show transitions require reason codes in the shared state package, so admin flows should supply explicit operational context for those outcomes (packages/booking-state/src/index.ts:43-75).

Holds

Public holds currently last five minutes, not thirty minutes; the hold route uses a HOLD_DURATION_MINUTES = 5 constant (workers/booking/src/routes/holds.ts:6).

Hold creation checks for overlapping active holds and overlapping active bookings before inserting an active hold row with an expiration timestamp (workers/booking/src/routes/holds.ts:44-126).

Hold release only changes active holds to released, which preserves the row for audit and checkout history rather than deleting it (workers/booking/src/routes/holds.ts:129-157).

Hold refresh first expires stale holds, then removes the refreshed hold if a booking has already taken the slot, and otherwise extends the hold expiration when the slot remains free (workers/booking/src/routes/holds.ts:159-270).

Checkout

Checkout validates that all submitted hold IDs exist, are active, and have not expired; expired holds are marked expired before a validation error is returned (workers/booking/src/routes/checkout.ts:77-108).

Checkout enforces required booking questions before customer, booking, session, or order rows are created (workers/booking/src/routes/checkout.ts:110-146).

Checkout creates or updates the customer record with contact details and notification preferences before it creates bookings (workers/booking/src/routes/checkout.ts:148-218).

Initial booking status is pending_approval only when REQUIRE_BOOKING_APPROVAL is true; otherwise checkout creates confirmed bookings (workers/booking/src/routes/checkout.ts:224-225).

That pending_approval status is an implementation mismatch, not a cleanly supported lifecycle state: the checkout route writes it, but the booking table CHECK constraint and the shared booking-state package do not recognize it (workers/booking/src/routes/checkout.ts:224-225, workers/booking/src/db/schema.sql:22-35, packages/booking-state/src/index.ts:6-17).

Checkout resolves service pricing, validates a single checkout currency, creates an order when the total is greater than zero, inserts the booking, marks the hold converted, creates a service session, inserts order items, and stores booking answers (workers/booking/src/routes/checkout.ts:227-520).

Admin Mutation Model

Admin mutation routes build a command context from the viewer scope, which carries actor identity, tenant, branch, request, and database information into the command engine (workers/booking/src/routes/bookings.ts:394-410).

The shared booking command package exports cancel, reschedule, reassign, complete, no-show, check-in, start-serving, waitlist promotion, note, pending-cancellation, restore, and reverse-action commands (packages/booking-commands/src/index.ts:1-17).

Command results can carry side effects for notification, calendar sync, follow-up, and waitlist promotion plus reversible snapshots for undoable operations (packages/booking-commands/src/types.ts:43-85).

Booking audit entries include session, booking, company, branch, actor, action, old value, new value, reversible flag, and custom metadata fields (packages/booking-audit/src/audit-log.ts:23-40).

Audit reads support action, actor, booking, staff, branch, date, and RBAC filters, and the audit package also claims reversal tokens for undo flows (packages/booking-audit/src/audit-log.ts:85-250).

Reschedule

Admin reschedule rejects externally managed bookings, checks for schedule conflicts or waitlist capacity, executes the reschedule command, synchronizes the service session, reschedules reminders, and dispatches command side effects (workers/booking/src/routes/bookings.ts:2360-2483).

Public reschedule rejects externally managed bookings, checks online-reschedule permissions and notice hours, verifies conflict or waitlist capacity, executes under the customer actor, synchronizes the service session, and dispatches side effects (workers/booking/src/routes/bookings.ts:3304-3428).

Online cancel and reschedule configuration is read from service and company-level configuration helpers, and notice windows are calculated with the current booking start time (workers/booking/src/routes/bookings.ts:317-350).

No-Show, Serving, and Completion

The start-serving admin route executes a command and synchronizes the service session to being_served (workers/booking/src/routes/bookings.ts:2607-2648).

The complete route executes the complete command through the shared command path (workers/booking/src/routes/bookings.ts:2650-2704).

The no-show route executes the no-show command, synchronizes the service session to no_show, and returns a specific NO_SHOW_TOO_EARLY validation code when command details report that the no-show is premature (workers/booking/src/routes/bookings.ts:2706-2745).

Service session synchronization explicitly maps booking lifecycle changes to service-session state and timestamps for being_served, completed, and no_show states (workers/booking/src/routes/bookings.ts:688-831).

Undo, Bulk Actions, and Presence

The reverse-action route claims an audit reversal token, rejects invalid reversal targets, restores the prior booking snapshot, and emits calendar and notification side effects (packages/booking-commands/src/reverseAction.ts:14-75).

Admin booking routes expose reverse action, bulk cancel, bulk reassign, and a Durable Object-backed presence WebSocket route (workers/booking/src/routes/bookings.ts:1128-1411).

Bulk cancel protects externally managed bookings, runs the command engine per booking, cancels reminders, synchronizes service sessions, dispatches side effects, and can promote waitlisted bookings (workers/booking/src/routes/bookings.ts:1178-1277).

Bulk reassign runs through command execution and currently requires an explicit staff_id; when no staff is supplied, the route returns ASSIGNMENT_UNAVAILABLE instead of auto-selecting staff (workers/booking/src/routes/bookings.ts:1279-1380).

The presence Durable Object tracks viewers and editing state, sends snapshots, accepts heartbeat and editing messages, and evicts stale connections after 30 seconds (workers/booking/src/durable-objects/presence.ts:3-183).

The booking UI kit exposes presence, audit, reverse, bulk cancel, bulk reassign, and booking action hooks plus unstyled reference components (packages/booking-ui-kit/src/index.ts:3-37).

Side Effects

Booking routes queue notification and campaign triggers on a best-effort path after lifecycle changes (workers/booking/src/routes/bookings.ts:504-546).

Booking routes trigger calendar sync on a best-effort path after relevant booking mutations (workers/booking/src/routes/bookings.ts:548-558).

Calendar reconciliation can convert Exchange deletions into pending cancellations, restore deleted events, reschedule moved events, or flag declined invitations through the shared calendar-sync core (packages/calendar-sync-core/src/reconciliation.ts:21-81).

Corrections From Frozen HTML

The frozen page's thirty-minute hold language is stale because current public holds are five minutes (workers/booking/src/routes/holds.ts:6).

The frozen page's simple appointment lifecycle is incomplete because current booking state includes undoable pending_cancellation, active service states, waitlist support, and terminal states (packages/booking-state/src/index.ts:6-23).

The frozen page did not cover F24 through F31 work: API contracts, audit, command engine, UI kit, reverse action, presence, and bulk actions are now first-class code paths (packages/api-contracts/src/index.ts:1-17, workers/booking/src/routes/bookings.ts:1128-1411).


last verified: 2026-04-24, commit 14f3b190db8817399dcd30e1dc4e1ae7674bbf8a