Skip to main content

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

TablePurpose
queueA queue; relational status (OPEN/PAUSED/CLOSED), code unique per tenant/facility scope.
queue_itemAn entry; relational status (WAITING/IN_SERVICE/COMPLETED/DEFERRED/REMOVED), relational priority/sort; logical subject_type/subject_id.
queue_assignmentWho is serving an item; relational active.
waiting_boardA board over a queue; code unique per scope.
queue_status_historyAppend-only history of every item status change.

Key rules & invariants

  • Items may be added only to OPEN queues and enter WAITING; transfers are restricted to the same tenant/facility scope; terminal items reject changes; removal soft-deletes and releases active assignments.
  • queue_status_history is 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 PagedModel DTO.

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).

  • Workflow (operational pairing), Facility (scope). Domain plug-ins call via exchange.client.queue.

Milestone 28 — the queue follows the journey (2026-08-12)

  • queue_item.workflow_instance_id/workflow_stage_id link bridge-created items; entering a queue-bound stage enqueues, leaving ends the item, an override moves both (M28-003).
  • Status changes publish QueueItemStatusChangedEvent for every item (M28-006 widened it from bridge items): the workflow engine evaluates TRIGGER_ON_EVENT rules (skipping items with no instance), and QueuePatientStatusNotifier texts the patient their position on arrival and the summons on CALLED — through the preference-enforcing enqueue, so a muted channel yields a recorded SUPPRESSED event. Best-effort by design: the board is the truth, the alert a courtesy. Both listeners run AFTER_COMMIT in their own transaction (REQUIRES_NEW): until !297 they 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 showPatientNames is false (M28-004) — never the hospital number, which would link a person across days.
  • addWorkflowItem (bridge) and addRoutedItem (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 reasonQueueServiceImpl. 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.