From 2d36d1a5ddabecefc2d8dcb7eb571ab1c13fcd92 Mon Sep 17 00:00:00 2001 From: voltsrage Date: Sat, 27 Jun 2026 16:36:35 +0800 Subject: [PATCH] Add: No toast notification system or success feedback + No corrections/supersession UI --- README.md | 35 ++- docs/vigilcare-records-gap-analysis.md | 2 +- vigilcare-records-web/src/App.vue | 3 +- .../src/components/AppHeader.vue | 1 + .../src/components/EntryForm.vue | 27 +- .../src/components/ToastContainer.vue | 52 ++++ .../src/components/VerificationForm.vue | 12 +- .../src/composables/useToast.ts | 31 ++ vigilcare-records-web/src/router/index.ts | 13 + vigilcare-records-web/src/stores/batches.ts | 25 +- vigilcare-records-web/src/types/index.ts | 41 +++ .../src/views/ApprovalView.vue | 57 +++- .../src/views/IntakeView.vue | 43 ++- .../src/views/LiveCaptureView.vue | 12 +- .../src/views/PatientHistoryView.vue | 273 ++++++++++++++++++ 15 files changed, 598 insertions(+), 29 deletions(-) create mode 100644 vigilcare-records-web/src/components/ToastContainer.vue create mode 100644 vigilcare-records-web/src/composables/useToast.ts create mode 100644 vigilcare-records-web/src/views/PatientHistoryView.vue diff --git a/README.md b/README.md index 6359ec8..938d941 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A clinical records digitization platform built with ASP.NET Core 8, PostgreSQL, MinIO, Redis, and a Vue 3 workstation UI. The domain models the scan-to-approved lifecycle at the center of any paper chart digitization system: scanned document upload, human data entry, dual-human verification with separation of duties, site-configurable clinical approval routing, atomic promotion to live clinical tables, and governed correction via supersession. -**Implementation status:** Phases 1–9 are complete. Phase 7 delivers the digitization workstation UI (`vigilcare-records-web`). Phase 8 adds Prometheus metrics, supervisor work-queue overview, cursor-paginated batch audit trails, promotion deferral with exponential backoff retry, and a Docker monitoring stack (Prometheus on **9095**, Grafana on **3013**). Phase 9 adds extended demo seed data (10 batches across all types, tracks, and statuses), the E2E verification script (`scripts/run-vigilcare-records-verification-p9.sh`), and clinical scenario documentation ([digitization-workstation-guide.md](docs/digitization-workstation-guide.md)). Post-phase hardening includes health check endpoints, user management APIs, auth rate limiting, document access audit events, batch cancellation, unified promotion retry logic, normalized patient deduplication, and assignment-time `IN_ENTRY` transitions. See [Implemented Phases](#implemented-phases) and the [gap analysis](docs/vigilcare-records-gap-analysis.md) for remaining work. +**Implementation status:** Phases 1–9 are complete. Phase 7 delivers the digitization workstation UI (`vigilcare-records-web`) with role-specific views for intake, entry, verification, clinical approval, live capture, patient history, and supervisor dashboard. Phase 8 adds Prometheus metrics, supervisor work-queue overview, cursor-paginated batch audit trails, promotion deferral with exponential backoff retry, and a Docker monitoring stack (Prometheus on **9095**, Grafana on **3013**). Phase 9 adds extended demo seed data (10 batches across all types, tracks, and statuses), the E2E verification script (`scripts/run-vigilcare-records-verification-p9.sh`), and clinical scenario documentation ([digitization-workstation-guide.md](docs/digitization-workstation-guide.md)). Post-phase hardening includes health check endpoints, user management APIs, auth rate limiting, document access audit events, batch cancellation, list/queue sorting (`sortBy`/`sortDirection`), unified promotion retry logic, normalized patient deduplication, and assignment-time `IN_ENTRY` transitions. See [Implemented Phases](#implemented-phases) and the [gap analysis](docs/vigilcare-records-gap-analysis.md) for remaining work. ## Domain Model — How It Maps to a Real Clinical System @@ -57,11 +57,11 @@ Append-only audit log entry for every state transition, field-level correction, - **Corrections and Supersession** — approved live observations are never silently edited; a correction uploads a new batch with `supersedesBatchId`, goes through the full entry → verify → approve cycle, and on promotion marks the original batch's `live_observations` rows as `is_superseded` (append-only — never deleted); `422 SUPERSEDED_BATCH_NOT_PROMOTED`, `409 BATCH_ALREADY_SUPERSEDED`, and `404 SUPERSEDED_BATCH_NOT_FOUND` guard invalid supersession targets; correction batches inherit patient context and reuse the original promotion encounter - **Patient Digitization History** — `GET /patients/:id/digitization-history` returns all batches for a patient with correction chain metadata (`isCorrection`, `hasBeenSuperseded`, `supersededByBatchId`), live vs superseded observation counts, summary totals, and per-batch audit trails; `404 PATIENT_HISTORY_NOT_FOUND` when no batches exist for the patient - **Patient Registry Search** — `GET /patients/search?q=` searches live patients by MRN or full name (minimum 2 characters); used by the intake workstation to link uploads to existing patients -- **Work Queues** — `GET /work-queue/verification` (batches in `PENDING_VERIFICATION`, sorted by submission time ASC); `GET /work-queue/entry` (batches in `UPLOADED`, `IN_ENTRY`, or `REJECTED`); `GET /work-queue/clinical-approval` (batches in `AWAITING_CLINICAL_APPROVAL`); `GET /work-queue/overview` (aggregate status counts, average queue age, reject rate, oldest pending verification — administrator only); role-restricted access +- **Work Queues** — `GET /work-queue/verification` (batches in `PENDING_VERIFICATION`); `GET /work-queue/entry` (batches in `UPLOADED`, `IN_ENTRY`, or `REJECTED`); `GET /work-queue/clinical-approval` (batches in `AWAITING_CLINICAL_APPROVAL`); `GET /work-queue/overview` (aggregate status counts, average queue age, reject rate, oldest pending verification — administrator only); all queue and batch list endpoints support `sortBy` and `sortDirection`; role-restricted access - **Batch Audit Trail API** — `GET /digitization-batches/:id/events` returns cursor-paginated digitization events with actor username and full name; accessible by administrator, verifier, and clinical approver roles - **User Directory and Management** — `GET /users?role=` lists active users for batch assignment; administrators can `POST /users` (create), `PATCH /users/:id` (update name, role, active flag), `POST /users/:id/reset-password`, and any authenticated user can `POST /users/me/change-password` with current-password verification - **Document Access Audit** — `GET /digitization-batches/:id` writes a `document_accessed` digitization event (deduplicated per user within 5 minutes) whenever a presigned scan URL is issued -- **Digitization Workstation UI** — Vue 3 SPA at `vigilcare-records-web`: role-based routing (intake, entry, verification, supervisor dashboard), split-pane scan viewer with zoom/pan/rotate, draft entry with auto-save, field-level verification checkboxes, presigned URL refresh for long sessions, JWT refresh interceptor +- **Digitization Workstation UI** — Vue 3 SPA at `vigilcare-records-web`: role-based routing and nav (intake, entry, verification, clinical approval, live capture, patient history, supervisor dashboard); split-pane scan viewer with zoom/pan/rotate; draft entry with auto-save and batch-type-aware fields (allergies, medications, discharge diagnosis); field-level verification checkboxes; clinical approval queue with scan review, approve/reject, and retroactive alert toggle; live capture form for new or existing encounters with attestation and password confirm; patient history timeline with correction chain and audit trail; presigned URL refresh for long sessions; JWT refresh interceptor and toast notifications - **Live Capture (Track B)** — `POST /live-capture/encounters/{encounterId}/observations` and `POST /live-capture/encounters` for credentialed clinicians entering vitals at bedside; clinician attestation + password re-confirm replaces the dual-human verification gate; observations promote synchronously to live tables in a single transaction with `source = live_capture`; critical threshold evaluation runs before the response returns, with inline `criticalAlert` per observation, committed `ClinicalAlert` rows, and `observation.recorded` / `alert.generated` outbox events; `422 ATTESTATION_REQUIRED`, `422 PASSWORD_CONFIRM_INVALID`, `422 EMPTY_OBSERVATIONS`, `409 ENCOUNTER_NOT_ACTIVE`, `409 ACTIVE_ENCOUNTER_EXISTS` - **Batch Status Machine** — explicit transition matrix enforced in the service layer; illegal transitions return `409`; `PROMOTED` and `CANCELLED` are terminal — corrections require a new batch with `supersedesBatchId` - **JWT Authentication** — `POST /auth/login` returns access token (15 min) and refresh token (7 days); `POST /auth/refresh` rotates tokens; `POST /auth/logout` revokes server-side; `GET /auth/me` returns authenticated user profile; BCrypt password hashing; login and refresh rate-limited to 10 requests per 5 minutes per client (`429`) @@ -173,11 +173,11 @@ VigilCareRecords/ ├── vigilcare-records-web/ # Vue 3 digitization workstation UI │ ├── src/ │ │ ├── api/client.ts # Axios client with JWT interceptor and proactive refresh -│ │ ├── stores/ # Pinia: auth (login, roles, routing), batches (CRUD, draft, verify) +│ │ ├── stores/ # Pinia: auth, batches, liveCapture │ │ ├── router/index.ts # Role-based routes and navigation guards -│ │ ├── views/ # Login, Intake, Entry, Verification, QueueDashboard -│ │ ├── components/ # ScanViewer, EntryForm, VerificationForm, BatchList, PatientSearch, AppHeader, AssignClerkDialog -│ │ ├── composables/usePresignedUrl.ts # Refreshes presigned document URLs before 15-minute expiry +│ │ ├── views/ # Login, Intake, Entry, Verification, Approval, LiveCapture, PatientHistory, QueueDashboard +│ │ ├── components/ # ScanViewer, EntryForm, VerificationForm, BatchList, PatientSearch, AppHeader, ToastContainer, AssignClerkDialog +│ │ ├── composables/ # usePresignedUrl (URL refresh), useToast (notifications) │ │ └── types/index.ts # TypeScript interfaces matching API response shapes │ ├── vite.config.ts # Dev server on port 3028; proxies /api → localhost:5217 │ └── tailwind.config.js # Clinical color palette and layout component classes @@ -366,8 +366,12 @@ Open `http://localhost:3028`. The Vite dev server proxies `/api` requests to the | `intake1` | `password` | `/intake` — upload scans, assign entry clerks | | `entry1` | `password` | `/entry` — data entry queue and split-pane form | | `verifier1` | `password` | `/verification` — field-level verification | +| `approver1` | `password` | `/approval` — clinical sign-off before promotion | +| `clinician1` | `password` | `/live-capture` — bedside vitals with attestation | | `admin1` | `password` | `/dashboard` — supervisor queue overview | +All roles can access `/patients` for patient search and digitization history. + See [docs/digitization-workstation-guide.md](docs/digitization-workstation-guide.md) for clinical scenarios (backfill, live capture, corrections) and the full clerk workflow. **Paper originals:** The scanned document is the working reference for entry and verification. The physical chart remains the legal original until jurisdiction-specific retention rules apply. Scans are never deleted on batch rejection. @@ -379,6 +383,8 @@ cd vigilcare-records-web npm run build # output in dist/ ``` +For production deployment where the UI and API are on different origins, add the frontend URL to `Cors:AllowedOrigins` in `appsettings.json` (default: `http://localhost:3028` for local dev). + ### Run Tests ```bash @@ -395,6 +401,7 @@ Integration tests use `WebApplicationFactory` with PostgreSQL, Redis, and MinIO | `CorrectionSupersessionTests` | 5 | Correction batch supersession, validation guards (non-promoted, already superseded), patient digitization history, unknown patient 404 | | `LiveCaptureIntegrationTests` | 6 | Attestation and password confirm, synchronous promotion, critical low/high potassium alerts, mixed-batch alerting, open encounter + vitals, role and validation guards, audit events | | `UserManagementTests` | 9 | User create/update/deactivate, admin password reset, self-service password change, duplicate username guard, weak password validation | +| `BatchOperationsTests` | — | Batch cancellation (status guards, Redis lock release), list/queue `sortBy`/`sortDirection` validation | ### Verification Scripts @@ -484,7 +491,7 @@ Error response: | Method | Path | Description | |---|---|---| | POST | `/digitization-batches` | Upload a scanned document and create a batch (multipart/form-data) | -| GET | `/digitization-batches` | List batches; optional `status`, `batchType`, `assignedTo`, `track` filters; paginated | +| GET | `/digitization-batches` | List batches; optional `status`, `batchType`, `assignedTo`, `track` filters; paginated and sortable (`sortBy`, `sortDirection`; default `createdAt desc`) | | GET | `/digitization-batches/{id}` | Batch detail with presigned document URL (15-minute expiry); audits `document_accessed` | | PATCH | `/digitization-batches/{id}/assign` | Assign batch to an entry clerk (Redis lock; transitions to `IN_ENTRY`) | | POST | `/digitization-batches/{id}/cancel` | Cancel batch permanently (administrator only; `UPLOADED`, `IN_ENTRY`, or `REJECTED`) | @@ -640,12 +647,12 @@ Error response: | Method | Path | Auth | Description | |---|---|---|---| -| GET | `/work-queue/verification` | Verifier, Clinical Approver, Administrator | Batches in `PENDING_VERIFICATION`, sorted by submission time | +| GET | `/work-queue/verification` | Verifier, Clinical Approver, Administrator | Batches in `PENDING_VERIFICATION` | | GET | `/work-queue/entry` | Data Entry Clerk, Administrator | Batches awaiting or in entry (`UPLOADED`, `IN_ENTRY`, `REJECTED`) | | GET | `/work-queue/clinical-approval` | Clinical Approver, Administrator | Batches in `AWAITING_CLINICAL_APPROVAL` | | GET | `/work-queue/overview` | Administrator | Aggregate metrics: status counts, average queue age, 24h reject rate, oldest pending verification | -All work queue endpoints support pagination via `?page=1&pageSize=20` (except `overview`). +All work queue endpoints support pagination via `?page=1&pageSize=20` and sorting via `?sortBy=updatedAt&sortDirection=asc` (except `overview`). Allowed sort fields: `createdAt`, `updatedAt`, `status`, `batchType`, `track`. Invalid `sortBy` returns `422 INVALID_SORT_FIELD`. ### Batch Audit Trail @@ -1101,6 +1108,10 @@ List endpoints (batch list, work queues) use offset pagination: |---|---|---| | `page` | 1 | Page number (1-based) | | `pageSize` | 20 | Items per page | +| `sortBy` | `createdAt` (batch list) or `updatedAt` (work queues) | Sort field: `createdAt`, `updatedAt`, `status`, `batchType`, `track` | +| `sortDirection` | `desc` (batch list) or `asc` (work queues) | `asc` or `desc` | + +Invalid `sortBy` values return `422` with code `INVALID_SORT_FIELD`. Response shape: @@ -1148,7 +1159,7 @@ Phases 1–9 are fully implemented and verified via integration tests and per-ph | 4 | Approval with three-person separation of duties (entry ≠ verifier ≠ approver), atomic promotion to live `patients`/`encounters`/`observations` tables in a single PostgreSQL transaction, PostgreSQL sequence-backed MRN generation (`VCR-NNNNNN`), patient dedup by normalized name + DOB, encounter matching by patient + department + active status, transactional outbox (`observation.created` events), retroactive alert policy per batch, `Idempotency-Key` header with 24h TTL for safe retries, `PromotionTests` integration tests, Phase 4 verification script | Done | | 5 | Correction batches via `supersedesBatchId`, supersession validation on create (`422`/`404`/`409`), append-only `live_observations` supersession flags (`is_superseded`, `superseded_by_batch_id`, `superseded_at`), correction promotion reuses original encounter and linked patient, `correction_uploaded`/`correction_promoted`/`superseded` audit events, `GET /patients/:id/digitization-history` with correction chain and per-batch audit trails, `CorrectionSupersessionTests` integration tests, Phase 5 verification script | Done | | 6 | Track B live capture: `LiveCaptureController` with clinician-only endpoints, `AttestationService` (role + password re-confirm), synchronous promotion via `LiveCaptureService`, Redis-backed critical threshold evaluation, inline critical alerts + committed `ClinicalAlert` rows, `observation.recorded` and `alert.generated` outbox events, open-encounter + vitals outpatient workflow, `LiveCaptureIntegrationTests`, Phase 6 verification script | Done | -| 7 | Digitization workstation UI (`vigilcare-records-web`): Vue 3 + Pinia + Tailwind, role-based routing, split-pane scan viewer, draft entry with auto-save, verification checkboxes, patient search, assign-clerk dialog, presigned URL refresh, supervisor dashboard shell | Done | +| 7 | Digitization workstation UI (`vigilcare-records-web`): Vue 3 + Pinia + Tailwind, role-based routing and nav, split-pane scan viewer, batch-type-aware draft entry (allergies, medications, discharge diagnosis), verification checkboxes, clinical approval view, live capture view (new/existing encounter), patient history timeline, patient search, assign-clerk dialog, presigned URL refresh, supervisor dashboard, toast notifications | Done | | 8 | Prometheus metrics (`GET /metrics`), custom digitization gauges/histograms/counters, `MetricsCollectorService`, supervisor `GET /work-queue/overview`, cursor-paginated `GET /digitization-batches/:id/events`, promotion deferral (202 / `PROMOTION_DEFERRED`), `PromotionRetryService` with exponential backoff, Docker Prometheus (9095) + Grafana (3013), Phase 8 verification script | Done | | 9 | Extended seed data (10 demo batches across all types/tracks/statuses), E2E verification script (`run-vigilcare-records-verification-p9.sh`), clinical scenario docs (`docs/digitization-workstation-guide.md`), `UserManagementTests` | Done | -| — | Health probes (`/health/live`, `/health/ready`, `/health/startup`), user management CRUD + password endpoints, auth rate limiting, document access audit, batch cancellation, unified `ExecutePromotionCoreAsync` for approve + retry promote, assignment-time `IN_ENTRY` transition | Done | +| — | Health probes (`/health/live`, `/health/ready`, `/health/startup`), user management CRUD + password endpoints, auth rate limiting, document access audit, batch cancellation, list/queue sorting, unified `ExecutePromotionCoreAsync` for approve + retry promote, assignment-time `IN_ENTRY` transition, CORS policy for production frontend origins | Done | diff --git a/docs/vigilcare-records-gap-analysis.md b/docs/vigilcare-records-gap-analysis.md index be0f587..d25c116 100644 --- a/docs/vigilcare-records-gap-analysis.md +++ b/docs/vigilcare-records-gap-analysis.md @@ -604,7 +604,7 @@ Two of the seven batch types (`ALLERGY_UPDATE` and `MEDICATION_LIST`) cannot be --- -## P4 — No corrections/supersession UI +## ~~P4 — No corrections/supersession UI~~ DONE ### Problem diff --git a/vigilcare-records-web/src/App.vue b/vigilcare-records-web/src/App.vue index b28aea4..f394b1f 100644 --- a/vigilcare-records-web/src/App.vue +++ b/vigilcare-records-web/src/App.vue @@ -1,7 +1,8 @@ \ No newline at end of file diff --git a/vigilcare-records-web/src/components/AppHeader.vue b/vigilcare-records-web/src/components/AppHeader.vue index 39516fd..89cfda3 100644 --- a/vigilcare-records-web/src/components/AppHeader.vue +++ b/vigilcare-records-web/src/components/AppHeader.vue @@ -8,6 +8,7 @@ Verification Approval Live Capture + History Dashboard diff --git a/vigilcare-records-web/src/components/EntryForm.vue b/vigilcare-records-web/src/components/EntryForm.vue index 324fa8e..52fb6b1 100644 --- a/vigilcare-records-web/src/components/EntryForm.vue +++ b/vigilcare-records-web/src/components/EntryForm.vue @@ -232,6 +232,7 @@ + + diff --git a/vigilcare-records-web/src/components/VerificationForm.vue b/vigilcare-records-web/src/components/VerificationForm.vue index f67ad46..bc92092 100644 --- a/vigilcare-records-web/src/components/VerificationForm.vue +++ b/vigilcare-records-web/src/components/VerificationForm.vue @@ -189,6 +189,7 @@ import { ref, computed, watch } from 'vue' import { useRouter } from 'vue-router' import { useBatchStore } from '../stores/batches' +import { useToast } from '../composables/useToast' import ObservationRow from '../components/ObservationRow.vue' import type { BatchDetailResponse, DraftObservation } from '../types' @@ -199,6 +200,7 @@ const props = defineProps<{ const batchStore = useBatchStore() const router = useRouter() +const toast = useToast() const processing = ref(false) const errorMessage = ref('') const showRejectDialog = ref(false) @@ -344,9 +346,12 @@ async function approveVerification() { passed, })) await batchStore.verifyBatch(props.batchId, checks, true) + toast.success('Batch verified successfully') router.push('/verification') } catch (e: unknown) { - errorMessage.value = e instanceof Error ? e.message : 'Verification failed' + const msg = e instanceof Error ? e.message : 'Verification failed' + errorMessage.value = msg + toast.error(msg) } finally { processing.value = false } @@ -358,9 +363,12 @@ async function rejectVerification() { try { await batchStore.rejectBatch(props.batchId, rejectionReason.value) showRejectDialog.value = false + toast.warning('Batch rejected') router.push('/verification') } catch (e: unknown) { - errorMessage.value = e instanceof Error ? e.message : 'Rejection failed' + const msg = e instanceof Error ? e.message : 'Rejection failed' + errorMessage.value = msg + toast.error(msg) } finally { processing.value = false } diff --git a/vigilcare-records-web/src/composables/useToast.ts b/vigilcare-records-web/src/composables/useToast.ts new file mode 100644 index 0000000..5e8a72d --- /dev/null +++ b/vigilcare-records-web/src/composables/useToast.ts @@ -0,0 +1,31 @@ +import { ref } from 'vue' + +export type ToastType = 'success' | 'error' | 'warning' | 'info' + +export interface Toast { + id: number + message: string + type: ToastType + duration: number +} + +let nextId = 0 +export const toasts = ref([]) + +function addToast(message: string, type: ToastType, duration = 4000) { + const id = nextId++ + toasts.value.push({ id, message, type, duration }) + setTimeout(() => { + toasts.value = toasts.value.filter(t => t.id !== id) + }, duration) +} + +export function useToast() { + return { + success: (message: string) => addToast(message, 'success'), + error: (message: string) => addToast(message, 'error', 6000), + warning: (message: string) => addToast(message, 'warning', 5000), + info: (message: string) => addToast(message, 'info'), + toasts, + } +} diff --git a/vigilcare-records-web/src/router/index.ts b/vigilcare-records-web/src/router/index.ts index 34ede20..91e81ef 100644 --- a/vigilcare-records-web/src/router/index.ts +++ b/vigilcare-records-web/src/router/index.ts @@ -65,6 +65,19 @@ const routes: RouteRecordRaw[] = [ }, props: true, }, + { + path: '/patients/:patientId/history', + name: 'PatientHistory', + component: () => import('../views/PatientHistoryView.vue'), + meta: { requiresAuth: true }, + props: true, + }, + { + path: '/patients', + name: 'PatientSearch', + component: () => import('../views/PatientHistoryView.vue'), + meta: { requiresAuth: true }, + }, { path: '/live-capture', name: 'LiveCapture', diff --git a/vigilcare-records-web/src/stores/batches.ts b/vigilcare-records-web/src/stores/batches.ts index 0c9edb1..15ed4f1 100644 --- a/vigilcare-records-web/src/stores/batches.ts +++ b/vigilcare-records-web/src/stores/batches.ts @@ -9,6 +9,7 @@ import type { DraftObservation, BatchListResponse, FieldCheck, + PatientDigitizationHistoryResponse, } from '../types' export const useBatchStore = defineStore('batches', () => { @@ -68,13 +69,15 @@ export const useBatchStore = defineStore('batches', () => { file: File, batchType: string, track: string, - patientId?: string + patientId?: string, + supersedesBatchId?: string, ): Promise { loading.value = true error.value = null try { const fields: Record = { batchType, track } if (patientId) fields.patientId = patientId + if (supersedesBatchId) fields.supersedesBatchId = supersedesBatchId const response = await uploadFile( 'digitization-batches', @@ -194,6 +197,25 @@ async function assignBatch(batchId: string, entryClerkUserId: string): Promise { + loading.value = true + error.value = null + try { + const response = await get( + `patients/${patientId}/digitization-history`, + ) + if (response.success && response.data) { + return response.data + } + return null + } catch (e: unknown) { + error.value = e instanceof Error ? e.message : 'Failed to load patient history' + return null + } finally { + loading.value = false + } + } + return { batches, currentBatch, @@ -216,5 +238,6 @@ async function assignBatch(batchId: string, entryClerkUserId: string): Promise + +
+

Correction Batch

+

+ This batch corrects and will supersede batch + {{ currentBatch.supersedesBatchId.substring(0, 8) }}... +

+ +
+
Patient Demographics @@ -134,6 +153,21 @@

Patient MRN: {{ promotionResult.mrn }}

Encounter: {{ promotionResult.encounterId?.substring(0, 8) }}...

Observations promoted: {{ promotionResult.observationIds?.length }}

+
+ + +
@@ -188,6 +222,7 @@ import { ref, computed, onMounted, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useBatchStore } from '../stores/batches' import { usePresignedUrl } from '../composables/usePresignedUrl' +import { useToast } from '../composables/useToast' import AppHeader from '../components/AppHeader.vue' import ScanViewer from '../components/ScanViewer.vue' import BatchList from '../components/BatchList.vue' @@ -198,6 +233,7 @@ const props = defineProps<{ batchId?: string }>() const batchStore = useBatchStore() const route = useRoute() const router = useRouter() +const toast = useToast() const batchId = computed(() => props.batchId ?? (route.params.batchId as string | undefined)) const currentBatch = computed(() => batchStore.currentBatch) @@ -231,21 +267,37 @@ async function approve() { const response = await batchStore.approveBatch(batchId.value, enableRetroactiveAlerts.value) if (response?.status === 202) { deferred.value = true + toast.info('Approved. Promotion will be retried automatically.') } else if (response?.data) { promotionResult.value = response.data + toast.success('Batch approved and promoted successfully') } } catch (e: unknown) { const msg = e instanceof Error ? e.message : 'Approval failed' if (msg.includes('PROMOTION_DEFERRED')) { deferred.value = true + toast.info('Approved. Promotion will be retried automatically.') } else { errorMessage.value = msg + toast.error(msg) } } finally { processing.value = false } } +function createCorrection() { + if (!batchId.value) return + router.push({ + path: '/intake', + query: { + supersedesBatchId: batchId.value, + patientId: currentBatch.value?.patientId ?? undefined, + batchType: currentBatch.value?.batchType ?? undefined, + }, + }) +} + async function reject() { if (!batchId.value) return processing.value = true @@ -253,9 +305,12 @@ async function reject() { try { await batchStore.rejectBatch(batchId.value, rejectionReason.value) showRejectDialog.value = false + toast.warning('Batch rejected') router.push('/approval') } catch (e: unknown) { - errorMessage.value = e instanceof Error ? e.message : 'Rejection failed' + const msg = e instanceof Error ? e.message : 'Rejection failed' + errorMessage.value = msg + toast.error(msg) } finally { processing.value = false } diff --git a/vigilcare-records-web/src/views/IntakeView.vue b/vigilcare-records-web/src/views/IntakeView.vue index 6e40266..74bc66f 100644 --- a/vigilcare-records-web/src/views/IntakeView.vue +++ b/vigilcare-records-web/src/views/IntakeView.vue @@ -64,6 +64,21 @@ + +
+

Correction Batch

+

+ This upload will supersede batch + {{ supersedesBatchId.substring(0, 8) }}... +

+ +
+
{{ uploadError }}
@@ -104,18 +119,23 @@