Queue & Waiting Board — How it works
Overview
The queue engine provides operational queues and the waiting boards that surface them: create a queue, add items (a logical subject — patient, referral — referenced by type+id), move/prioritise them, change their status, assign/release them, and expose a paginated, filterable board feed. All statuses are relational (never JSONB-only), and every status change is recorded append-only.
Data model & ownership
| Table | Purpose |
|---|---|
queue | A queue; relational status (OPEN/PAUSED/CLOSED), code unique per tenant/facility scope. |
queue_item | An entry; relational status (WAITING/IN_SERVICE/COMPLETED/DEFERRED/REMOVED), relational priority/sort; logical subject_type/subject_id. |
queue_assignment | Who is serving an item; relational active. |
waiting_board | A board over a queue; code unique per scope. |
queue_status_history | Append-only history of every item status change. |
Key rules & invariants
- Items may be added only to
OPENqueues and enterWAITING; transfers are restricted to the same tenant/facility scope; terminal items reject changes; removal soft-deletes and releases active assignments. queue_status_historyis append-only — written on entry (null → WAITING) and every subsequent transition/removal, preserved across item removal.- The board feed is status-filterable and paginated via a stable
PagedModelDTO.
API
See the API Reference. Endpoint groups under /api/v1/queue: queues
(create/get/search/status), queue items (/{id} move/reorder, status change, remove, assign/release,
history), and waiting boards (create/get/search + /feed).
Configuration & feature flags
Domain-module Feign URL queue.service.url (QUEUE_SERVICE_URL).
Related features
Milestone 28 — the queue follows the journey (2026-08-12)
queue_item.workflow_instance_id/workflow_stage_idlink bridge-created items; entering a queue-bound stage enqueues, leaving ends the item, an override moves both (M28-003).- Status changes publish
QueueItemStatusChangedEventfor every item (M28-006 widened it from bridge items): the workflow engine evaluatesTRIGGER_ON_EVENTrules (skipping items with no instance), andQueuePatientStatusNotifiertexts the patient their position on arrival and the summons onCALLED— through the preference-enforcing enqueue, so a muted channel yields a recordedSUPPRESSEDevent. Best-effort by design: the board is the truth, the alert a courtesy. Both listeners runAFTER_COMMITin their own transaction (REQUIRES_NEW): until!297they did not, so the triggered advance and the alert row were written into the already-committed transaction and silently dropped — both features logged success and changed nothing. See the architecture rules. - Boards show the visit token for journey rows when
showPatientNamesis false (M28-004) — never the hospital number, which would link a person across days. addWorkflowItem(bridge) andaddRoutedItem(M28-006 checkout routing) enqueue with explicit scope for no-session paths; both share one internal write.
Why it is this way
A queue entry is derived from a workflow stage, not maintained by hand. Boards reflect where patients are in their journey, so binding stages to queues is what puts anyone on a board at all. A journey with unbound stages checks patients in successfully and shows them nowhere.
Boards are scoped to a facility and a unit. A waiting board is a physical thing in a physical place; a tenant-wide board would show patients who are not in the building.
Traps
⚠ equals, not == — WaitingBoardServiceImpl. This comparison was == while subject type was
an enum, where it was correct. When M28-013 turned subject type into data, == silently became a
reference comparison that is almost always false — the kind of change that compiles, passes review,
and quietly empties a board.
⚠ The explicit-tenant overload exists for a reason — QueueServiceImpl. It takes the tenant
rather than reading it from the session, because it is called from paths that have no session.
Calling the session-scoped version from there fails only at runtime, and only under load.
⚠ A checked-in patient on no board is usually unbound stages, not a queue fault. Look at the journey definition before the board.