import type { PractitionerRole, Reference } from 'fhir/r4'; // - ONE implementation of "which of this practitioner's roles belong to the org in view". // // WHY THIS EXISTS // A `PractitionerRole` means **"this person works at THIS organization."** A practitioner at two // practices holds two of them, and every UI that reads or writes a practitioner's roles must scope // to the organization currently in view. // // Three surfaces got that wrong in the same way, one at a time: StaffServicesOfferedPage (, // fixed), SchedulesPage and PractitionerRolesPage (, this change). Each had its own inline // `ref.reference?.split('/').pop()` and its own filter. A rule re-implemented per call site is the // shape that produced three separate instances of one bug - so the rule lives here now, tested once. // // NOT a security boundary. The server is: `PractitionerRole` routes require `X-Organization-ID`, // and `assertScheduleBelongsToOrganization` refuses a Schedule anchored to another org's role. This // keeps the UI from BUILDING those refusals in the first place, which is what turns an inexplicable // save failure into a correct form. const refId = (ref: Reference): string | undefined => ref.reference?.split('/').pop(); /** * The subset of `roles` that belong to `organizationId`. * * FAILS CLOSED, DELIBERATELY. With no organization in view (`undefined`) this returns NOTHING * rather than everything. "We don't know which org this is" must never resolve to "so use every org's * roles" - that is precisely the pooled-across-orgs behaviour being removed, and it would reappear at * exactly the moment context is missing. A role carrying no `organization` reference is excluded for * the same reason: it cannot be shown to belong here. */ export const rolesForOrganization = ( roles: readonly PractitionerRole[] | undefined, organizationId: string | undefined, ): PractitionerRole[] => { if (!organizationId) return []; return (roles ?? []).filter( (role) => Boolean(role.organization && refId(role.organization) === organizationId), ); }; /** * The single role this practitioner holds at `organizationId`, or `undefined`. * * "One role per organization" is the product rule the identity-role tab is built on - and it is * true PER ORG and false across them, which is the assumption fixed. If an org somehow holds * more than one, the first is returned rather than throwing: a surface that renders nothing is worse * for the practice than one that renders the first of two. */ export const roleForOrganization = ( roles: readonly PractitionerRole[] | undefined, organizationId: string | undefined, ): PractitionerRole | undefined => rolesForOrganization(roles, organizationId)[0];