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.
Source:
- Model: [https://github.com/ohcnetwork/care/blob/develop/care/emr/models/scheduling/booking.py
care/emr/models/scheduling/booking.py] - Resource spec: [https://github.com/ohcnetwork/care/blob/develop/care/emr/resources/scheduling/slot/spec.py
care/emr/resources/scheduling/slot/spec.py] - Viewset (side effects): [https://github.com/ohcnetwork/care/blob/develop/care/emr/api/viewsets/scheduling/booking.py
care/emr/api/viewsets/scheduling/booking.py]
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
{{Field|model=Booking|section=TokenBooking fields|name=status|type=CharField (storage) → BookingStatusChoices (API)|notes=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.
Related
Pages that link here:
- Reference: References/Charge Item