Skip to main content
Version: 3.1

Booking

A booking puts a patient into a concrete time slot generated from a Schedule. TokenSlot is a bookable unit of availability; TokenBooking is a patient's appointment against that slot, carrying its status through the lifecycle. You touch both whenever an integration creates, cancels, reschedules, or reads an appointment.

Source:

These models live in two layers. The Django model is storage: status is an unconstrained CharField and several relations are opaque at the DB level. The Pydantic resource specs (care/emr/resources/scheduling/slot/) are the API layer — they pin status to a fixed enum, validate writes, and define the read schemas that embed slot, resource, facility, token, charge item, encounter, and patient sub-objects.

Models

ModelPurpose
TokenSlotA concrete, bookable time window generated from a schedulable resource's availability
TokenBookingA patient's appointment against a TokenSlot, with status, notes, and optional links to an encounter, token, and charge item

Both extend EMRBaseModel, the shared Care EMR base providing external_id, created_date/modified_date, soft-delete via deleted, created_by/updated_by, and history/meta JSON.

TokenBooking fields

FieldTypeNotes
token_slotFK → TokenSlot (PROTECT)The slot this appointment occupies; required. PROTECT prevents deleting a slot that has bookings
patientFK → emr.Patient (CASCADE)The booked patient; required
booked_onDateTimeFieldauto_now_add — stamped once at creation, never accepted from clients
booked_byFK → User (CASCADE)User who created the booking; nullable for self-service or system-created bookings
statusCharField (storage) → BookingStatusChoices (API)Appointment lifecycle status. Unbounded on the model; the write spec restricts it to the BookingStatusChoices enum
noteTextFieldFree-text note; nullable in storage but required (str) on write
tagsArrayField[int]Tag IDs, default empty list. Managed via SingleFacilityTagManager; reads return rendered tag objects, not raw IDs
associated_encounterFK → emr.Encounter (PROTECT)Encounter linked to the appointment; nullable, defaults to None. Surfaced only by TokenBookingRetrieveSpec
tokenFK → emr.Token (PROTECT)Queue token issued for this booking; nullable, defaults to None, related_name="token_booking". Created by the generate_token action
charge_itemFK → emr.ChargeItem (CASCADE)Billing charge item for the appointment; nullable. Auto-created on booking when the schedule has a charge_item_definition

BookingStatusChoices values

BookingStatusChoices (str, Enum) is the full set of status values the write spec accepts:

ValueNotes
proposed
pending
bookedDefault applied server-side when a booking is created via lock_create_appointment
arrived
fulfilledTerminal; in COMPLETED_STATUS_CHOICES
cancelledSet only via the cancel endpoint; in CANCELLED_STATUS_CHOICES + COMPLETED_STATUS_CHOICES
noshowTerminal; in COMPLETED_STATUS_CHOICES
entered_in_errorSet only via the cancel endpoint; in CANCELLED_STATUS_CHOICES + COMPLETED_STATUS_CHOICES
checked_in
waitlist
in_consultationCannot be cancelled — the cancel handler rejects it
rescheduledSet only via the reschedule endpoint; in CANCELLED_STATUS_CHOICES + COMPLETED_STATUS_CHOICES

Two derived sets (from slot/spec.py) drive the lifecycle rules:

SetValues
CANCELLED_STATUS_CHOICESentered_in_error, cancelled, rescheduled
COMPLETED_STATUS_CHOICESfulfilled, noshow, entered_in_error, cancelled, rescheduled

TokenBookingWriteSpec.perform_extra_deserialization rejects any write whose status is in CANCELLED_STATUS_CHOICES with "Cannot cancel a booking. Use the cancel endpoint". Cancellation and rescheduling go through dedicated endpoints (see below), never a plain update.

TokenSlot

A bookable window derived from a resource's availability. Bookings reference it via PROTECT, so a slot with active bookings cannot be deleted.

FieldTypeNotes
resourceFK → SchedulableResource (CASCADE)The schedulable resource — practitioner, location, or healthcare service — the slot belongs to; required
availabilityFK → Availability (CASCADE)The availability rule that generated this slot; nullable
start_datetimeDateTimeFieldSlot start; required, tz-aware
end_datetimeDateTimeFieldSlot end; required, tz-aware
allocatedIntegerFieldCount of bookings currently allocated to the slot, used to enforce capacity. Defaults to 0. Platform-maintained — never written by clients

SchedulableResource and Availability live in the Schedule module. SchedulableResource.resource_type is one of SchedulableResourceTypeOptions: practitioner, location, healthcare_service.

Resource specs (API schema)

Every spec extends EMRResource (base.py): serialize (DB → pydantic, read) and de_serialize (pydantic → DB, write), with perform_extra_serialization / perform_extra_deserialization hooks.

Spec classRoleExposes / behaviour
TokenBookingBaseSpecshared__model__ = TokenBooking, __exclude__ = ["token_slot", "patient"]
TokenBookingWriteSpecwrite · create + updateFields: status: BookingStatusChoices, note: str. Rejects status in CANCELLED_STATUS_CHOICES (use the cancel/reschedule endpoints). Serves as both pydantic_model and pydantic_update_model
TokenBookingMinimumReadSpecread · embedLightweight read for when a booking is embedded in another resource, such as a token. Fields: id, token_slot (embedded TokenSlotBaseSpec), booked_on, status: str, note, created_date, modified_date
TokenBookingBaseReadSpecread · sharedAdds booked_by: UserSpec, resource_type, resource (serialized resource), facility, created_by/updated_by, token: TokenReadSpec, tags: list[dict] (rendered), charge_item: dict
TokenBookingReadSpecread · list/defaultTokenBookingBaseReadSpec + patient: PatientRetrieveSpec. The viewset's pydantic_read_model for list responses
TokenBookingOTPReadSpecread · OTP flowTokenBookingBaseReadSpec + patient: PatientOTPReadSpec for the public/OTP booking flow
TokenBookingRetrieveSpecread · detailTokenBookingReadSpec + associated_encounter: dict (serialized EncounterListSpec). The viewset's pydantic_retrieve_model
TokenSlotBaseSpecread · embed__model__ = TokenSlot, __exclude__ = ["resource", "availability"]. Fields: id, availability (embedded as {name, tokens_per_slot, id, schedule:{name,id}}), start_datetime, end_datetime, allocated

Read serialization details (perform_extra_serialization)

  • id always comes from obj.external_id (UUID), not the integer PK.
  • token_slot is embedded via TokenSlotBaseSpec.serialize(...).
  • resource_type comes from token_slot.resource.resource_type; resource is built by serialize_resource() — a UserSpec for practitioner, HealthcareServiceReadSpec for healthcare_service, FacilityLocationListSpec for location.
  • facility is a cached FacilityBareMinimumSpec; booked_by / created_by / updated_by are cached UserSpecs.
  • tags are rendered through SingleFacilityTagManager().render_tags(obj) as objects, not raw IDs.
  • token (TokenReadSpec), charge_item (ChargeItemReadSpec), and associated_encounter (EncounterListSpec) appear only when present.

Methods & save behaviour

status has no model-level choices; the lifecycle lives entirely in the spec/viewset layer (TokenBookingViewSet). The server-maintained behaviour:

  • Creation (lock_create_appointment) — runs under a per-resource lock and a transaction. Rejects past slots (end_datetime < now) and full slots (allocated >= availability.tokens_per_slot), rejects a duplicate active booking for the same patient/slot (any status not in COMPLETED_STATUS_CHOICES), then increments token_slot.allocated, creates the booking with status="booked", and — if the schedule has a charge_item_definition — auto-creates the linked charge_item.
  • Cancel (cancel action) — accepts CancelBookingSpec { reason: cancelled | entered_in_error | rescheduled, note?: str }. Rejects cancelling an in_consultation booking. Decrements token_slot.allocated (unless already cancelled), sets status = reason, optionally overwrites note, sets updated_by, and aborts any linked charge_item (handle_charge_item_cancel + status aborted).
  • Reschedule (reschedule action) — accepts RescheduleBookingSpec { new_slot: UUID, new_booking_note: str, previous_booking_note?: str, tags: list[UUID] } and requires can_reschedule_booking. Cancels the existing booking with reason rescheduled, then runs lock_create_appointment to create a fresh booking on new_slot for the same patient. Rejects rescheduling to the same slot.
  • Generate token (generate_token action) — accepts TokenGenerationSpec { category: UUID, note?: str, queue?: UUID }. Rejects if a token already exists. Resolves or creates a TokenQueue for the slot's date and resource, allocates the next number under a queue lock, creates a Token (status="CREATED"), and links it to booking.token.
  • booked_on is auto_now_add — never set by clients.

API integration notes

  • TokenSlot and TokenBooking are exposed through Care's scheduling REST API and align with the FHIR Slot and Appointment resources; payload field names may differ from Django model names.
  • Writes use TokenBookingWriteSpec (status constrained to BookingStatusChoices, note required). cancelled / entered_in_error / rescheduled cannot be set through a plain update — use the cancel / reschedule actions.
  • List responses use TokenBookingReadSpec; detail responses use TokenBookingRetrieveSpec, which adds associated_encounter.
  • allocated on TokenSlot is a capacity counter maintained by lock_create_appointment and cancel; do not write it directly.
  • tags is stored as an array of integer tag IDs but returned as rendered tag objects on read.
  • List requires a resource_type query param and authorization scoped to organizations or resource IDs.