References/Booking
More languages
More actions
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.
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
| Model | Purpose |
|---|---|
TokenSlot
|
A concrete, bookable time window generated from a schedulable resource's availability |
TokenBooking
|
A 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
| Field | Type | Notes |
|---|---|---|
token_slot |
FK → TokenSlot (PROTECT) |
The slot this appointment occupies; required. PROTECT prevents deleting a slot that has bookings |
patient |
FK → emr.Patient (CASCADE) |
The booked patient; required |
booked_on |
DateTimeField |
auto_now_add — stamped once at creation, never accepted from clients
|
booked_by |
FK → User (CASCADE) |
User who created the booking; nullable for self-service or system-created bookings |
status |
CharField (storage) → BookingStatusChoices (API) |
Appointment lifecycle status. Unbounded on the model; the write spec restricts it to the BookingStatusChoices values |
note |
TextField |
Free-text note; nullable in storage but required (str) on write
|
tags |
ArrayField[int] |
Tag IDs, default empty list. Managed via SingleFacilityTagManager; reads return rendered tag objects, not raw IDs
|
associated_encounter |
FK → emr.Encounter (PROTECT) |
Encounter linked to the appointment; nullable, defaults to None. Surfaced only by TokenBookingRetrieveSpec
|
token |
FK → emr.Token (PROTECT) |
Queue token issued for this booking; nullable, defaults to None, related_name="token_booking". Created by the generate_token action
|
charge_item |
FK → 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:
| Value | Notes |
|---|---|
proposed
|
|
pending
|
|
booked
|
Default applied server-side when a booking is created via lock_create_appointment
|
arrived
|
|
fulfilled
|
Terminal; in COMPLETED_STATUS_CHOICES
|
cancelled
|
Set only via the cancel endpoint; in CANCELLED_STATUS_CHOICES + COMPLETED_STATUS_CHOICES
|
noshow
|
Terminal; in COMPLETED_STATUS_CHOICES
|
entered_in_error
|
Set only via the cancel endpoint; in CANCELLED_STATUS_CHOICES + COMPLETED_STATUS_CHOICES
|
checked_in
|
|
waitlist
|
|
in_consultation
|
Cannot be cancelled — the cancel handler rejects it |
rescheduled
|
Set only via the reschedule endpoint; in CANCELLED_STATUS_CHOICES + COMPLETED_STATUS_CHOICES
|
Two derived sets (from slot/spec.py) drive the lifecycle rules:
| Set | Values |
|---|---|
CANCELLED_STATUS_CHOICES
|
entered_in_error, cancelled, rescheduled
|
COMPLETED_STATUS_CHOICES
|
fulfilled, 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.
Related models
TokenSlot
A bookable window derived from a resource's availability. Bookings reference it via PROTECT, so a slot with active bookings cannot be deleted.
| Field | Type | Notes |
|---|---|---|
resource |
FK → SchedulableResource (CASCADE) |
The schedulable resource — practitioner, location, or healthcare service — the slot belongs to; required |
availability |
FK → Availability (CASCADE) |
The availability rule that generated this slot; nullable |
start_datetime |
DateTimeField |
Slot start; required, tz-aware |
end_datetime |
DateTimeField |
Slot end; required, tz-aware |
allocated |
IntegerField |
Count 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 ([https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/base.py base.py]): serialize (DB → pydantic, read) and de_serialize (pydantic → DB, write), with perform_extra_serialization / perform_extra_deserialization hooks.
| Spec class | Role | Exposes / behaviour |
|---|---|---|
TokenBookingBaseSpec
|
shared | __model__ = TokenBooking, __exclude__ = ["token_slot", "patient"]
|
TokenBookingWriteSpec
|
write · create + update | Fields: status: BookingStatusChoices, note: str. Rejects status in CANCELLED_STATUS_CHOICES (use the cancel/reschedule endpoints). Serves as both pydantic_model and pydantic_update_model
|
TokenBookingMinimumReadSpec
|
read · embed | Lightweight 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
|
TokenBookingBaseReadSpec
|
read · shared | Adds booked_by: UserSpec, resource_type, resource (serialized resource), facility, created_by/updated_by, token: TokenReadSpec, tags: list[dict] (rendered), charge_item: dict
|
TokenBookingReadSpec
|
read · list/default | TokenBookingBaseReadSpec + patient: PatientRetrieveSpec. The viewset's pydantic_read_model for list responses
|
TokenBookingOTPReadSpec
|
read · OTP flow | TokenBookingBaseReadSpec + patient: PatientOTPReadSpec for the public/OTP booking flow
|
TokenBookingRetrieveSpec
|
read · detail | TokenBookingReadSpec + associated_encounter: dict (serialized EncounterListSpec). The viewset's pydantic_retrieve_model
|
TokenSlotBaseSpec
|
read · 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)
idalways comes fromobj.external_id(UUID), not the integer PK.token_slotis embedded viaTokenSlotBaseSpec.serialize(...).resource_typecomes fromtoken_slot.resource.resource_type;resourceis built byserialize_resource()— aUserSpecforpractitioner,HealthcareServiceReadSpecforhealthcare_service,FacilityLocationListSpecforlocation.facilityis a cachedFacilityBareMinimumSpec;booked_by/created_by/updated_byare cachedUserSpecs.tagsare rendered throughSingleFacilityTagManager().render_tags(obj)as objects, not raw IDs.token(TokenReadSpec),charge_item(ChargeItemReadSpec), andassociated_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 inCOMPLETED_STATUS_CHOICES), then incrementstoken_slot.allocated, creates the booking withstatus="booked", and — if the schedule has acharge_item_definition— auto-creates the linkedcharge_item. - Cancel (
cancelaction) — acceptsCancelBookingSpec { reason: cancelled | entered_in_error | rescheduled, note?: str }. Rejects cancelling anin_consultationbooking. Decrementstoken_slot.allocated(unless already cancelled), setsstatus = reason, optionally overwritesnote, setsupdated_by, and aborts any linkedcharge_item(handle_charge_item_cancel+ statusaborted). - Reschedule (
rescheduleaction) — acceptsRescheduleBookingSpec { new_slot: UUID, new_booking_note: str, previous_booking_note?: str, tags: list[UUID] }and requirescan_reschedule_booking. Cancels the existing booking with reasonrescheduled, then runslock_create_appointmentto create a fresh booking onnew_slotfor the same patient. Rejects rescheduling to the same slot. - Generate token (
generate_tokenaction) — acceptsTokenGenerationSpec { category: UUID, note?: str, queue?: UUID }. Rejects if a token already exists. Resolves or creates aTokenQueuefor the slot's date and resource, allocates the nextnumberunder a queue lock, creates aToken(status="CREATED"), and links it tobooking.token. booked_onisauto_now_add— never set by clients.
API integration notes
TokenSlotandTokenBookingare exposed through Care's scheduling REST API and align with the FHIRSlotandAppointmentresources; payload field names may differ from Django model names.- Writes use
TokenBookingWriteSpec(statusconstrained toBookingStatusChoices,noterequired).cancelled/entered_in_error/rescheduledcannot be set through a plain update — use thecancel/rescheduleactions. - List responses use
TokenBookingReadSpec; detail responses useTokenBookingRetrieveSpec, which addsassociated_encounter. allocatedonTokenSlotis a capacity counter maintained bylock_create_appointmentand cancel; do not write it directly.tagsis stored as an array of integer tag IDs but returned as rendered tag objects on read.- List requires a
resource_typequery param and authorization scoped to organizations or resource IDs.
Source code
The files below are fetched live from the care repository — any changes pushed to the repo will show here automatically:
Model
Loading…
Spec
Loading…
Viewset
Loading…
Related links
Related
Pages that link here:
- Reference: References/Charge Item