import { useEffect, useMemo, useRef, useState } from 'react'; import { useHttpService } from '../services/HttpService'; import { environment } from '../environments/environment'; import { isSaveRetired, useSaveScope, type SaveOperation, type SaveTransport } from '../utils/saveOperation'; import { writeThroughViewer } from '../utils/writeThroughHold'; import { clearDiagnosticOperation, diagnosticDebtRemains, diagnosticStatus, pendingDiagnosticOperation, rememberDiagnosticOperation, type DiagnosticPublicationKind, type DiagnosticPublicationRequest, type DiagnosticPublicationStatus, } from '../utils/diagnosticPublicationOperation'; import type { EnteredResult } from './useDiagnosticResult'; interface ActiveAttempt { subject: string; operationId: string; operation: SaveOperation; sent: boolean } function reportMatchesOperation( result: EnteredResult | undefined, kind: DiagnosticPublicationKind, operationId: string, reviewed: EnteredResult['reviewBasis'], committedVersion?: string, ): boolean { const owner = result?.publicationOperation; if (owner) return owner.kind === kind && owner.operationId === operationId; const current = result?.reviewBasis?.report; if (!current || current.id !== reviewed?.report.id) return false; if (current.versionId === reviewed.report.versionId) return true; const published = result?.publishedOperation; return published ? published.kind === kind && published.operationId === operationId : current.versionId === committedVersion; } export function useDiagnosticPublicationOperation( kind: DiagnosticPublicationKind, orderId: string | undefined, patient: string | undefined, result: EnteredResult | undefined, permitted: boolean, send: (orderId: string, request: DiagnosticPublicationRequest, transport: SaveTransport) => Promise, onCommitted: (status: DiagnosticPublicationStatus, closed: boolean) => void, ) { const { get } = useHttpService(); const subject = JSON.stringify([writeThroughViewer(), orderId, result?.patientId, patient, result?.reviewBasis?.report.id]); const key = JSON.stringify([writeThroughViewer(), result?.patientId, orderId, kind]); const [busy, setBusy] = useState(false); const [notice, setNotice] = useState(); const [held, setHeld] = useState<{ subject: string; operationId: string }>(); const [closedEarlier, setClosedEarlier] = useState>(() => new Set()); const active = useRef(); const notified = useRef(); const initiated = useRef<{ subject: string; operationId: string; reviewed?: EnteredResult['reviewBasis'] }>(); const latest = useRef({ subject, permitted, result }); latest.current = { subject, permitted, result }; const saves = useSaveScope(subject, () => { setBusy(false); setNotice(undefined); active.current = undefined; initiated.current = undefined; notified.current = undefined; }); useEffect(() => { if (permitted || !active.current || active.current.sent) return; active.current.operation.retire(); active.current = undefined; initiated.current = undefined; setBusy(false); setNotice('You can no longer do this here, so it was not sent.'); }, [permitted]); const records = useMemo(() => { const local = pendingDiagnosticOperation(key); const all = result?.operations?.items ?? []; const rows = all.filter(status => status.kind === kind && diagnosticDebtRemains(status)); if (local && !all.some(status => status.kind === kind && status.operationId === local.operationId)) rows.push({ ...local, kind, operationOutcome: 'pending', }); return rows.filter(status => !closedEarlier.has(`${key}:${status.operationId}`)); }, [key, kind, result, closedEarlier]); useEffect(() => { const current = records.find(status => { if (status.clinical?.state === 'committed') return false; if (result?.publicationOperation) return result.publicationOperation.kind === kind && result.publicationOperation.operationId === status.operationId; if (result?.publishedOperation?.operationId === status.operationId && result.publishedOperation.kind === kind) return true; const original = status.reviewedReport; const basis = result?.reviewBasis?.report; return !original || original.id === basis?.id && (original.versionId === basis.versionId || status.claimVersionId === basis.versionId); }); setHeld(previous => previous?.subject === subject && initiated.current?.subject === subject && initiated.current.operationId === previous.operationId && reportMatchesOperation(result, kind, previous.operationId, initiated.current.reviewed) ? previous : current ? { subject, operationId: current.operationId } : undefined); }, [records, subject, kind, result]); const pending = held?.subject === subject ? held.operationId : undefined; const available = Boolean(orderId && result?.reviewBasis && !result.reviewUnavailable && !result.entryOperation && !result.publicationOperation && !result.publicationUnavailable && result.operations?.state === 'available' && result.operations.complete); const earlier = records.filter(status => status.operationId !== pending); const run = async (body?: { text?: string; criticalFinding?: unknown }, earlierId?: string) => { const resuming = earlierId ?? pending; if (!orderId || active.current?.subject === subject || (!resuming && (!available || !permitted))) return; const operationId = resuming ?? crypto.randomUUID(); const operation = saves.start(); const mine: ActiveAttempt = { subject, operationId, operation, sent: false }; if (!resuming) initiated.current = { ...mine, reviewed: result?.reviewBasis }; active.current = mine; let sent = false; setBusy(true); setNotice(undefined); const transport: SaveTransport = { ...operation.transport, admit: (phase?: 'answer') => { operation.admit(); if (phase !== 'answer' && !latest.current.permitted) throw new Error('You can no longer do this here.'); }, onSend: () => { sent = true; mine.sent = true; if (!resuming) rememberDiagnosticOperation(key, operationId, result?.reviewBasis?.report); }, }; const accept = (status: DiagnosticPublicationStatus) => { operation.admit(); if (active.current !== mine || latest.current.subject !== subject) return; const closed = !diagnosticDebtRemains(status); const currentReport = reportMatchesOperation(latest.current.result, kind, operationId, result?.reviewBasis, status.clinical?.versionId); if (status.operationOutcome === 'not-admitted' && !resuming) { clearDiagnosticOperation(key, operationId); setHeld(undefined); setNotice(status.message ?? 'This action was not admitted. Reload the result before trying again.'); return; } if (!earlierId && currentReport) setHeld(closed ? undefined : mine); if (closed) { clearDiagnosticOperation(key, operationId); setClosedEarlier(previous => new Set(previous).add(`${key}:${operationId}`)); } setNotice(earlierId || !currentReport ? closed ? 'The earlier action has no remaining follow-up work.' : 'The earlier action still has follow-up work.' : status.clinical?.state === 'committed' ? closed ? 'The report is saved and published.' : 'The report is saved and published. Follow-up work is pending.' : 'The outcome is not yet confirmed. Check or continue this same action.'); if (!earlierId && status.clinical?.state === 'committed' && status.continuations?.order === 'committed' && currentReport && (initiated.current?.subject === subject && initiated.current.operationId === operationId || result?.reviewBasis?.report.versionId === status.clinical.versionId) && notified.current !== `${operationId}:${closed}`) { notified.current = `${operationId}:${closed}`; onCommitted(status, closed); } }; try { if (resuming) { const read = await operation.step(() => get(`${environment.fhirBaseApi}/diagnostic-order/${orderId}/result?operationKind=${kind}&operationId=${encodeURIComponent(operationId)}`, operation.transport).catch((error: unknown) => { const status = diagnosticStatus((error as { body?: unknown })?.body, kind, operationId); if (status) return status; throw error; })); const status = diagnosticStatus(read, kind, operationId); if (!status) throw new Error('The operation could not be confirmed.'); if (!diagnosticDebtRemains(status) || !permitted) { accept(status); return; } } const request: DiagnosticPublicationRequest = resuming ? { operationId, resume: true } : { operationId, basis: structuredClone(result!.reviewBasis!), ...structuredClone(body ?? {}) }; const reply = await operation.step(() => send(orderId, request, transport), true); const status = diagnosticStatus(reply, kind, operationId); if (!status) throw new Error('The operation could not be confirmed.'); accept(status); } catch (error) { if (!operation.belongs() || isSaveRetired(error) || active.current !== mine) return; const status = diagnosticStatus((error as { body?: unknown })?.body, kind, operationId); if (status) accept(status); else if (sent || resuming) { const currentReport = reportMatchesOperation(latest.current.result, kind, operationId, result?.reviewBasis); if (!earlierId && currentReport) setHeld(mine); setNotice(earlierId || !currentReport ? 'The earlier action is not yet confirmed.' : 'The outcome is not yet confirmed. Check or continue this same action.'); } else { setHeld(undefined); setNotice('This action was not sent. Reload the result before trying again.'); } } finally { if (operation.belongs() && active.current === mine) { active.current = undefined; setBusy(false); } operation.retire(); } }; return { busy, notice, pending, earlier, available, run }; }