import type { Encounter } from 'fhir/r4'; // Prepare a video visit via the server's named operation (POST /R4/Encounter, X-Encounter-Operation: prepare-visit); the // client enters only on `entered`, with the record returned. Deploy the server first: an older one ignores the header. export const ENCOUNTER_OPERATION_HEADER = 'X-Encounter-Operation'; export const PREPARE_VISIT_OPERATION = 'prepare-visit'; export type PrepareVisitAnswer = | { result: 'entered'; how: 'created' | 'reused' | 'stamped' | 'restamped' | 'revived' | 'adopted'; encounter: Encounter; owed?: 'stamp' } | { result: 'visit-not-open'; owed?: 'close-open-visit-records' } | { result: 'generation-unproved'; reason: string; owed?: 'adopt-preparation' | 'redo-check-in'; record?: string } | { result: 'needs-update-authority' } | { result: 'record-conflict' } | { result: 'write-unknown' }; const RESULTS = new Set(['entered', 'visit-not-open', 'generation-unproved', 'needs-update-authority', 'record-conflict', 'write-unknown']); /** A typed answer, and for `entered` one that carries the record to enter. Anything else is not an answer. */ export const isPrepareVisitAnswer = (body: unknown): body is PrepareVisitAnswer => { const result = (body as { result?: unknown } | null | undefined)?.result; if (typeof result !== 'string' || !RESULTS.has(result)) return false; if (result !== 'entered') return true; const encounter = (body as { encounter?: Encounter }).encounter; return encounter?.resourceType === 'Encounter' && typeof encounter.id === 'string'; }; export const PREPARE_VISIT_NOT_OPEN = 'This visit is no longer scheduled, so it was not opened.'; export const PREPARE_VISIT_CLOSE_RECORDS = 'Its prepared record is still open: close it from the visit\'s details (Close open visit records).'; export const PREPARE_VISIT_UNPROVED = 'Could not confirm this visit is still the one that was prepared, so it was not opened. Try again?'; export const PREPARE_VISIT_ADOPT = 'This visit\'s prepared record was made before the visit was cancelled and booked again,' + ' so it could not be confirmed as this booking\'s. Use it for this booking?'; export const PREPARE_VISIT_REDO_CHECK_IN = 'This visit was checked in before it was cancelled and booked again, so it was not opened.' + ' Ask the front desk to undo the check-in and check the patient in again, then open the visit.'; export const PREPARE_VISIT_NEEDS_EDITOR = 'This visit\'s prepared record needs updating by someone who can edit visits, so it was not opened.'; export const PREPARE_VISIT_CONFLICT = 'This visit\'s record does not match this booking, so it was not opened.'; export const PREPARE_VISIT_UNKNOWN = 'Opening this visit may already have been saved, though it is not showing yet, so it was not opened.' + ' Try again? Trying again finds the same visit and never makes a second one.'; /** What a person is told about an answer that does not enter, and the one explicit next step it offers, if any. */ export const prepareVisitLine = (answer: Exclude): { message: string; next?: 'try-again' | 'adopt' } => { switch (answer.result) { case 'visit-not-open': return { message: answer.owed === 'close-open-visit-records' ? `${PREPARE_VISIT_NOT_OPEN} ${PREPARE_VISIT_CLOSE_RECORDS}` : PREPARE_VISIT_NOT_OPEN }; case 'generation-unproved': if (answer.owed === 'adopt-preparation') return { message: PREPARE_VISIT_ADOPT, next: 'adopt' }; if (answer.owed === 'redo-check-in') return { message: PREPARE_VISIT_REDO_CHECK_IN }; return { message: PREPARE_VISIT_UNPROVED, next: 'try-again' }; case 'needs-update-authority': return { message: PREPARE_VISIT_NEEDS_EDITOR }; case 'record-conflict': return { message: PREPARE_VISIT_CONFLICT }; case 'write-unknown': return { message: PREPARE_VISIT_UNKNOWN, next: 'try-again' }; } };