A booking system fails the morning two people are given the same chair. The record has to make that impossible, including when the deposit arrives late and when the customer moves the time.
The slot is a row, not a feeling
Model the thing that can be double-booked: a person, a room, a vehicle, a machine. A slot is that resource, a start, and an end. Two bookings overlap when one starts before the other ends and they share the resource. Write that rule in the database, with an exclusion constraint or a lock around the insert, not only in the screen that happens to check first.
Deposits are part of the booking, not a separate product. Hold the slot when the customer starts checkout, and release it if the payment does not arrive within a short window you publish. A hold that lasts all day will show a full diary that is actually empty. A hold that lasts ten seconds will lose the slot between the STK push and the callback.
Moving a time is a new claim
A reschedule is not an edit of the clock on the old row if that edit can collide. Release the old slot and claim the new one in the same transaction. If the new slot is taken, the old appointment stays. The customer should see that sentence, not a spinner that ends on the original time with no explanation.
Cancellations need a reason and a moment. If the deposit is refundable before a cutoff, record the cutoff on the service, not in a staff member’s memory. After the cutoff, the slot can return to the diary and the deposit can stay. Both facts belong on the booking so the next person on the desk does not invent a policy.
The diary the desk actually uses
Staff need a day view that shows holds, confirmed bookings, and cancellations in different states. A list of every row ever created is an archive. The desk needs today, the gaps, and who has not paid. Put the customer’s phone on the booking because that is how the desk will reach them when a practitioner is late.
Reminders go out once, for a booking that is still confirmed, at a time the business chooses. A reminder for a cancelled slot is how you train customers to ignore you. Store the reminder as sent, with the booking id, so a retry does not send a second SMS.
One transaction for the claim
The unique pair is the resource and the start, only among bookings that still occupy the diary. Cancelled rows stay for the history and drop out of the constraint. The application checks overlap as well, because a unique start does not by itself stop a long appointment from covering the next one. The constraint stops two requests from taking the same start. The overlap query stops a 90-minute job from sitting on top of a 30-minute one.
Python · bookings/claims.py
from django.db import transaction
def claim(resource_id: int, start, end, customer_id: int):
with transaction.atomic():
clash = Booking.objects.select_for_update().filter(
resource_id=resource_id,
status__in=["held", "confirmed"],
start__lt=end,
end__gt=start,
)
if clash.exists():
return None
return Booking.objects.create(
resource_id=resource_id,
customer_id=customer_id,
start=start,
end=end,
status="held",
)
What the desk sees on a busy morning
The day view is the product. It shows holds that will die if the payment does not arrive, confirmed bookings, and the gaps a caller can still take. A list of every booking ever made is an archive the desk will not open while the phone is ringing. Put today first, with the customer's phone on the row, because that is how a late practitioner is explained.
Overlapping services on one person are the case the constraint must catch, not only two bookings with the same start. A ninety-minute appointment that begins at 10:00 covers 10:30. The overlap query is the rule. The unique start is only the easy half. Write a test with those two intervals and expect the second claim to fail.
Reminders are part of the booking, not a separate campaign. Send one message for a booking that is still confirmed, at the hour the business chose, and store that it was sent. A reminder for a cancelled slot is how customers learn to ignore the next one, which might be the one that mattered.
When you demo this, book the same resource twice from two browsers at the same moment. One succeeds. The other gets a sentence. If both succeed, the diary is a suggestion and the first real Saturday will prove it.
Before the diary goes live
- Name the resource that must not be double-booked.
- Hold a slot only for the length of the payment window.
- Reschedule by releasing the old slot and claiming the new one together.