import type { Location, PractitionerRole, Reference, Schedule } from 'fhir/r4'; import { onlySites } from '../roomLocation'; import { getScheduleLocationReferences } from '../scheduleLocations'; /** * The PHYSICAL site a schedule's hours are written in, from the Location references it carries. * * - THIS IS THE INPUT TO THE PRACTICE'S CLOCK, which is why "first reference" is not good * enough. `resolveBookingTimeZone` takes a site and falls back to the organization, and a video-enabled * block carries **two** Location actors - the physical site and the org's virtual one. Taking * `location[0]` hands a telehealth block the virtual Location, which has no clock of its own (that is * what makes it virtual), so the zone silently falls through to the organization's. * * Right by luck for a single-site practice, and wrong for the exact case the clock work exists for - * a west-coast patient booking an east-coast therapist. Sites only, always. * * EXTRACTED because both booking flows had already written these five lines independently and two more * callers were about to. A derivation with a documented trap in it, copied four times, is a trap that * only gets fixed in three places. * * It takes REFERENCES rather than a Schedule on purpose: resolving a schedule's references to the role * fallback lives in the flows (a block saved before location moved onto the Schedule carries it on the * PractitionerRole), and importing that here would point a util at a service. * * `undefined` is a real answer - an "all locations" block names no site, and the caller then correctly * falls back to the organization's zone. */ export const firstPhysicalSite = ( locationRefs: Reference[] | undefined, locationsList: Location[] | undefined, ): Location | undefined => onlySites( (locationRefs ?? []) .map((ref) => (locationsList ?? []).find((loc) => loc.id === ref.reference?.split('/').pop()), ) .filter((loc): loc is Location => Boolean(loc)), )[0]; /** * The same answer, straight from a schedule and its owning role. * * THE ROLE FALLBACK IS NOT OPTIONAL: a block saved before `location` moved onto the Schedule carries * it on the `PractitionerRole` instead. Dropping it reads those blocks as "all locations" and quietly * puts every one of them on the organization's clock. * * Both booking flows already wrote this pair of lines locally; this is the same derivation with the * trap documented once. Only `getScheduleLocationReferences` is imported - a pure util - so nothing here * points at a service. */ export const scheduleSiteFor = ( schedule: Schedule | undefined, role: PractitionerRole | undefined, locationsList: Location[] | undefined, ): Location | undefined => { const scheduleRefs: Reference[] = schedule ? getScheduleLocationReferences(schedule) : []; // A video-only block names only the virtual Location, which has no clock: its hours are kept on // the clinician's site, else the practice's only site, before the organization's (often unset) zone. const soleSite = onlySites(locationsList ?? []); return firstPhysicalSite(scheduleRefs, locationsList) ?? firstPhysicalSite(role?.location, locationsList) ?? (soleSite.length === 1 ? soleSite[0] : undefined); };