import type { PractitionerRole } from 'fhir/r4'; /** * The clinician-enabled flag, client side ( seat model / ). * * THIS IS THE THIRD COPY OF THIS URL - api, auth, and here - and that is not carelessness, it is the * shape of the system: three separate deployables (two SAM services and a WordPress bundle) that cannot * import from one another. So all three pin the literal in a test. Change one and that side's build * fails, which is the only thing stopping a silent divergence. * * Why a "keep in sync" comment would not do: a wrong URL does NOT throw. It reads as `false` - every * clinician silently loses the practitioner workspace, and every seat count reads zero. That is the * DEFAULT_ROLE_PERMISSION_PROFILES failure, which drifted for weeks behind exactly such a * comment before anyone noticed. */ export const CLINICIAN_ENABLED_EXTENSION = 'https://akashichealthrecords.com/fhir/extensions/clinician-enabled'; /** * `=== true`, never truthiness - mirroring the server. An ABSENT flag is NOT enabled, and that default is * load-bearing: it is what makes this a gate rather than a suggestion. */ export const isClinicianEnabled = (role?: PractitionerRole): boolean => (role?.extension ?? []).some( (extension) => extension.url === CLINICIAN_ENABLED_EXTENSION && extension.valueBoolean === true, ); /** * THE WORKSPACE GATE THAT USED TO LIVE HERE HAS MOVED, AND IT NO LONGER ASKS THIS QUESTION. * * `applyClinicianWorkspaceGate` removed the practitioner workspace from anyone who was not * clinician-enabled, so the paid seat decided whether the door opened at all. Spencer reversed that on * 2026-09-03: anyone the ORG allows can room patients, and only the encounter itself is gated to a seat. * The replacement is `applyPractitionerWorkspaceGate` in `staffAccess.ts`, beside the resolver that * reads the organization's per-role grant - it belongs there because it is no longer about the seat. * * It is deleted rather than left unused on purpose. A gate nobody calls reads in review exactly like a * gate that works, and one that also encodes a rule which has been reversed is worse than absent. */ /** * Pure. REPLACES rather than appends, so a reader can never meet two copies of the flag and have to guess * which wins - same contract as the server's setClinicianEnabled. */ export const setClinicianEnabled = ( role: PractitionerRole, enabled: boolean, ): PractitionerRole => ({ ...role, extension: [ ...(role.extension ?? []).filter((extension) => extension.url !== CLINICIAN_ENABLED_EXTENSION), { url: CLINICIAN_ENABLED_EXTENSION, valueBoolean: enabled }, ], }); /** * Does this person hold a paid clinician seat on ANY of the roles they carry here? * * ANY, NOT THE ACTIVE ONE, and that is the same rule the token mint uses. A seat belongs to the PERSON at * a practice rather than to one of their hats, so somebody whose primary role is Office Manager and who * also holds a seated clinical role is a clinician - reading only the role they happen to be viewing * under would hide the seeing-patients side from somebody who is paying for it. * * INACTIVE ROLES DO NOT COUNT. A deactivated role is not billable and its holder is not practising, which * is how `getActiveClinicianSeats` counts the bill; the two must agree or the product and the invoice * disagree about who is a clinician. */ export const holdsClinicianSeat = ( roles: PractitionerRole[] | undefined | null, ): boolean => (roles ?? []).some((role) => role.active !== false && isClinicianEnabled(role)); /** * The billable seat count for an organization, and the people holding those seats. * * COUNTS PEOPLE, NOT ROLES. One human can hold several PractitionerRoles in the same org, and the seat * model bills per clinician - counting roles would over-report the bill on exactly the staff most likely * to have more than one. Dedupes on the practitioner reference, falling back to the role id only when a * role carries no practitioner (which should not happen, but must not silently collapse two people into * one entry). * * An INACTIVE role is not a seat. `active === false` means the person has been deactivated; they are * not billable and must not appear in the list. */ export const getActiveClinicianSeats = ( roles: PractitionerRole[] | undefined | null, ): PractitionerRole[] => { const seen = new Set(); return (roles ?? []) .filter((role) => role.active !== false && isClinicianEnabled(role)) .filter((role) => { const key = role.practitioner?.reference ?? `PractitionerRole/${role.id}`; if (seen.has(key)) { return false; } seen.add(key); return true; }); };