feat(bookings): session-to-session move for class bookings

Class customers can move a booking to another future session of the
same template from the manage page. Full target sessions place the
booking on the waitlist; freeing a confirmed seat drains the old
session's queue via the existing promotion path. Reminders re-arm
for the new time.

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 15:42:04 +02:00
1 parent 6c3b2bf6bf
commit e5cb9bd543
10 files changed
+927 -10

No files matched your search

+9 -2
View File
@@ -140,7 +140,7 @@
- remaining: live staging validation on Neon + pgx repository path
- `[done/partial]` Milestone 21: Customer self-service rebooking
- `CustomerBookingView` now carries `tenantSlug`, `serviceId`, `staffId`, `classSessionId` — everything the manage page needs to query real availability; class bookings resolve the session title as their service label instead of rendering blank
- `RescheduleBooking` hardened: rejects `waitlisted`/`completed` bookings and any class booking (session-bound times cannot move to arbitrary windows; switching sessions is cancel-and-rebook); the previously dead `ClassSessionID == nil` branch collapsed into a straight appointment validation
- `RescheduleBooking` hardened: rejects `waitlisted`/`completed` bookings and any class booking (session-bound times cannot move to arbitrary windows; session-to-session moves landed in Milestone 23); the previously dead `ClassSessionID == nil` branch collapsed into a straight appointment validation
- manage page reschedule dialog replaced free-form date+time inputs with a real slot picker: pick a date → `GET /v1/public/tenants/{slug}/availability` (filtered by the booking's service/staff) → time buttons; the slot carries its own duration so the hardcoded 60-minute assumption is gone; 409 surfaces "slot just taken" instead of a generic failure
- action buttons are now status-gated: reschedule shows only for confirmed/pending appointments, cancel for any live booking, and class bookings get an explanatory note instead of a broken button
- `CustomerBookingView` schema added to OpenAPI and referenced from `GET /v1/public/bookings/{reference}`; the reschedule contract documents the class-booking 400 and slot-conflict 409
@@ -154,6 +154,14 @@
- settings page "Kalendář (ICS)" card: copyable feed URLs per business/staff member, two-step rotate with confirmation, demo-mode placeholders pointing at real seeded tokens
- verified live: feed info returns tenant+staff tokens, tenant feed renders the seeded booking with staff name in the summary, staff feed omits it, bogus token 404s, rotation kills the old URL and the new one serves the same feed
- remaining: live staging validation on Neon + pgx repository path; Google Calendar two-way sync stays deferred (the ICS read direction covers the common ask)
- `[done/partial]` Milestone 23: Class session-to-session move
- class bookings are session-bound, so rebooking is a move between sessions of the same template rather than a free reschedule — `GET /v1/public/bookings/{reference}/sessions` lists future same-template targets with capacity/confirmed/waitlist counts, `POST /v1/public/bookings/{reference}/move` performs the move under the manage token
- move semantics: confirmed when the target has a free seat, waitlisted when full; the old waitlist entry resolves, reminders re-arm for the new time, and freeing a confirmed seat drains the old session's queue through the existing promotion path
- rejects appointment bookings (they use the slot picker), cancelled/completed bookings, same-session moves, foreign-template or past sessions
- manage page: "Change session" / "Změnit lekci" action opens a target picker — date/time/location rows with free-seat or waitlist badges, per-row selection, localized success/error states; the current session is excluded from targets
- `SessionMoveTarget` schema + both endpoints documented in OpenAPI; generated client regenerated
- verified live + browser: created a second session of the demo template, moved a confirmed booking over HTTP (200 + new `classSessionId`), then repeated through the real manage page in Czech and English — success banner, updated date/time, clean console
- remaining: live staging validation on Neon + pgx repository path
## Overview
Bookra is a Czech-first booking SaaS for small local service businesses. It targets salons, massage therapists, small clinics, repair shops, and small studios/clubs that need a simple public booking flow, internal schedule management, reminders, and basic business reporting without becoming a full ERP.
@@ -404,7 +412,6 @@ Key entities:
Shortlist of worthwhile directions once v1 stabilizes — kept deliberately small, in line with the "no feature sprawl" principle:
- **Activity/audit log**: a per-tenant feed of who changed what (booking created/cancelled/promoted, settings edits). Natural fit for the notification dropdown and useful for multi-staff accountability.
- **Session-to-session move for classes**: M21 covered appointment rebooking; class customers currently must cancel and book another session. A "move to another session of the same template" flow (target session picker + capacity/waitlist handling) would close the gap.
- **Google Calendar sync**: M22 shipped read-only ICS feeds; two-way sync (push bookings into Google, pull busy blocks back as availability exceptions) needs OAuth and conflict rules — larger commitment, defer until a concrete request.
- **Recurring class schedules**: templates currently schedule sessions one at a time; a weekly recurrence rule with per-session overrides would cut setup time for studios.
- **Booking notes intake**: structured intake fields per service (e.g. "first visit?", "injuries?") instead of the single free-text notes field — still schema-light.