Availability engine + booking API (create/cancel/reschedule) #16
Notifications
Due Date
No due date set.
Blocks
Depends on
#17 Public booking page + iframe embed
BPPP/smb-online
#23 Owner notification webhook (Telegram re-point)
BPPP/smb-online
#15 Booking schema + tenancy-safe data access layer
BPPP/smb-online
Reference: BPPP/smb-online#16
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
#14
What to build
The availability engine and booking API, exercised headlessly (via the Flask test client / HTTP,
no browser UI yet). This is the ticket that makes "can a customer actually get booked, without
double-booking, respecting the business's rules" true end to end at the API layer.
duration_minutes, andthe resource's
min_notice_minutes/max_advance_days/buffer_minutes, compute the list ofbookable start times for a given date range. Must be correct across a DST transition date
(Europe/Berlin).
via the ticket-1 data-access layer, sets status to
confirmedorpendingbased on the client'sauto_confirmsetting.one succeeds; the other gets a clean, specific error (not a 500), surfaced by the
EXCLUDEconstraint from ticket 1.
booking_id+ expiry), marks thebooking cancelled.
start_time/end_timeon the existingbooking (still protected by the
EXCLUDEconstraint) — implemented as a first-class update, notcancel-then-rebook. This action is written so it can be reused by both the customer-facing flow
(this ticket) and the owner dashboard (ticket 6) later.
cancel/reschedule token to be used by ticket 3/4's UI and ticket 4's email.
Acceptance criteria
hand-computed expected slots for a normal day and for a day either side of a DST transition.
min_notice_minutesof "now" are excluded; slots beyondmax_advance_daysareexcluded; slots that would violate
buffer_minutesagainst an existing booking are excluded.auto_confirm = trueyields statusconfirmed; on aclient with
auto_confirm = falseyields statuspending.created; the failing request gets a clean 4xx response, not a 500.
rejected.
EXCLUDEconstraint (rescheduling into an already-occupied slot fails cleanly).
Blocked by
Implemented in
2f6e0c1.resourcesgainsmin_notice_minutes/max_advance_days/buffer_minutes; newresource_hourstable (per-weekday opening hours) -- #15 didn't add this config even though #16 depends on it, so it's included here.availability.py: pure slot-generation function (no I/O). Tested against hand-computed slots for a normal day and both sides of the 2026 Europe/Berlin DST transitions (spring-forward and fall-back), plus min-notice/max-advance/buffer exclusion.booking_api.py: new JSON blueprint --GET /api/booking/slots,POST /api/booking(create; status isconfirmed/pendingper the client'sauto_confirm),POST /api/booking/cancel,POST /api/booking/reschedule, using a signed JWT (dedicatedBOOKING_TOKEN_SECRET, not shared withCRM_API_TOKEN) embeddingbooking_id+ expiry for the manage actions.DeadlockDetectedinstead ofExclusionViolationwhen two inserts race the exclusion constraint directly, which would have surfaced as an uncaught 500. Both are now caught and turned into a clean 409.All acceptance criteria met. Closing.