Token
A Token is a numbered ticket a patient holds in a walk-in queue — use it when scheduling runs on first-come flow rather than fixed appointment slots. The patient gets a number against a schedulable resource (practitioner, location, or healthcare service), optionally routed to a sub-queue or room.
The Django models store the data; the rules that make a token correct — status enums, the resource binding, queue progression — live in the Pydantic resource specs and the API viewsets. Both layers are covered here.
Source:
- Model:
care/emr/models/scheduling/token.py - Specs:
token/spec.py·token_category/spec.py·token_queue/spec.py·token_sub_queue/spec.py - Viewsets:
api/viewsets/scheduling/token.py·token_queue.py·token_sub_queue.py·token_category.py
Models
Four models make up the subsystem.
| Model | Purpose |
|---|---|
TokenQueue | A queue of tokens for one schedulable resource on one date |
TokenSubQueue | A sub-queue splitting a resource's tokens (e.g. multiple rooms) |
TokenCategory | Reusable token categories per facility / resource type |
Token | A numbered token issued to a patient within a queue |
All four extend EMRBaseModel, the shared Care EMR base that supplies external_id, audit fields created_by/updated_by/created_date/modified_date, and soft-delete via deleted.
TokenQueue and TokenSubQueue both carry a resource FK to emr.SchedulableResource, a polymorphic binding to a practitioner (user), location, or healthcare_service, distinguished by resource_type. See Schedule.
Token fields
| Field | Type | Required | Notes |
|---|---|---|---|
facility | FK → facility.Facility (CASCADE) | yes (server) | Copied from the queue's facility on create |
patient | FK → emr.Patient (CASCADE) | optional | Nullable. The patient the token belongs to. See Patient |
queue | FK → TokenQueue (CASCADE) | yes (server) | Taken from the URL queue, not the body |
category | FK → TokenCategory (CASCADE) | yes | Resolved from the category UUID on create. Must be in the same facility as the queue |
sub_queue | FK → TokenSubQueue (CASCADE) | optional | Nullable. Must match the queue's facility and resource |
number | IntegerField | yes (server) | Auto-assigned: count(tokens in queue with same category) + 1 |
status | CharField(255) | yes (server) | TokenStatusOptions enum (below). Starts at CREATED |
is_next | BooleanField (default False) | server | Queue-progression flag; clients never write it |
note | TextField | optional | Nullable free-text note |
booking | FK → emr.TokenBooking (CASCADE) | optional | Nullable. Links the token to a booking. related_name="booking_token" |
number is unique per category within a queue, not across the whole queue — so one patient can hold several tokens in the same queue, and two categories can both have a token 1.
TokenStatusOptions values
Defined in token/spec.py; bound on Token.status through the read and update specs.
| Value | Meaning |
|---|---|
UNFULFILLED | Token issued but not served |
CREATED | Default state on creation |
IN_PROGRESS | Currently being served (set when made the sub-queue's current token) |
FULFILLED | Service completed |
CANCELLED | Cancelled |
ENTERED_IN_ERROR | Set automatically on delete (soft-delete) |
Related models
TokenQueue
One resource, one date, one queue of tokens.
facility → FK Facility (CASCADE)
resource → FK SchedulableResource (CASCADE)
name → CharField(255)
is_primary → BooleanField (default True)
date → DateField
system_generated → BooleanField (default False)
is_primaryis resolved server-side on create: the first queue for a(resource, date)pair becomes primary, later ones do not. Theset_primaryaction re-points it.generate_tokencreatessystem_generatedqueues implicitly (name"System Generated") when no primary queue exists for the date.
TokenSubQueue
A sub-queue routes one queue's tokens to several physical points — multiple vaccination rooms, say, each pulling from its own sub-queue.
facility → FK Facility (CASCADE)
resource → FK SchedulableResource (CASCADE)
name → CharField(255)
status → CharField(255) # TokenSubQueueStatusOptions
current_token → FK Token (CASCADE, nullable)
The spec binds status to TokenSubQueueStatusOptions:
| Value |
|---|
active |
inactive |
current_token is whichever token is being served at that point. The progression actions (set_next, set_next_token_to_subqueue) keep it current server-side.
TokenCategory
A reusable category scoped to a facility and resource type — "General", "Priority", and so on.
facility → FK Facility (CASCADE)
resource_type → CharField(255) # SchedulableResourceTypeOptions
name → CharField(255)
shorthand → CharField(255) # spec limits to max_length 5
metadata → JSONField (default dict)
default → BooleanField (default False)
- The spec binds
resource_typetoSchedulableResourceTypeOptions(practitioner,location,healthcare_service). shorthandis capped atmax_length=5by the spec even though the column allows 255.metadatais an open JSON bag for deployment-specific category config, exposed asdict | None.defaultis writable only through theset_defaultaction, which clearsdefaulton every other category sharing the facility and resource type. It isn't a field on the create spec.
SchedulableResourceTypeOptions values
Defined in scheduling/schedule/spec.py; used by TokenCategory.resource_type and every *WithQueue / create spec that binds to a resource.
| Value |
|---|
practitioner |
location |
healthcare_service |
Resource specs (API schema)
Every spec extends EMRResource (care/emr/resources/base.py): serialize builds the read payload from the model (plus perform_extra_serialization), and de_serialize writes a model from the payload (plus perform_extra_deserialization). id is always the model's external_id (UUID).
Token
| Spec | Role | Exposed fields |
|---|---|---|
TokenBaseSpec | shared | id |
TokenGenerateSpec | write · create (nested under a queue) | patient? (UUID), category (UUID, required), note?, sub_queue? (UUID) |
TokenGenerateWithQueueSpec | write · create (queue resolved/created) | adds resource_type (SchedulableResourceTypeOptions), resource_id (UUID), date |
TokenUpdateSpec | write · update | status? (TokenStatusOptions), note?, sub_queue (UUID, nullable) |
TokenMinimalSpec | read · embedded | note, number, status, category (serialized via TokenCategoryReadSpec) |
TokenReadSpec | read · list | category, sub_queue, note, patient, number, status, queue (TokenQueueReadSpec) |
TokenRetrieveSpec | read · detail | extends TokenReadSpec + created_by/updated_by (UserSpec), booking, resource_type, resource, encounter? |
What happens on write, from perform_extra_deserialization and the viewset:
TokenGenerateSpec.perform_extra_deserializationresolves thepatient,category, andsub_queueUUIDs to model instances, returning 404 if any is missing.TokenUpdateSpec.perform_extra_deserializationresolves thesub_queueUUID, or setssub_queue = Nonewhen it's omitted — so an update can clear it.- On create (
perform_create),queueandfacilitycome from the URL queue, category-vs-queue and sub-queue-vs-queue facility/resource matching is enforced,numberis computed under a per-queue lock, andstatusis forced toCREATED. - On update, changing the sub-queue clears the old sub-queue's
current_tokenif it pointed at this token. A sub-queue that already has a current token can't be reassigned. - On delete (
perform_destroy), the token is soft-deleted:status = ENTERED_IN_ERROR,deleted = True. TokenRetrieveSpecembeds the booking (viaTokenBookingMinimumReadSpec), the booking'sassociated_encounter(Encounter) when present, and the resolvedresource(practitionerUserSpec/HealthcareServiceReadSpec/FacilityLocationListSpec) keyed byresource_type.
TokenQueue
| Spec | Role | Exposed fields |
|---|---|---|
TokenQueueBaseSpec | shared | id, name |
TokenQueueCreateSpec | write · create | adds resource_type (SchedulableResourceTypeOptions), resource_id (UUID), date |
TokenQueueUpdateSpec | write · update | id, name only |
TokenQueueReadSpec | read · list | adds date, is_primary, system_generated |
TokenQueueRetrieveSpec | read · detail | adds created_by, updated_by |
TokenQueueCreateSpec.perform_extra_deserialization stashes resource_type and resource_id onto the instance. The viewset's perform_create then resolves or creates the SchedulableResource, sets facility, and computes is_primary.
TokenSubQueue
| Spec | Role | Exposed fields |
|---|---|---|
TokenSubQueueBaseSpec | shared / write · update | id, name, status (TokenSubQueueStatusOptions) |
TokenSubQueueCreateSpec | write · create | adds resource_type (SchedulableResourceTypeOptions), resource_id (UUID) |
TokenSubQueueReadSpec | read | adds current_token (TokenMinimalSpec, nullable) |
TokenSubQueueCreateSpec.perform_extra_deserialization stashes resource_type and resource_id. On create the viewset resolves or creates the resource and validates it. current_token is serialized only when set.
TokenCategory
| Spec | Role | Exposed fields |
|---|---|---|
TokenCategoryBaseSpec | shared | id, name, resource_type (SchedulableResourceTypeOptions), shorthand (max 5), metadata? (dict) |
TokenCategoryCreateSpec | write · create | same as base |
TokenCategoryReadSpec | read · list | adds default |
TokenCategoryRetrieveSpec | read · detail | adds created_by, updated_by |
facility is set from the URL on create. default is read-only here and toggled through the set_default action.
Methods & save behaviour
- Number assignment —
Token.numberiscount(tokens with same queue + category) + 1, computed inside aLock("booking:token:{queue.id}")and an atomic transaction so two tokens can't claim the same number. - Status lifecycle — create forces
CREATED;set_nextandset_next_token_to_subqueuesetIN_PROGRESS; delete setsENTERED_IN_ERROR. Status is a single field, with no separate history table. - Queue progression — custom actions maintain
current_token(onTokenSubQueue) andis_next; clients never write them directly:Tokenactionset_next(POST .../{id}/set_next, body{ sub_queue }) points the sub-queue'scurrent_tokenat this token and marks itIN_PROGRESS.TokenQueueactionset_next_token_to_subqueue(POST .../{queue}/set_next_token_to_subqueue, body{ sub_queue, category? }) picks the oldestCREATEDtoken (optionally filtered by category), assigns it to the sub-queue ascurrent_token, and setsIN_PROGRESS.TokenQueueactionset_primarymakes one queue primary for its(resource, date), clearing the flag on siblings.TokenCategoryactionset_defaultmakes one category default per(facility, resource_type).
- Token generation shortcut —
TokenQueueactiongenerate_token(POST .../generate_token, body =TokenGenerateWithQueueSpec) finds or creates the primary (system_generated) queue for(resource, date)and issues a token in one call. - Queue summary —
TokenQueueactionsummaryreturns per-category, per-status token counts for a queue.
API integration notes
- Create tokens under a queue with
TokenGenerateSpec—queue,facility,number, andstatusare all server-set — or call the queue'sgenerate_tokenaction withTokenGenerateWithQueueSpec, which also resolves or creates the queue. - Send
category,patient, andsub_queueas UUIDs (external_id); the server resolves them to FKs. Cross-facility and cross-resource mismatches return400. - The update spec accepts only
TokenStatusOptionsvalues forstatus, andTokenSubQueue.statusonlyTokenSubQueueStatusOptions. Both columns are plainCharFields, so the spec layer enforces this, not the database. - Never set
number,is_next,current_token, oris_primarydirectly — they're queue-progression state owned by the viewset actions above. TokenCategory.metadatais an open JSON bag for deployment-specific config;shorthandis capped at 5 characters by the spec.bookinglinks a token back to aTokenBooking; on retrieve it expands to the booking and its associated encounter.
Related
- Reference: Schedule
- Reference: Booking
- Reference: Base model
- Concept: Patient
- Reference: Encounter
- Source: token.py on GitHub
- Specs: token · token_category · token_queue · token_sub_queue