import type { HealthcareService } from 'fhir/r4'; import type { BookabilityContext } from './resolveBookability'; import type { MatchContext } from './matchSchedules'; import type { ReassignAccessors } from './reassignFeasibility'; /** * - build the reassign inputs from the context every staff surface already assembles. * * THIS EXISTS TO STOP A CALLER WIRING THE ACCESSORS BY HAND, and the hazard is documented in * `useStaffBookabilityContext` itself: *"availabilityEvents MUST be called the way the patient flow calls * it - `getScheduleAvailabilityEvents(schedule, transformFromFhirPractitionerRole(role))`. Passing a raw * PractitionerRole instead of the re-transformed form shape yields ZERO availability, SILENTLY."* * A hand-wired reassign would then find **no open hours for anybody**, report every appointment as * unmovable, and look exactly like a practice whose target simply does not work those days. Nothing would * error. So the accessors are taken from the assembled context, never rebuilt. * * THE EXAM GATE IS DELIBERATELY OFF, and it is inherited rather than chosen here. The staff context * turns it off because readiness asks *"can ANY patient book this provider"*. That is also the right * question for a reassignment: the patient is already established with the practice and STAFF are moving * the visit - the new-patient exam policy governs a patient booking their own first appointment, which * this is not. */ export interface ReassignInputs { accessors: ReassignAccessors; matchContext: MatchContext; } export function reassignInputsFrom( bookability: BookabilityContext, healthcareServices: ReadonlyArray, ): ReassignInputs { const typeById = new Map(healthcareServices.map((service) => [service.id ?? '', service.type ?? []])); return { accessors: { // Taken from the assembled context - see the header for why this must not be rebuilt. availabilityEvents: bookability.accessors.availabilityEvents, selfBookableServices: bookability.accessors.selfBookableServices, // An UNKNOWN service id yields no codings, which makes the service check refuse rather than // allow. That direction is deliberate: the catalog is an optional argument on the staff context // (it is not fetched on every screen), and an absent catalog must not silently widen a booking // rule into "everyone offers everything". serviceTypeCodings: (serviceId: string) => typeById.get(serviceId) ?? [], }, matchContext: { accessors: bookability.accessors, examGate: bookability.examGate, anyLocation: bookability.anyLocation, virtualLocationId: bookability.telehealth?.virtualLocationId, }, }; }