// Service-first booking - the funnel rule, in one place. // // Spencer, 2026-08-03: "Service shouldn't be optional in the booking flow, it's what determines which // location and practitioners the appointment can be booked with." // // That was already true of PATIENT self-booking, structurally: `bookingFlowSteps.ts` puts `service` // ahead of `location` and `practitioner` in STEP_ORDER and hides a step until everything before it is // answered, and `bookablePractitioners` returns [] until a service is chosen. The RECEPTIONIST flow // disagreed with its own comment - `ReceptionistBookAppointment` says "Service-first: Visit Service is a // PRIMARY filter (chosen before the practitioner)" while treating it as an optional narrowing that gated // nothing. This predicate is what makes the receptionist match the stated design and the patient flow. // // NOT A SECURITY BOUNDARY, and it must not be mistaken for one. The server refuses a video booking // that names no eligible service (, `appointment-booking-flags.enforceTelehealthEligibility`). // This decides only what the staff UI lets you *reach*. Both exist on purpose: the client makes the // right thing easy, the server makes the wrong thing impossible. // // Kept as a shared pure predicate rather than three inline booleans because the receptionist flow gates // three separate things on it (the practitioner/schedule list, the date-and-time step, the prep step), // and this file's neighbourhood has already been bitten twice by the same rule living in two hand-kept // copies that drifted (`getScheduleLocationRefs`, then the picker location gate). export interface BookingServiceGateInput { /** The chosen `HealthcareService` id, or empty/undefined when none has been picked. */ selectedServiceId: string | undefined; /** True when an EXISTING appointment is being edited rather than a new one created. */ isEditMode: boolean; } /** * May the booking funnel proceed past the service step? * * EDIT IS EXEMPT, DELIBERATELY. Creating a booking and changing one are different acts. An * appointment may legitimately carry no resolvable service - it predates this rule, or it was created by * the staff calendar modal, which has no service field at all (`FhirAppointment` never binds * `serviceType`). Gating edits would leave those appointments unmovable and uncancellable from the UI, * turning a funnel improvement into stranded real-world data. */ export const bookingServiceGateSatisfied = ({ selectedServiceId, isEditMode, }: BookingServiceGateInput): boolean => Boolean(selectedServiceId) || isEditMode;