import type { Appointment } from 'fhir/r4'; import { asRecordChanged, PAGE_OUT_OF_DATE_MESSAGE, RECORD_CHANGED_MESSAGE, RecordChangedError } from './recordChanged'; /** * The version an Appointment edit names: that of the record the action was based on, never a fresh read taken to * make an older choice look current. One read without a version cannot be named, so nothing is sent. */ export const appointmentVersion = (appointment: Appointment): string => { const version = appointment.meta?.versionId; if (!version) throw new RecordChangedError(PAGE_OUT_OF_DATE_MESSAGE, true); return version; }; /** The server also answers 409 STALE_REVISION when another write won between its read and its write. */ export const asAppointmentChanged = (error: unknown): unknown => { const { status, body } = (error ?? {}) as { status?: unknown; body?: { code?: unknown; error?: unknown } }; if (status === 409 && (body?.code === 'STALE_REVISION' || body?.error === 'STALE_REVISION')) { return new RecordChangedError(RECORD_CHANGED_MESSAGE); } return asRecordChanged(error); }; /** What the server could not finish after saving, from `X-Appointment-Follow-Up` (comma-separated). */ export const APPOINTMENT_FOLLOW_UP_HEADER = 'X-Appointment-Follow-Up'; /** * A change confirmed saved by reading it back after its answer was lost: what its answer would have said was left * unfinished is not known. */ export const COMPANIONS_UNCONFIRMED = 'companions-unconfirmed'; const FOLLOW_UP_LINES: Record = { [COMPANIONS_UNCONFIRMED]: 'It was saved, but whether the schedule was updated to match could not be confirmed. Check the schedule.', 'old-time-not-released': 'Its old time could not be freed, so the schedule may still show it as booked.', 'time-not-updated': 'Its time on the schedule could not be updated, so the schedule may not show this change yet.', 'time-not-held': 'Its time could not be protected against another booking. Check the schedule for a clash.', 'old-reservation-not-released': 'Its old time may stay closed to new bookings for a while.', }; /** What a saved change left unfinished, one plain line each; the change itself was saved. */ export const appointmentFollowUpLines = (followUps: readonly string[]): string[] => followUps.map((followUp) => FOLLOW_UP_LINES[followUp] ?? 'Part of the schedule could not be updated. Check the schedule.'); export const parseAppointmentFollowUps = (header: string | null): string[] => (header ?? '').split(',').map((part) => part.trim()).filter(Boolean); /** * What a save that did not go through did to the visit's own records (its Encounter), when it changed any: kept because * the visit already has the status they follow, put back, or not known. The save itself is not claimed either way. */ export type VisitEncounterOutcome = 'kept' | 'reverted' | 'unknown'; const VISIT_ENCOUNTER_OUTCOMES: readonly string[] = ['kept', 'reverted', 'unknown']; const VISIT_ENCOUNTER_PREFIX = 'visit-encounter-'; export const visitEncounterOutcomeOf = (error: unknown): VisitEncounterOutcome | undefined => { const carried = (error as { visitEncounterOutcome?: unknown } | null)?.visitEncounterOutcome; if (typeof carried === 'string' && VISIT_ENCOUNTER_OUTCOMES.includes(carried)) return carried as VisitEncounterOutcome; const header = (error as { header?: (name: string) => string | null } | null)?.header?.(APPOINTMENT_FOLLOW_UP_HEADER) ?? null; const reported = parseAppointmentFollowUps(header).find((followUp) => followUp.startsWith(VISIT_ENCOUNTER_PREFIX)); const outcome = reported?.slice(VISIT_ENCOUNTER_PREFIX.length); return outcome && VISIT_ENCOUNTER_OUTCOMES.includes(outcome) ? outcome as VisitEncounterOutcome : undefined; }; /** Carried on whatever the failure becomes, so a refusal turned into "changed since" still says it. */ export const withVisitEncounterOutcome = (error: E, outcome: VisitEncounterOutcome | undefined): E => { if (outcome && error && typeof error === 'object') Object.assign(error, { visitEncounterOutcome: outcome }); return error; }; const VISIT_ENCOUNTER_LINES: Record = { kept: 'Its visit record was already brought in line with this visit, and that change stands.', reverted: 'The change it made to its visit record was put back.', unknown: 'Whether its visit record was changed could not be confirmed. Check the visit before trying again.', }; /** One plain line for what a failed save did to the visit's record, or nothing when it did nothing there. */ export const visitEncounterLineOf = (error: unknown): string | undefined => { const outcome = visitEncounterOutcomeOf(error); return outcome ? VISIT_ENCOUNTER_LINES[outcome] : undefined; };