IPD Medication & Order Master Contracts
Source of truth for the IPD medication & order system: entity contracts, 12 status enums, event payloads, dispense-cycle engine, dose patterns.
Single source of truth for entity contracts, status progressions, event payloads, read-model schemas, and the end-to-end lifecycle from doctor order → pharmacy → nurse administration.
Last updated: 2026-05-19
Table of Contents
- System Overview
- Entity Contracts (MongoDB — Write Model)
- Status Enums & Progressions
- Hospital Events & Payloads
- Read-Model Tables (Supabase)
- Orchestrator Handlers & Side Effects
- Dispense Cycle Engine
- Order Bus (Cross-Department Stream)
- Policy Gates & Safety
- REST API Endpoints
- Frontend Component Map
- Dose Pattern System
- Related Docs (Index)
1. System Overview
Two-Layer Source of Truth
┌─────────────────────────────────────────────────────────────────────┐
│ CANONICAL WRITE MODEL │
│ │
│ NestJS Medication Service ──NATS──▶ MongoDB │
│ (orderRequest, medicationRequest, (order_request, │
│ medicationAdministration, etc.) medication_request, │
│ medication_administration) │
└──────────────────────────┬──────────────────────────────────────────┘
│ hospital_events (INSERT)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ READ MODEL / PROJECTION │
│ │
│ Supabase (PostgreSQL) │
│ ├─ hospital_events (append-only event log) │
│ ├─ encounter_journey_cache (per-encounter manifest) │
│ ├─ department_queues (operational worklists) │
│ ├─ medication_requests (IPD Rx projection) │
│ ├─ medication_holds (auto-hold tracking) │
│ ├─ medication_administration_cycles (e-MAR time slots) │
│ ├─ medication_administrations (permanent EMAR record) │
│ ├─ medication_label_qrcodes (barcode/QR labels) │
│ ├─ mar_audit (append-only MAR audit) │
│ ├─ order_bus (cross-dept order stream) │
│ ├─ dispense_cycle_configs (materialization rules) │
│ └─ dispense_cycle_runs (execution log) │
│ │
│ encounter-orchestrator (Deno Edge Function) │
│ └─ inpatient-handlers/ (event → side-effect handlers) │
│ │
│ dispense-cycle-runner (Deno Edge Function) │
│ └─ materializes e-MAR cycles from medication_requests │
└─────────────────────────────────────────────────────────────────────┘
End-to-End Lifecycle (Happy Path)
5-Step IPD Medication Flow:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Doctor │──▶│ Nurse │──▶│ Pharmacy │──▶│ Dispense │──▶│ e-MAR │
│ Order │ │ Ack │ │ (Screen/ │ │ Rounds │ │ Admin │
│ │ │ Checklist│ │ Prep/ │ │ (รอบยา) │ │ (ให้ยา) │
│ คำสั่ง │ │ รับทราบ │ │ Dispense)│ │ │ │ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
Detailed Event Flow:
Doctor orders medication
│
▼
OrderRequest (MongoDB) ─────────▶ hospital_events: ORDER_CREATED
│ │
▼ ▼
MedicationRequest (MongoDB) encounter-orchestrator
│ │
▼ ▼
Workflow: wait-acknowledgement department_queues: wait-acknowledgement
│
▼ (Nurse reviews & acknowledges via DialogNurseAcknowledge)
POST /orderRequests/updateAcknowledge/:_id
│ sets acknowledgeBy + acknowledgeAt
│ items grouped by productType:
│ drug → T (queues_screening_pharmacy)
│ lab → L (queues_lab)
│ image → X (queues_radiology)
│ nutrition → N (queues_nutrition)
│ procedure → P (queues_procedure)
│
├─ hospital_events: ORDERS_ACKNOWLEDGED
├─ syncDepartmentQueue → POST /encounterJourney/queue-transition
│ (wait-acknowledgement → wait-result)
│
▼ (Pharmacy receives acknowledged drug orders)
hospital_events: RX_PRESCRIBED ───▶ handleRxPrescribed
│ ├─ department_queues: ipd_pharmacy_pending
│ ├─ encounter_journey_cache: has_active_medications
│ └─ acknowledgement_requests (if urgent)
│
▼ (Pharmacist screens & verifies)
hospital_events: RX_VERIFIED ─────▶ handleRxVerified
│ ├─ close ipd_pharmacy_pending → COMPLETED
│ ├─ department_queues: ipd_pharmacy_verified
│ └─ close urgent ack requests
│
▼ (Pharmacist prepares & dispenses)
hospital_events: RX_DISPENSED ────▶ handleRxDispensed
│ ├─ close ipd_pharmacy_verified → COMPLETED
│ └─ department_queues: ipd_pharmacy_dispensed (audit)
│
▼ (Nurse assigns dispensed drugs to dispense rounds)
medication_requests INSERT/UPDATE ─▶ trg_medication_request_materialize_cycles
│ └─ hospital_events: manifest.rx.cycles_requested
│ │
│ ▼
│ handleRxCycleMaterialization
│ ├─ upsert_medication_request() RPC
│ ├─ invoke dispense-cycle-runner edge fn
│ └─ encounter_journey_cache: pending_cycle_count
│
▼ (dispense-cycle-runner materializes time slots per round)
medication_administration_cycles ──▶ e-MAR UI (nurse view)
│ rounds: เช้า 06:00 / กลางวัน 12:00 / เย็น 18:00 / ก่อนนอน 22:00
│
▼ (Nurse scans patient + drug, administers)
hospital_events: MAR_ADMINISTERED ─▶ handleMarAdministered
│ └─ mar_audit (append-only)
│
▼
medication_administrations (Supabase) ── permanent EMAR record
MedicationAdministration (MongoDB) ── canonical write
2. Entity Contracts
2.1 OrderRequest (MongoDB: order_request)
The meta-order wrapper — one per prescribing session. Contains line items for all order types.
| Field | Type | Required | Notes |
|---|---|---|---|
_id |
ObjectId | auto | |
encounter |
Ref → Encounter | yes | |
patient |
Ref → Patient | yes | |
category |
OrderRequestCategoryType |
no | inpatient/outpatient/forensic/community/discharge |
requester |
Ref → User | yes | Prescribing doctor |
status |
OrderRequestStatusType |
yes | See §3.1 |
code |
String | yes | Unique order identifier |
priority |
OrderRequestItemPriority |
no | See §3.7 |
isForward |
Boolean | no | IPD/OPD indicator |
isBillable |
Boolean | no | Financial tracking flag |
labReportResult[] |
Array | no | Lab results (if order type = lab) |
Schema file: packages/platform-api-schema/src/medication/orderRequest/entity/OrderRequest.ts
2.2 OrderRequestItem (MongoDB: order_request_item)
Individual line item within an OrderRequest.
| Field | Type | Required | Notes |
|---|---|---|---|
_id |
ObjectId | auto | |
orderRequest |
Ref → OrderRequest | yes | Parent |
product |
Ref → Product | yes | Medication/lab/supply |
status |
StatusOrderApprove |
no | request/accepted/reject |
quantity |
Number | no | |
dosage |
String | no | |
frequency |
String | no | |
priority |
OrderRequestItemPriority |
no | See §3.7 |
icd9Ref |
Ref → ICD9 | no | Clinical indication |
injection[] |
OrderRequestItemInjection[] |
no | IV/IM injection details |
taperRamp[] |
OrderRequestItemTaperRamp[] |
no | Taper schedule steps |
medicationRequestType |
MedicationRequestType |
no | ‘continue’ or ‘oneday’ |
Schema file: packages/platform-api-schema/src/medication/orderRequestItem/entity/OrderRequestItem.ts
2.3 MedicationRequest (MongoDB: medication_request)
IPD/OPD medication prescription. Created per pharmacy batch from an OrderRequest.
| Field | Type | Required | Notes |
|---|---|---|---|
_id |
ObjectId | auto | |
orderRequest |
Ref → OrderRequest | yes | Parent order |
items[] |
MedicationRequestItem[] |
yes | Drug line items |
status |
MedicationRequestStatus |
yes | See §3.2 |
priority |
OrderRequestItemPriority |
no | |
isIPD |
Boolean | no | Inpatient flag |
isScreened |
Boolean | no | Pharmacist reviewed |
screenedBy |
Ref → User | no | First-pass screener |
screenedAt |
Date | no | |
qcVerifiedBy |
Ref → User | no | Second-pass QC |
qcVerifiedAt |
Date | no | |
multipleScreenedUser[] |
Ref[] → User | no | Multiple screeners |
receivedBy |
Ref → User | no | Pharmacy receipt |
receivedAt |
Date | no | |
isMedicationSupply |
Boolean | no | Bulk supply flag |
isMedReconcile |
Boolean | no | Medication reconciliation |
isHomeMed |
Boolean | no | Home medication |
sortingMethod |
String | no | Ward stock sorting strategy |
Schema file: packages/platform-api-schema/src/medication/medicationRequest/entity/MedicationRequest.ts
2.4 MedicationAdministration (MongoDB: medication_administration)
MAR record — tracks actual drug administration to patient.
| Field | Type | Required | Notes |
|---|---|---|---|
_id |
ObjectId | auto | |
medicationRequestRef |
Ref → MedicationRequest | yes | Prescription |
resourceId |
Ref → MedicationRequestItem | yes | Drug line item |
status |
MedicationAdministrationStatus |
yes | See §3.4 |
eventRef |
Ref → MedicationAdministrationEvent | no | |
start, end, time |
Date | no | Administration timing |
fiveRightsCheck[] |
{ rightName, rightValue }[] |
no | 5R verification |
infusion |
{ rate, hour, bagNumber } |
no | IV infusion details |
medicationProviderRef |
Ref → User | no | Administering nurse |
medicationVerificationProviderRef |
Ref → User | no | Verifying provider |
Schema file: packages/platform-api-schema/src/medication/medicationAdministration/entity/MedicationAdministration.ts
2.5 OrderRequestProductDispense (MongoDB: order_request_product_dispense)
Tracks physical dispensing event per line item.
| Field | Type | Required | Notes |
|---|---|---|---|
_id |
ObjectId | auto | |
orderRequestItem |
Ref → OrderRequestItem | yes | |
status |
OrderRequestProductDispenseStatusType |
yes | See §3.5 |
quantity |
Number | no | |
daysSupply |
Number | no | IPD: supplies for N days |
preparedAt |
Date | no | |
handedOverAt |
Date | no | |
remark |
String | no |
Schema file: packages/platform-api-schema/src/medication/orderRequestProductDispense/entity/OrderRequestProductDispense.ts
2.6 MedicationCart (MongoDB: medication_cart)
Batches multiple medication requests for dispensing.
| Field | Type | Required | Notes |
|---|---|---|---|
_id |
ObjectId | auto | |
refMedicationRequest[] |
Ref[] → MedicationRequest | yes | Batch |
user |
Ref → User | yes | Pharmacist/Dispenser |
status |
MedicationCartStatus |
yes | pending / success |
2.7 MedicationReturnSystem (MongoDB: medication_return_system)
Drug returns and waste tracking.
| Field | Type | Required | Notes |
|---|---|---|---|
_id |
ObjectId | auto | |
medicationRequestRef |
Ref → MedicationRequest | yes | Original Rx |
quantity |
Number | yes | |
returnedQuantity |
Number | no | |
remainingQuantity |
Number | no | |
status |
MedicationReturnSystemStatus |
yes | See §3.6 |
returningStatus |
MedicationReturnAmountStatus |
no | returned/not returned/partially returned |
3. Status Enums & Progressions
3.1 OrderRequestStatusType
File: packages/platform-api-schema/src/medication/orderRequest/entity/OrderRequestStatusType.ts
enum OrderRequestStatusType {
DRAFT = 'draft',
PRE_PENDING = 'pre-pending',
PENDING = 'pending',
ACCEPTED = 'accepted',
COMPLETED = 'completed',
CANCELLED = 'cancelled',
}
Progression:
DRAFT ──▶ PRE_PENDING ──▶ PENDING ──▶ ACCEPTED ──▶ COMPLETED
│ │ │ │
└────────────┴──────────────┴────────────┴──────▶ CANCELLED
| Transition | Trigger |
|---|---|
| DRAFT → PRE_PENDING | Doctor submits order (auto on save) |
| PRE_PENDING → PENDING | Price check / stock check passed |
| PENDING → ACCEPTED | All items dispatched to departments |
| ACCEPTED → COMPLETED | All items fulfilled |
| Any → CANCELLED | Doctor cancels order |
3.1b OrderRequestStatus (HL7 Standard Codes)
File: packages/platform-api-schema/src/medication/orderRequest/entity/OrderRequestStatus.ts
Used for HL7v2/FHIR interop mapping:
| Code | Meaning |
|---|---|
A |
Some, but not all, results available |
CA |
Order was canceled |
CM |
Order is completed |
DC |
Order was discontinued |
ER |
Error, order not found |
HD |
Order is on hold |
IP |
In process, unspecified |
RP |
Order has been replaced |
SC |
In process, scheduled |
3.2 MedicationRequestStatus
File: packages/platform-api-schema/src/medication/medicationRequest/entity/MedicationRequestStatus.ts
enum MedicationRequestStatus {
CHECK_PRICE = 'check_price',
PENDING = 'pending',
DRAFT = 'draft',
ACCEPTED = 'accepted',
COMPLETED = 'completed',
CANCELLED = 'cancelled',
REVIEW = 'reviewing',
AUDIT = 'AUDIT',
EDITTED = 'editted',
// Compounding room
CHECKING_BEFORE_COMPOUNDING = 'audit_before_compounding',
COMPOUNDING = 'compounding',
CHECKING_AFTER_COMPOUNDING = 'audit_after_compounding',
// Mix drug
REVIEW_BEFORE_MIX = 'reviewBeforeMix',
MIXDRUG = 'mixdrug',
REVIEW_AFTER_MIX = 'reviewAfterMix',
}
Standard Pharmacy Progression:
CHECK_PRICE ──▶ DRAFT ──▶ PENDING ──▶ REVIEW ──▶ AUDIT ──▶ ACCEPTED ──▶ COMPLETED
│
EDITTED (loop back)
│
CANCELLED
Compounding Room Progression:
PENDING ──▶ CHECKING_BEFORE_COMPOUNDING ──▶ COMPOUNDING ──▶ CHECKING_AFTER_COMPOUNDING ──▶ ACCEPTED
Mix Drug Progression:
PENDING ──▶ REVIEW_BEFORE_MIX ──▶ MIXDRUG ──▶ REVIEW_AFTER_MIX ──▶ ACCEPTED
3.2b Pharmacy UI OrderStatus (Frontend Step Mapping)
File: web/packages/medical-kit/src/pharmacy/components/interface.ts
enum OrderStatus {
ORDERED = 'Ordered', // Step 0: New order arrived
SCREENED = 'Screened', // Step 1: Pharmacist screened
PREPARED = 'Prepared', // Step 2: Physically picked & packed
VERIFIED = 'Verified', // Step 3: Final QC check
ACCEPTED = 'Accepted', // Step 4: Ready for handoff
DONE = 'Done', // Step 5: Dispensed to patient/ward
HOLD = 'HOLD',
CANCEL = 'Cancel',
EDITTED = 'Editted',
ORDEROSRC = 'ใบสั่งยานอก', // External prescription
}
ORDERED ──▶ SCREENED ──▶ PREPARED ──▶ VERIFIED ──▶ ACCEPTED ──▶ DONE
│
HOLD / CANCEL / EDITTED
3.3 MedicationRequestItemStatus
File: packages/platform-api-schema/src/medication/medicationRequestItem/entity/MedicationRequestItemStatus.ts
enum MedicationRequestItemStatus {
PENDING = 'pending',
ACCEPTED = 'accepted',
COMPLETED = 'completed',
CANCELLED = 'cancelled',
REVIEW = 'reviewing',
AUDIT = 'AUDIT',
EDITED = 'edited',
OFF = 'off', // หยุดยา (medication stopped)
CONTINUE = 'continue', // ใช้ยาต่อ (medication continued)
REVIEW_BEFORE_MIX = 'reviewBeforeMix',
MIXDRUG = 'mixdrug',
REVIEW_AFTER_MIX = 'reviewAfterMix',
}
IPD Medication Continuation:
CONTINUE ◀──▶ OFF (toggle: nurse/doctor can pause/resume individual drug)
3.4 MedicationAdministrationStatus
File: packages/platform-api-schema/src/medication/medicationAdministration/entity/MedicationAdministrationStatus.ts
enum MedicationAdministrationStatus {
PENDING = 'pending',
IN_PROGRESS = 'in-progress',
COMPLETED = 'completed',
OFF_SCHEDULE = 'off-schedule',
OVERDUE = 'overdue',
}
Progression:
PENDING ──▶ IN_PROGRESS ──▶ COMPLETED
│ │
▼ ▼
OVERDUE OFF_SCHEDULE
(time elapsed) (schedule changed)
3.5 OrderRequestProductDispenseStatusType
File: packages/platform-api-schema/src/medication/orderRequestProductDispense/entity/OrderRequestProductDispenseStatusType.ts
enum OrderRequestProductDispenseStatusType {
PREPARATION = 'preparation',
INPROGRESS = 'in-progress',
CANCELLED = 'cancelled',
ONHOLD = 'on-hold',
COMPLETED = 'completed',
ENTERED_IN_ERROR = 'entered-in-error',
STOPPPED = 'stopped', // note: typo is canonical
DECLINED = 'declined',
UNKNOWN = 'unknown',
}
PREPARATION ──▶ INPROGRESS ──▶ COMPLETED
│ │
▼ ▼
ONHOLD STOPPPED / CANCELLED / DECLINED / ENTERED_IN_ERROR
3.6 MedicationReturnSystemStatus & MedicationReturnAmountStatus
enum MedicationReturnSystemStatus {
PENDING = 'pending',
CANCELLED = 'cancelled',
COMPLETED = 'completed',
}
enum MedicationReturnAmountStatus {
RETURNED = 'returned',
NOT_RETURNED = 'not returned',
PARTIALLY_RETURNED = 'partially returned',
}
3.7 OrderRequestItemPriority
File: packages/platform-api-schema/src/medication/orderRequestItem/entity/OrderRequestItemPriority.ts
enum OrderRequestItemPriority {
ROUTINE = 'routine',
STAT = 'stat',
URGENT = 'urgent',
ASAP = 'asap',
MEDICATIONRECONCILE = 'medicationreconcile',
HOME_MED = 'homeMed',
HALF_HOUR = '30 min',
ONE_HOUR = '60 min',
}
Priority Level Mapping (numeric sort order):
| Level | Priority | SLA |
|---|---|---|
| 1 | 30 min | Must arrive in 30 min |
| 2 | 60 min | Must arrive in 60 min |
| 3 | medicationreconcile | Reconciliation batch |
| 4 | routine | Standard processing |
| 5 | urgent | Expedited |
| 6 | stat | Immediate |
| 7 | asap | As soon as possible |
| 8 | homeMed | Home medication (discharge) |
OPD vs IPD visibility:
| Priority | OPD | IPD |
|---|---|---|
| routine | yes | yes |
| stat | yes | yes |
| urgent | no | yes |
| asap | no | yes |
| 30 min | no | yes |
| 60 min | no | yes |
3.8 OrderRequestCategoryType
enum OrderRequestCategoryType {
INPATIENT = 'inpatient',
OUTPATIENT = 'outpatient',
FORENSIC = 'forensic',
COMMUNITY = 'community',
DISCHARGE = 'discharge',
}
3.9 MedicationRequestType (Order Duration)
enum MedicationRequestType {
ONEDAY = 'oneday', // Auto-expires (one-time supply)
CONTINUE = 'continue', // Standing order (repeats per schedule)
}
Rule: STAT priority forces ONEDAY. OPD auto-hides toggle (always ONEDAY). IPD shows toggle.
3.10 StatusOrderApprove
enum StatusOrderApprove {
REQUEST = 'request',
ACCEPTED = 'accepted',
REJECT = 'reject',
}
3.11 MedicationCartStatus
enum MedicationCartStatus {
PENDING = 'pending',
SUCCES = 'success', // note: typo is canonical
}
3.12 Supabase Read-Model Statuses
These are TEXT columns (not enums) — values used by handlers and edge functions:
medication_requests.status:
pending→active→verified→in_progress→completed- Also:
discontinued,cancelled - Trigger fires on:
active,verified,in_progress(IMP only)
medication_holds.status:
active→resumed|cancelled
medication_administration_cycles.status:
pending→in_progress→completed|skipped
medication_administrations.status (permanent EMAR):
administered,held,refused,omitted,self_administered,not_given
medication_administrations.witness_status:
pending→confirmed|declined
department_queues.status:
WAITING→IN_PROGRESS→COMPLETED|CANCELLED
4. Hospital Events & Payloads
All events are INSERT-only rows in the hospital_events table. The encounter-orchestrator Deno edge function routes each event_type to the corresponding handler.
4.1 Rx Pipeline Events
RX_PRESCRIBED / rx.prescribed
Emitted by: encounterJourney.service.ts → emitRxPrescribed()
{
event_type: 'RX_PRESCRIBED', // or 'rx.prescribed' in handler routing
encounter_id: string,
patient_id: string,
target_id: string, // rx_id
payload: {
rx_id: string,
rx_item_id?: string,
encounter_id: string,
patient_id: string,
hn_snapshot?: string,
an_snapshot?: string,
patient_name_snapshot?: string,
ward_id: string,
order_type: 'continuous' | 'one_day' | 'home_medication',
is_urgent: boolean, // default: false
drug_name_th?: string,
drug_name_en: string,
is_out_of_formulary: boolean, // default: false
prescriber_doctor_id: string,
occurred_at: string, // ISO 8601
}
}
RX_VERIFIED / rx.verified
Emitted by: encounterJourney.service.ts → emitRxVerified()
{
event_type: 'RX_VERIFIED',
payload: {
rx_id: string,
rx_item_id?: string,
encounter_id: string,
patient_id: string,
hn_snapshot?: string,
ward_id: string,
pharmacist_id: string,
verified_at: string, // ISO 8601
verification_notes?: string,
occurred_at: string,
}
}
RX_DISPENSED / rx.dispensed
Emitted by: encounterJourney.service.ts → emitRxDispensed()
{
event_type: 'RX_DISPENSED',
payload: {
rx_id: string,
rx_item_id?: string,
encounter_id: string,
patient_id: string,
ward_id: string,
dispensed_qty: number, // default: 0
dispenser_pharmacist_id: string,
dispensed_at: string, // ISO 8601
occurred_at: string,
}
}
4.2 Medication Administration Events
MEDICATION_ADMINISTERED / mar.administered
Emitted by: encounterJourney.service.ts → emitMedicationAdministered()
{
event_type: 'MEDICATION_ADMINISTERED',
payload: {
// Service-layer shape (camelCase):
medicationRequestId: string,
productId: string,
productName: string,
administeredAt: string,
dose?: number,
unit?: string,
// Handler-expected shape (snake_case):
mar_event_id: string,
rx_id: string,
rx_item_id: string,
encounter_id: string,
patient_id: string,
ward_id: string,
bed_id?: string,
drug_name_th?: string,
drug_name_en: string,
dose_administered: string, // e.g., "500mg"
route?: string, // PO, IV, IM
administered_at: string,
administered_by_nurse_id: string,
scan_method: 'camera' | 'hid_keyboard' | 'manual_paste' | 'manual_override',
notes?: string,
}
}
4.3 Cycle Materialization Event
manifest.rx.cycles_requested
Emitted by: Postgres trigger trg_medication_request_materialize_cycles() on medication_requests table (IMP only, on INSERT or UPDATE OF status to active/verified/in_progress).
{
event_type: 'manifest.rx.cycles_requested',
encounter_id: string,
patient_id: string,
target_id: string, // medication_requests.id (UUID)
payload: {
rx_id: string, // UUID
encounter_id: string,
patient_id: string,
ward_id: string,
frequency?: string, // OD, BID, TID, Q8H, etc.
medication_name?: string,
medication_name_th?: string,
order_type: 'continuous' | 'one_day' | 'home_medication',
is_urgent: boolean,
triggered_by: 'medication_request_trigger' | 'orchestrator',
}
}
4.4 Medication Hold/Release Events
manifest.medication.auto_held
{
event_type: 'manifest.medication.auto_held',
payload: {
encounter_id: string,
patient_id?: string,
reason: string, // 'NPO', 'discharge_pending', 'transfer'
}
}
manifest.medication.auto_resumed
{
event_type: 'manifest.medication.auto_resumed',
payload: {
encounter_id: string,
patient_id?: string,
reason: string,
}
}
4.5 Order Lifecycle Events
| Event Type | Trigger | Payload Key Fields |
|---|---|---|
ORDER_CREATED |
Doctor submits order | orderId, orderType, items[], encounterId |
ORDERS_ACKNOWLEDGED |
Nurse batch-acks (see §4.5a) | orderIds[], acknowledgedBy, queuesPushed[] |
ORDER_DISPATCH_HELD |
Policy: nurse ack required | orderId, reason |
ORDER_DISPATCH_RELEASED |
Nurse releases held order | orderId, releasedBy |
SUPPLEMENTAL_ORDER_CREATED |
Late-added order | orderId, parentOrderId |
PHARMACY_SCREENING_COMPLETED |
Pharmacist done screening | medicationRequestIds[], screened_at |
PHARMACY_DISPENSED |
Pharmacy dispensed batch | medicationRequestIds[], dispensed_at |
4.5a Nurse Acknowledgement Flow (Step 2)
Critical step between Doctor Order and Pharmacy. After a doctor writes orders, the nurse reviews ALL order types (drugs, labs, x-rays, procedures, nutrition, notes) via a checklist dialog and acknowledges them. Only acknowledged drug orders flow to the pharmacy queue.
Workflow State: wait-acknowledgement → wait-result
UI Component: DialogNurseAcknowledge.tsx — 11-section checklist (Chief Complaint, Present Illness,
Past Medical History, Order, Pre-order Admit, Surgery/Procedure, Lab Specimen, X-ray, Medicine/EMAR,
Doctor Note, Healthcare Note, Note to Nurse)
Service: web/src/services/nurse-acknowledgement.service.ts → acknowledgeAndPushToQueues()
Acknowledge API
POST /api/v2/orderRequests/updateAcknowledge/:orderId
Body: { _id: orderRequestId, reasoncancel: '', patient: patientId }
Response: Items grouped by backend type key:
{ medicationRequest: [...], labRequest: [...], imageRequest: [...],
procedureRequest: [...], nutritionRequest: [...], pathologyRequest: [...] }
Product Type → Queue Mapping
After acknowledge, items are grouped by productType and pushed to destination queues:
| Product Type | Queue Prefix | Firestore Collection | Supabase dept_type |
|---|---|---|---|
drug |
T | queues_screening_pharmacy_{pathName} |
pharmacy_screening |
lab |
L | queues_lab_{pathName} |
LAB |
image |
X | queues_radiology_{pathName} |
RADIOLOGY |
procedure |
P | queues_procedure_{pathName} |
PROCEDURE |
nutrition |
N | queues_nutrition_{pathName} |
NUTRITION |
blood-bank |
B | queues_blood_bank_{pathName} |
BLOOD_BANK |
pathology |
PA | queues_pathology_{pathName} |
PATHOLOGY |
physical-therapy |
PT | queues_physical_therapy_{pathName} |
PHYSICAL_THERAPY |
medical-supply |
MS | queues_medical_supply_{pathName} |
MEDICAL_SUPPLY |
transport |
TR | queues_transport_{pathName} |
TRANSPORT |
API Response Key → productType mapping:
| Backend Response Key | Internal productType |
|---|---|
medicationRequest |
drug |
labRequest |
lab |
imageRequest |
image |
procedureRequest |
procedure |
nutritionRequest |
nutrition |
pathologyRequest |
pathology |
Post-Acknowledge Workflow Transition
1. POST /orderRequests/updateAcknowledge/:orderId
→ sets acknowledgeBy + acknowledgeAt on all items
→ returns items grouped by type
2. Items grouped by productType via API_RESPONSE_TYPE_MAPPING
3. Each group → push to destination Firestore queue
(drug items → queues_screening_pharmacy with queue number T1, T2, etc.)
4. POST /v2/medication/encounterJourney/queue-transition
{ encounterId, targetWorkflowState: 'wait-result', fromWorkflowState: 'wait-acknowledgement' }
5. Non-blocking audit: INSERT nurse_acknowledgement_log (Supabase)
Bulk Acknowledge Variant
POST /api/v2/orderRequests/updateAcknowledgeAllOrderRequest
Sets acknowledgeBy + acknowledgeAt on ALL items in the order.
Groups items by productType → creates SalesOrder → pushes to queues.
Order Dispatch Policy (§9.1 link)
The order_dispatch_policies table controls whether auto-dispatch is allowed:
- If
requires_nurse_ack = truefor adept_type+scope, orders are held until nurse ack - If
auto_dispatch = true, orders skip the acknowledgement step and go directly to department queues - Priority resolution: location > sub_clinic > clinic > global
4.6 Clinical Alert Event
{
event_type: 'CLINICAL_ALERT',
payload: {
tier: 0 | 1 | 2 | 3, // 0=passive, 1=info, 2=soft-stop, 3=hard-stop
alert_type: string, // drug_interaction, allergy, dosage_limit, etc.
message_en: string,
message_th?: string,
source_order_id?: string,
encounter_id: string,
}
}
5. Read-Model Tables
5.1 medication_requests (Supabase)
Migration: 20260518h_ipd_medication_orchestrator.sql
Projection table for the dispense-cycle-runner to scan. Shared OPD+IPD, filtered by encounter_class.
| Column | Type | Default | Notes |
|---|---|---|---|
id |
UUID | PK | |
encounter_id |
TEXT | NOT NULL | |
patient_id |
TEXT | NOT NULL | |
ward_id |
TEXT | ||
encounter_class |
TEXT | ‘IMP’, ‘AMB’, ‘EMER’ | |
status |
TEXT | ‘pending’ | pending/active/verified/in_progress/completed/discontinued/cancelled |
frequency |
TEXT | OD, BID, TID, Q8H, etc. | |
rrule |
TEXT | RFC 5545 RRULE string | |
start_date |
TIMESTAMPTZ | ||
end_date |
TIMESTAMPTZ | ||
medication_name |
TEXT | ||
medication_name_th |
TEXT | ||
med_category |
TEXT | drug, medical-supply, blood-product | |
amount_generate_drug |
INTEGER | ||
next_dispense_date |
DATE | ||
is_urgent |
BOOLEAN | false | |
is_out_of_formulary |
BOOLEAN | false | |
is_home_med |
BOOLEAN | false | |
order_type |
TEXT | ‘continuous’ | continuous, one_day, home_medication |
prescriber_doctor_id |
TEXT | ||
rx_mongo_id |
TEXT | Back-ref to MongoDB MedicationRequest._id | |
rx_item_mongo_id |
TEXT | Back-ref to MongoDB MedicationRequestItem._id | |
created_at |
TIMESTAMPTZ | NOW() | |
updated_at |
TIMESTAMPTZ | NOW() |
Indexes: encounter, patient, status, ward, encounter_class
RPC: upsert_medication_request() — idempotent ON CONFLICT UPDATE
5.2 medication_holds (Supabase)
Migration: 20260518h_ipd_medication_orchestrator.sql
| Column | Type | Default | Notes |
|---|---|---|---|
id |
UUID | PK | |
patient_id |
TEXT | ||
encounter_id |
TEXT | NOT NULL | |
medication_request_id |
UUID | FK → medication_requests | |
medication_name |
TEXT | ‘unknown’ | |
hold_reason |
TEXT | NOT NULL | NPO, discharge_pending, transfer |
hold_reason_detail |
TEXT | ||
hold_duration |
TEXT | ‘1_dose’, ‘1_day’, ‘until_order’ | |
status |
TEXT | ‘active’ | active, resumed, cancelled |
resumed_at |
TIMESTAMPTZ | ||
resumed_by |
UUID | ||
resume_notes |
TEXT | ||
created_by |
TEXT | sentinel UUID | |
created_at |
TIMESTAMPTZ | NOW() | |
updated_at |
TIMESTAMPTZ | NOW() |
5.3 medication_administration_cycles (Supabase)
Migration: 20260518h_ipd_medication_orchestrator.sql
Materialized e-MAR time slots from dispense-cycle-runner.
| Column | Type | Default | Notes |
|---|---|---|---|
id |
UUID | PK | |
patient_id |
TEXT | ||
encounter_id |
TEXT | NOT NULL | |
medication_request_id |
UUID | FK → medication_requests | |
medication_name |
TEXT | ‘unknown’ | |
cycle_number |
INTEGER | 1 | |
total_cycles |
INTEGER | 1 | |
scheduled_times |
TEXT[] | [‘08:00’, ‘14:00’, ‘20:00’] | |
scheduled_at |
TIMESTAMPTZ | ||
frequency |
TEXT | ||
status |
TEXT | ‘pending’ | pending, in_progress, completed, skipped |
ward_id |
TEXT | ||
config_id |
UUID | FK → dispense_cycle_configs | |
administered_at |
TIMESTAMPTZ | ||
administered_by |
UUID | ||
administered_by_name |
TEXT | ||
notes |
TEXT | ||
skip_reason |
TEXT | ||
created_at |
TIMESTAMPTZ | NOW() | |
updated_at |
TIMESTAMPTZ | NOW() |
5.4 medication_administrations (Supabase — Permanent EMAR)
Migration: web/supabase/migrations/20260507_medication_administrations.sql
| Column | Type | Notes |
|---|---|---|
id |
UUID | PK |
organization_id |
UUID | |
patient_id, encounter_id, admission_id |
UUID | |
medication_request_id, med_order_id |
UUID/VARCHAR | |
medication_name, medication_name_th, generic_name |
VARCHAR | |
dose, dose_value, dose_unit, route, frequency |
TEXT/NUMERIC | |
administered_at |
TIMESTAMPTZ | NOT NULL |
scheduled_at |
TIMESTAMPTZ | |
status |
VARCHAR(30) | administered/held/refused/omitted/self_administered/not_given |
administered_by, administered_by_name, administered_by_role, administered_by_license |
UUID/VARCHAR | |
| Witness (high-alert) | ||
witness_required |
BOOLEAN | |
witness_by, witness_by_name, witness_at |
UUID/VARCHAR/TIMESTAMPTZ | |
witness_status |
VARCHAR | pending/confirmed/declined |
| BCMA / Five Rights | ||
scanned_2id, patient_barcode_scanned, medication_barcode_scanned, five_rights_verified |
BOOLEAN | |
scan_proof |
JSONB | { method, scannedValue, expectedValue, matched, confidence } |
verification_method |
VARCHAR | qr, barcode, voice, manual |
| Infusion | ||
is_infusion |
BOOLEAN | |
pump_id, infusion_rate, rate_unit, bag_volume_ml, bag_remaining_ml |
VARCHAR/NUMERIC | |
| Notes & Overrides | ||
notes, override_reason, not_given_reason, hold_reason |
TEXT | |
is_high_alert, high_alert_category |
BOOLEAN/VARCHAR | |
device_id, ip_address |
VARCHAR |
5.5 medication_label_qrcodes (Supabase)
Migration: web/supabase/migrations/20260507_medication_administrations.sql
| Column | Type | Notes |
|---|---|---|
id |
UUID | PK |
medication_request_id |
UUID | |
medication_name, dose, route, frequency |
VARCHAR | |
patient_id, patient_hn, patient_name, encounter_id |
UUID/VARCHAR | |
qr_payload |
TEXT | JSON string encoded in QR |
barcode_value |
VARCHAR | Linear barcode |
format |
VARCHAR | qr, code128, code39 |
valid_from, valid_until |
TIMESTAMPTZ | |
is_active |
BOOLEAN | |
printed_at, printed_by, print_count |
TIMESTAMPTZ/UUID/INTEGER |
5.6 encounter_journey_cache — Medication Columns
Added by: 20260518h_ipd_medication_orchestrator.sql
| Column | Type | Default | Set by |
|---|---|---|---|
has_active_medications |
BOOLEAN | false | handleRxPrescribed |
active_rx_count |
INTEGER | 0 | handleRxPrescribed (increment) |
has_held_medications |
BOOLEAN | false | handleMedicationAutoHeld/Resumed |
hold_reason |
TEXT | null | handleMedicationAutoHeld |
pending_cycle_count |
INTEGER | 0 | handleRxCycleMaterialization |
last_cycle_materialized_at |
TIMESTAMPTZ | null | handleRxCycleMaterialization |
5.7 department_queues — IPD Pharmacy Queue Types
| dept_type | Created by | Meaning |
|---|---|---|
ipd_pharmacy_pending |
handleRxPrescribed | New Rx, awaiting pharmacist verification |
ipd_pharmacy_verified |
handleRxVerified | Verified, awaiting physical dispense |
ipd_pharmacy_dispensed |
handleRxDispensed | Audit row (status=COMPLETED) |
ipd_pharmacy_hold_alert |
handleMedicationAutoHeld | Auto-hold notification |
Priority mapping in handlers:
| Condition | Priority |
|---|---|
is_urgent = true |
URGENT |
is_out_of_formulary = true |
HIGH |
| Default | NORMAL |
| Hold alert | HIGH |
6. Orchestrator Handlers & Side Effects
All handlers live in infrastructure/medbase/functions/inpatient-handlers/ and are wired into encounter-orchestrator/index.ts.
6.1 handleRxPrescribed
Event: rx.prescribed
| Side Effect | Table | Action |
|---|---|---|
| 1 | department_queues |
INSERT dept_type='ipd_pharmacy_pending', status=WAITING |
| 2 | encounter_journey_cache |
UPDATE has_active_medications=true, increment active_rx_count |
| 3 | acknowledgement_requests |
INSERT (if is_urgent) — pharmacist must verify in 15 min, reminder every 5 min x3, escalate after 15 min |
6.2 handleRxVerified
Event: rx.verified
| Side Effect | Table | Action |
|---|---|---|
| 1 | department_queues |
UPDATE ipd_pharmacy_pending → status=COMPLETED |
| 2 | department_queues |
INSERT dept_type='ipd_pharmacy_verified', status=WAITING |
| 3 | acknowledgement_requests |
UPDATE status=‘fulfilled’ (close urgent ack) |
6.3 handleRxDispensed
Event: rx.dispensed
| Side Effect | Table | Action |
|---|---|---|
| 1 | department_queues |
UPDATE ipd_pharmacy_verified → status=COMPLETED |
| 2 | department_queues |
INSERT dept_type='ipd_pharmacy_dispensed', status=COMPLETED (audit) |
6.4 handleRxCycleMaterialization
Event: manifest.rx.cycles_requested
| Side Effect | Table/Function | Action |
|---|---|---|
| 1 | medication_requests |
RPC upsert_medication_request() — idempotent |
| 2 | Edge function | POST /functions/v1/dispense-cycle-runner (non-fatal) |
| 3 | encounter_journey_cache |
UPDATE pending_cycle_count, last_cycle_materialized_at |
6.5 handleMarAdministered
Event: mar.administered
| Side Effect | Table | Action |
|---|---|---|
| 1 | mar_audit |
INSERT (append-only, no UPDATE/DELETE allowed) |
No department_queues write — purely audit. The MongoDB MedicationAdministration entity is the canonical write (handled by NestJS medicationAdministration module).
6.6 handleMedicationAutoHeld
Event: manifest.medication.auto_held
| Side Effect | Table | Action |
|---|---|---|
| 1 | encounter_journey_cache |
UPDATE has_held_medications=true, hold_reason |
| 2 | department_queues |
INSERT dept_type='ipd_pharmacy_hold_alert', priority=HIGH |
6.7 handleMedicationAutoResumed
Event: manifest.medication.auto_resumed
| Side Effect | Table/Function | Action |
|---|---|---|
| 1 | medication_holds |
READ — count active holds for encounter |
| 2 | encounter_journey_cache |
UPDATE has_held_medications (false if no more active holds) |
| 3 | department_queues |
UPDATE ipd_pharmacy_hold_alert → status=COMPLETED |
| 4 | Edge function | POST /functions/v1/dispense-cycle-runner (re-materialize unblocked cycles) |
7. Dispense Cycle Engine
Migration: 20260514_ipd_dispense_cycles.sql
Activation: 20260518c_activate_dispense_cycle.sql
Edge Function: infrastructure/medbase/functions/dispense-cycle-runner/index.ts
7.1 dispense_cycle_configs
| Field | Type | Notes |
|---|---|---|
id |
UUID | PK |
name |
TEXT | e.g., “default-ipd-cycle” |
description |
TEXT | |
status |
TEXT | ‘draft’, ‘active’, ‘inactive’ — only ‘active’ picked up |
priority |
INTEGER | Higher wins on scope conflict |
organization_id |
UUID | Nullable (multi-tenant forward-compat) |
scope_json |
JSONB | { ward_ids[], patient_classes[], med_categories[], encounter_types[] } |
schedule_json |
JSONB | { timezone, lookahead_hours, generation_mode, rounds[], default_frequency } |
hold_json |
JSONB | { auto_hold_on_npo, auto_hold_on_discharge_pending, auto_hold_on_transfer, hold_reason_default } |
cart_json |
JSONB | { group_by, require_pharmacist_ack } |
Default seed: patient_classes=['IMP'], timezone=‘Asia/Bangkok’, lookahead_hours=24, generation_mode=‘on_demand’, rounds=[morning 06:00/noon 12:00/evening 18:00/night 22:00].
7.2 Three Invocation Paths
| Path | Trigger | triggered_by |
|---|---|---|
| On-demand | Postgres trigger on medication_requests → orchestrator → edge fn |
'trigger' |
| Scheduled | pg_cron */30 * * * * → SELECT dispense_cycle_run() |
'cron' |
| Manual | Super-admin UI buttons at /super-admin/dispense-cycles |
'manual' / 'preview' / 'reconcile' |
7.3 Three Modes
| Mode | Writes? | Purpose |
|---|---|---|
| Materialize | Yes | Expand frequencies → time slots → INSERT medication_administration_cycles |
| Preview | No | Dry-run, same logic, triggered_by='preview' |
| Reconcile | No (reads both) | Diff Supabase projection vs MongoDB canonical → report drift |
7.4 Fixed Frequency Expansion
| Frequency | Scheduled Times |
|---|---|
| OD | [‘08:00’] |
| BID | [‘08:00’, ‘20:00’] |
| TID | [‘08:00’, ‘14:00’, ‘20:00’] |
| QID | [‘06:00’, ‘12:00’, ‘18:00’, ‘22:00’] |
| Q4H | [‘02:00’, ‘06:00’, ‘10:00’, ‘14:00’, ‘18:00’, ‘22:00’] |
| Q6H | [‘06:00’, ‘12:00’, ‘18:00’, ‘00:00’] |
| Q8H | [‘06:00’, ‘14:00’, ‘22:00’] |
| Q12H | [‘08:00’, ‘20:00’] |
7.5 dispense_cycle_runs (Execution Log)
| Field | Type | Notes |
|---|---|---|
id |
UUID | PK |
config_id |
UUID | FK → dispense_cycle_configs |
triggered_by |
TEXT | cron/manual/trigger/preview/reconcile |
triggered_by_user_id |
UUID | |
started_at, completed_at |
TIMESTAMPTZ | |
status |
TEXT | running/success/partial/failed |
cycles_materialized |
INTEGER | |
cycles_skipped |
INTEGER | |
cycles_held |
INTEGER | |
orders_scanned |
INTEGER | |
summary_json |
JSONB | Per-config breakdown, per-order decisions |
error_payload |
JSONB | Error list |
8. Order Bus
Migration: 20260515h_order_bus.sql
Real-time, append-only, cross-department order status stream for all order types.
8.1 order_bus Schema
| Column | Type | Notes |
|---|---|---|
id |
UUID | PK |
created_at |
TIMESTAMPTZ | |
order_type |
TEXT | pathology/blood_bank/imaging/or_request/labour_room/admission/er_request |
order_id |
TEXT | MongoDB _id of specialized order |
order_request_id |
TEXT | Parent orderRequest _id (billing hub) |
encounter_id |
TEXT | |
patient_id |
TEXT | |
event_kind |
TEXT | created/status_changed/cancelled/completed/acknowledged/financial_updated |
status_before |
TEXT | |
status_after |
TEXT | |
dept_target |
TEXT[] | GIN indexed — e.g., {‘cashier’,‘lab’,‘pharmacy’} |
priority |
TEXT | ROUTINE/NORMAL/HIGH/URGENT |
financial |
JSONB | {salesOrderId, total, netPay, currency, paymentStatus} |
actor_user_id, actor_name |
TEXT | Who triggered the change |
source |
TEXT | backend/frontend/fhir/hl7v2 |
patient_hn, patient_name, encounter_vn, doctor_name |
TEXT | Denormalized display |
metadata |
JSONB | Order-type-specific |
8.2 Default Department Routing
| order_type | dept_targets |
|---|---|
pathology |
lab, cashier, pharmacy |
blood_bank |
blood_bank, cashier, lab |
imaging |
imaging, cashier, radiology |
or_request |
or, cashier, anesthesia, nursing |
labour_room |
labour, cashier, anesthesia, nursing |
admission |
ipd, cashier, nursing, bed_management |
er_request |
er, cashier, triage |
Realtime filter: dept_target=cs.{dept} (array contains) or order_type=eq.blood_bank.
9. Policy Gates & Safety
9.1 Order Dispatch Policies
Migration: 008_order_dispatch_policies.sql
Controls whether orders auto-dispatch or require nurse acknowledgment.
| Column | Type | Notes |
|---|---|---|
dept_type |
TEXT | medication, lab, imaging, blood, procedure |
scope_type |
TEXT | location, sub_clinic, clinic, global |
scope_id |
TEXT | NULL for global |
requires_nurse_ack |
BOOLEAN | |
auto_dispatch |
BOOLEAN | |
priority |
INTEGER | Higher = more specific (location > clinic > global) |
9.2 Medication Safety Policy Gates
Migration: 065_phase2_policy_gates_seed.sql
| Gate ID | Trigger | Action | Description |
|---|---|---|---|
| O.17 | medication.high_alert.ordered |
warn | Independent verification at order entry |
| O.19 | medication.high_alert.dispense |
warn | Second pharmacist verification at dispense |
| M.60 | medication.high_alert.admin |
warn | Second nurse verification at bedside |
| M.61 | medication.high_alert.admin + badge.count<2 |
block | Both nurse + witness must scan badges |
| M.62 | medication.double_check.completed |
warn | Record decision into MAR + audit |
| M.95 | medication.antimicrobial.ordered + duration>3d |
warn | 72h review required; auto-stop enforced |
9.3 e-MAR Safety Tables
Migration: 055_medication_administration_tracking.sql
| Table | Purpose |
|---|---|
med_admin_vital_gates |
Per-drug vital sign thresholds (e.g., hold if HR < 60) |
med_admin_observation_periods |
Post-injection observation windows |
med_admin_prn_justifications |
PRN justification + effectiveness scoring |
10. REST API Endpoints
10.1 OrderRequest
| Method | Path | Action |
|---|---|---|
| GET | /api/v2/orderRequests |
List all |
| GET | /api/v2/orderRequests/:_id |
Get one |
| GET | /api/v2/orderRequests/getLastDraftOrderByEncounterId |
Current draft |
| GET | /api/v2/orderRequests/getByPatient/:patientId |
By patient |
| GET | /api/v2/orderRequests/getByPatientAndEncounter |
By patient+encounter |
| GET | /api/v2/orderRequests/getOrderRequestByEncounter/:encounterId |
By encounter |
| GET | /api/v2/orderRequests/getMedicationItemHistoryByPatient/:patientId |
Medication history |
| GET | /api/v2/orderRequests/getServiceByEncounterId/:encounterId |
Services for encounter |
| GET | /api/v2/orderRequests/getOrderRequestTodayByPatient |
Today’s orders |
| POST | /api/v2/orderRequests |
Create |
| POST | /api/v2/orderRequests/update/:_id |
Update |
| POST | /api/v2/orderRequests/updateStatus/:_id |
Status change |
| POST | /api/v2/orderRequests/cancel/:_id |
Cancel |
| POST | /api/v2/orderRequests/updateAcknowledge/:_id |
Single acknowledge |
| POST | /api/v2/orderRequests/updateAcknowledgeAllOrderRequest |
Batch acknowledge |
| PUT | /api/v2/orderRequests/updateLabWorklistResultEntry/:_id |
Lab result entry |
10.2 MedicationRequest
| Method | Path | Action |
|---|---|---|
| GET | /api/v2/medicationRequests/:_id |
Get one |
| GET | /api/v2/medicationRequests/listIPD |
IPD-only queue |
| GET | /api/v2/medicationRequests/listByEncounter/:encounterRef |
By encounter |
| GET | /api/v2/medicationRequests/listMedicalSupplyWorklist |
Supply worklist |
| GET | /api/v2/medicationRequests/getAmountPharmacy |
Queue counts |
| GET | /api/v2/medicationRequests/getMedicationRequestHomeMedDoctor |
Home med (doctor) |
| GET | /api/v2/medicationRequests/getMedicationReconcile |
Reconciliation list |
| POST | /api/v2/medicationRequests/cancel/:_id |
Cancel single |
| POST | /api/v2/medicationRequests/cancelAll/:orderRequestId |
Cancel all in order |
| POST | /api/v2/medicationRequests/dailyGenerateMedicationRequest |
Daily batch generation |
| PUT | /api/v2/medicationRequests/:_id |
Update |
| PUT | /api/v2/medicationRequests/:_id/updateMedicationRequestStatus |
Status change |
| PUT | /api/v2/medicationRequests/updatescreened/:_id |
Mark screened |
| PUT | /api/v2/medicationRequests/qcVerification/:_id |
QC verify |
| PUT | /api/v2/medicationRequests/medicationReceivedBy/:_id |
Mark received |
| PUT | /api/v2/medicationRequests/:_id/medicationRequestItem |
Update line item |
| PUT | /api/v2/medicationRequests/updateManyMedicationRequestItem |
Batch update items |
| PUT | /api/v2/medicationRequests/medicationRequestItemOffMed/:_id |
Off medication |
10.3 MedicationAdministration
| Method | Path | Action |
|---|---|---|
| GET | /api/v2/medicationAdministrations/:_id |
Get one |
| GET | /api/v2/medicationAdministrations |
List |
| POST | /api/v2/medicationAdministrations |
Create MAR record |
| PUT | /api/v2/medicationAdministrations/:_id |
Update |
| PUT | /api/v2/medicationAdministrations/:_id/updateMedicationAdministrationStatus |
Status change |
10.4 Supporting Endpoints
| Resource | Base Path |
|---|---|
| MedicationCart | /api/v2/medicationCart |
| MedicationReturnSystem | /api/v2/medicationReturnSystem |
| OrderRequestProductDispense | /api/v2/orderRequestProductDispense |
| MedicationAdministrationEvent | /api/v2/medicationAdministrationEvent |
11. Frontend Component Map
11.0 IPD Medication Hub (Unified 5-Tab Flow)
Sandbox: http://localhost:5179/?target=IpdMedicationHub
| Tab | Thai Label | Component | Data Source |
|---|---|---|---|
| 1 | คำสั่งแพทย์ (Doctor Order) | DoctorOrderTarget |
REST: /api/v2/medicationRequests/listByEncounter |
| 2 | รับทราบคำสั่ง (Nurse Ack) | NurseAcknowledgeTarget / DialogNurseAcknowledge |
REST: /api/v2/orderRequests/updateAcknowledge |
| 3 | สถานะรับยา (Pharmacy) | MedicationReceiveStatusTarget |
REST: /api/v2/medicationRequests/listIPD |
| 4 | วางแผนรอบยา (Dispense Rounds) | OrderDetailsPageTarget |
Supabase: medication_administration_cycles |
| 5 | e-MAR (Administration) | MarSystemPageTarget |
Supabase: medication_administrations + MongoDB |
Key files:
- Hub:
web/sandbox/targets/IpdMedicationHubTarget.tsx - Registry:
web/sandbox/registry.ts→IpdMedicationHub
11.0a Nurse Acknowledgement
| Component | Path | Purpose |
|---|---|---|
| DialogNurseAcknowledge | medical-kit/src/medical-record/components/dialogs/DialogNurseAcknowledge.tsx |
11-section checklist dialog |
| NurseAcknowledgementService | src/services/nurse-acknowledgement.service.ts |
acknowledgeAndPushToQueues() — API + queue push |
| QueueGenerationRegistry | src/services/queue-generation-registry.ts |
productType → queue config mapping |
| WorkflowActionHandler | src/setup/dynamic/central-table/handlers/workflowActionHandler.ts |
ack-order action routing (3 paths: modal / explicit / legacy) |
| workflowState.ts | medical-kit/src/medical-worklist/workflowState.ts |
wait-acknowledgement node definition |
11.1 Order Dialog
| Component | Path | Purpose |
|---|---|---|
| DialogNewEditOrder2026 | medical-kit/src/order/components/dialogs/dialog-edit-order/DialogNewEditOrder2026.tsx |
Primary drug order dialog (Formik) |
| DynamicScheduler | ui-kit/src/components/dynamic-scheduler-2/DynamicScheduler.tsx |
Dose pattern builder |
| SummaryCard | ui-kit/src/components/dynamic-scheduler-2/summary-container/SummaryCard.tsx |
Formatted medication summary |
| TimePerDay | ui-kit/src/components/dynamic-scheduler-2/summary-container/TimePerDay.tsx |
12-pattern frequency visualization |
| Priority | ui-kit/src/components/dynamic-scheduler-2/components/Priority.tsx |
Priority selector (2-button OPD / 6-button IPD) |
| useOrderManagement | medical-kit/src/order/components/hooks/useOrderManagement.ts |
Order CRUD + allergy/interaction checks |
11.2 Pharmacy Worklist (IPD Pharmacy — Screen/Prepare/Verify/Dispense)
This is the pharmacist’s own workspace — not a nurse view. Pharmacy receives acknowledged drug orders (after nurse ack) and processes them through their internal pipeline: screening → preparation → QC verification → acceptance → dispensing. The nurse’s “สถานะรับยา” view (Hub tab 3) shows the pharmacy status from the ward side.
Pharmacy internal pipeline (§3.2b):
ORDERED ──▶ SCREENED ──▶ PREPARED ──▶ VERIFIED ──▶ ACCEPTED ──▶ DONE
(รอคัดกรอง) (คัดกรองแล้ว) (จัดยาแล้ว) (ตรวจสอบแล้ว) (พร้อมจ่าย) (จ่ายยาแล้ว)
| Component | Path | Purpose |
|---|---|---|
| TableRowPharmacy | medical-kit/src/pharmacy/components/pharmacy-worklist/TableRowPharmacy.tsx |
Pharmacy worklist row |
| DialogPharmacyOrder | medical-kit/src/pharmacy/components/pharmacy-worklist/dialog-order-drug/DialogPharmacyOrder.tsx |
Drug order details |
| DialogScreeningDrugLeaves | medical-kit/src/pharmacy/components/pharmacy-worklist/dialog-screening-drug-leaves/ |
Screening + dispense workflow |
| RowTabbleDispense | ...dialog-screening-drug-leaves/RowTabbleDispense.tsx |
Dispense confirmation per drug |
| DialogPrintDrug | medical-kit/src/pharmacy/components/pharmacy-worklist/dialog-print-drug/DialogPrintDrug.tsx |
Medication label printing |
11.3 IPD Command Center
| Component | Path | Step |
|---|---|---|
| Step1AdmitPanel | medical-kit/src/ipd-system/command-center/Step1AdmitPanel.tsx |
Admission |
| Step2AssessmentPanel | ...Step2AssessmentPanel.tsx |
Nursing assessment |
| Step3CarePanel | ...Step3CarePanel.tsx |
Care + medication orders |
| Step4ProcedurePanel | ...Step4ProcedurePanel.tsx |
OR procedures |
| Step5DischargePanel | ...Step5DischargePanel.tsx |
Discharge prep |
| Step6CloseoutPanel | ...Step6CloseoutPanel.tsx |
Financial settlement |
11.4 e-MAR
| Component | Path | Purpose |
|---|---|---|
| EmarDialog | medical-kit/src/ipd-system/command-center/dialogs/EmarDialog.tsx |
3-step MAR (verify → select → outcome) |
| MarSystemEnhanced | miniapps/e-mar/MarSystemEnhanced.tsx |
Production e-MAR system |
| MarSystemProvider | miniapps/e-mar/MarSystemProvider.tsx |
REST backbone for e-MAR |
| useSupabaseEmarSync | miniapps/e-mar/useSupabaseEmarSync.ts |
Supabase realtime sync |
11.5 IPD Nurse Worklist
| Component | Path | Purpose |
|---|---|---|
| usePatientOrderStatus | medical-kit/src/ipd-system/hooks/usePatientOrderStatus.ts |
Per-patient order status badges |
| MainTab | medical-kit/src/ipd-system/nurse-ipd-work-list/components/MainTab.tsx |
Nurse worklist tabs |
| WardViewPanel | medical-kit/src/ipd-system/ipd-management/components/dashboard/ |
Ward floor plan |
11.6 IPD Encounter Step Status
File: medical-kit/src/ipd-system/command-center/stepStatus.ts
pending → admit-requested → ipd-admitted → ipd-nursing-assessment
→ ipd-nurse-rounds → ipd-medication-administration
→ ipd-nurse-handover → ipd-nurse-discharge-prep → finished
Step status per step: 'todo' | 'active' | 'done' | 'skipped' | 'blocked'
12. Dose Pattern System
File: ui-kit/src/components/dynamic-scheduler-2/summary-container/dosePatternKind.ts
12.1 DosePatternKind (14 types)
| Kind | TH Name | Detection Signal |
|---|---|---|
fixed |
ตารางคงที่ | Standard frequency (OD/BID/TID/etc.) |
prn |
เมื่อจำเป็น | priority = ‘prn’ or frequency contains ‘prn’ |
one_time |
ครั้งเดียว | priority = STAT or frequency = ONCE |
continuous |
หยดต่อเนื่อง | infusionRate present + route = IV |
taper |
ลดขนาดยา | taperSteps[] present |
titration |
ไตเตรท | method = ‘titration’ |
sliding_scale |
Sliding Scale | slidingScaleTable[] present |
cyclic |
รอบ/พัลส์ | cyclic on/off days pattern |
loading_maintenance |
โหลด+บำรุงรักษา | loadingDose present |
bolus_infusion |
โบลัส+หยด | bolus + continuous infusion |
pca |
PCA | pcaConfig present |
range |
ช่วงขนาดยา | min/max dose range |
protocol_lookup |
Protocol | Lookup-based |
bsa_based / weight_based / renal_adjusted / age_based |
Derivation | Calculation methods |
12.2 Detection Priority
detectDosePattern(order) runs a 19-step priority chain (first match wins):
- pcaConfig →
pca - slidingScaleTable →
sliding_scale - taperSteps →
taper - loadingDose + maintenanceDose →
loading_maintenance - bolus + infusion →
bolus_infusion - cyclic pattern →
cyclic - titration method →
titration - infusionRate + IV route →
continuous - dose range →
range - PRN flag →
prn - STAT/ONCE →
one_time - Standard frequency →
fixed(default)
12.3 Specialized Protocol Row Types
| Type | Fields |
|---|---|
SlidingScaleRow |
glucose range → dose lookup |
TaperStepRow |
day range → dose step-down |
PcaProtocolRow |
basal rate, demand dose, lockout minutes |
CyclicProtocolRow |
days on/off, cycle count, rest days |
LoadingMaintenanceRow |
loading dose → maintenance dose |
ContinuousInfusionRow |
rate range, duration, titration |
RangeOrderRow |
min-max dose, nurse instructions |
FrequencyPolicyRow |
per-drug/category frequency restrictions |
13. Related Docs
| Doc | Path | Covers |
|---|---|---|
| Central Order System | docs/architecture/central-order-system.md |
Hub-and-spoke order model, order bus, verification flows |
| IPD Dispense Cycles | docs/architecture/ipd-dispense-cycles.md |
Dispense cycle engine design, two-layer model |
| Order Dialog Drug Subtypes | docs/architecture/order-dialog-2026-drug-subtypes.md |
Injection/taper panels, duration toggle, IPD priority |
| Dose Pattern Visualization | docs/architecture/dose-pattern-visualization-system.md |
12-pattern rendering engine |
| Frequency RRule Overhaul | docs/architecture/frequency-rrule-overhaul-plan.md |
5-phase frequency + RRule cleanup |
| Standing Orders RRule | docs/architecture/standing-orders-rrule-system.md |
Continue vs oneday semantics |
| Medication Per-Drug Defaults | docs/architecture/medication-per-drug-default-usage.md |
Per-drug frequency locking |
| Injection Timeline MAR | docs/architecture/injection-timeline-mar-integration.md |
Injection/infusion → MAR integration |
| CDS Vital Signs Rules | docs/architecture/cds-vital-signs-rules.md |
CDS triggers on observations from orders |
| Pharmacy Gap Analysis | docs/architecture/pharmacy-module-gap-analysis.md |
Gap analysis for pharmacy module |
| Encounter Orchestrator Triggers | docs/architecture/encounter-orchestrator-triggers.md |
Master trigger inventory |
| Dose Pattern Master Setup | docs/architecture/dose-pattern-master-setup-checklist.md |
Setup checklist |
| Department Command Center | docs/architecture/department-command-center-tabs.md |
Timeline + Main + Order System tabs |