feat(bookings): customer self-service rebooking with real slot picker

CustomerBookingView now carries tenantSlug/serviceId/staffId/
classSessionId so the manage page can query live availability; class
bookings resolve their session title as the display label. Reschedule
rejects waitlisted/completed bookings and session-bound class bookings
(400), collapsing a dead branch in the appointment path.

The manage page reschedule dialog replaces free-form date/time inputs
with a date → availability-slot picker filtered to appointment mode,
so customers can only move to a genuinely free slot with the correct
duration; 409 surfaces "slot just taken". Actions are status-gated:
reschedule for confirmed/pending appointments, cancel for live
bookings, explanatory note for class bookings.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-18 14:58:49 +02:00
1 parent 1cd5b1ba91
commit 796c06ae66
7 files changed
+311 -87

No files matched your search

+58 -6
View File
@@ -346,6 +346,10 @@ paths:
responses:
"200":
description: Booking details
content:
application/json:
schema:
$ref: "#/components/schemas/CustomerBookingView"
"403":
description: Invalid manage token
"404":
@@ -360,29 +364,37 @@ paths:
required: true
schema:
type: string
- in: query
name: token
required: true
schema:
type: string
description: Manage token issued at booking time
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [token, startsAt]
required: [newStartsAt, newEndsAt]
properties:
token:
type: string
startsAt:
newStartsAt:
type: string
format: date-time
endsAt:
newEndsAt:
type: string
format: date-time
reason:
type: string
responses:
"200":
description: Booking rescheduled
"400":
description: Invalid new window, non-reschedulable status, or class booking
"403":
description: Invalid manage token
"409":
description: New slot conflicts
description: New slot conflicts or booking already cancelled
/v1/public/bookings/{reference}/cancel:
post:
tags: [Public Booking]
@@ -1770,6 +1782,46 @@ components:
waitlistPosition:
type: integer
description: 1-based queue spot for waitlisted class bookings; absent otherwise
CustomerBookingView:
type: object
properties:
reference:
type: string
customerName:
type: string
customerEmail:
type: string
service:
type: string
businessName:
type: string
tenantSlug:
type: string
serviceId:
type: string
format: uuid
staffId:
type: string
format: uuid
classSessionId:
type: string
format: uuid
description: Present for session-bound bookings, which cannot be rescheduled to arbitrary times
startsAt:
type: string
format: date-time
endsAt:
type: string
format: date-time
location:
type: string
status:
type: string
notes:
type: string
waitlistPosition:
type: integer
description: 1-based queue spot for waitlisted class bookings; absent otherwise
DashboardBookingRequest:
type: object
required: [customerName, customerEmail, startsAt]