feat(classes): session roster + manual waitlist promotion

Adds the roster and manual promotion controls deferred from the booking
lifecycle milestones. Businesses can now inspect who holds a seat and who
is queueing on a class session, and promote waitlisted customers by hand
— including a deliberate over-capacity override when the session is full.

Backend:
- GET /v1/catalog/class-sessions/{id}/roster — session metadata plus
  confirmed and waitlist entry lists with queue positions
- POST /v1/catalog/bookings/{id}/promote — 409 on a full session unless
  allowOverCapacity is set; 400 for non-waitlisted, 404 for missing or
  foreign bookings; reuses the shared confirm path so waitlist ledger
  rows resolve and reminder jobs re-arm
- ListBookingsBySession repository method (pgx + memory); ClassSession
  carries waitlistCount; UpcomingBooking carries classSessionId and
  waitlistPosition
- OpenAPI paths + schemas, regenerated client, catalog service tests

Frontend:
- classes tab session rows expand into a two-column roster panel with
  per-entry promote buttons and an explicit confirm-anyway step on 409
- booking detail modal shows waitlist position and offers promote for
  waitlisted bookings; list badges show #N queue positions
- CS/EN i18n keys and seeded demo roster data; demo mutations surface
  the demo notice

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:37:40 +02:00
1 parent a82a48e1c8
commit 1cd5b1ba91
18 files changed
+1022 -59

No files matched your search

+114 -1
View File
@@ -820,6 +820,34 @@ paths:
description: Booking already cancelled
"404":
description: Booking not found
/v1/catalog/bookings/{bookingID}/promote:
post:
tags: [Catalog]
operationId: promoteDashboardBooking
security:
- bearerAuth: []
parameters:
- in: path
name: bookingID
required: true
schema:
type: string
format: uuid
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/PromoteBookingRequest"
responses:
"200":
description: Waitlisted booking confirmed; customer notified
"400":
description: Booking is not on a waitlist
"404":
description: Booking not found
"409":
description: Session at capacity; retry with allowOverCapacity to override
/v1/catalog/blocked-days:
get:
tags: [Catalog]
@@ -1196,6 +1224,28 @@ paths:
description: Class session deleted
"404":
description: Class session not found
/v1/catalog/class-sessions/{sessionID}/roster:
get:
tags: [Catalog]
operationId: getSessionRoster
security:
- bearerAuth: []
parameters:
- in: path
name: sessionID
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Confirmed attendees and FIFO waitlist for the session
content:
application/json:
schema:
$ref: "#/components/schemas/SessionRoster"
"404":
description: Class session not found
# ---------------- Billing ----------------
/v1/billing/subscription:
get:
@@ -1714,6 +1764,12 @@ components:
type: string
notes:
type: string
classSessionId:
type: string
format: uuid
waitlistPosition:
type: integer
description: 1-based queue spot for waitlisted class bookings; absent otherwise
DashboardBookingRequest:
type: object
required: [customerName, customerEmail, startsAt]
@@ -2126,7 +2182,7 @@ components:
type: integer
ClassSession:
type: object
required: [id, tenantId, templateId, templateTitle, startsAt, endsAt, capacity, bookedCount]
required: [id, tenantId, templateId, templateTitle, startsAt, endsAt, capacity, bookedCount, waitlistCount]
properties:
id:
type: string
@@ -2154,6 +2210,63 @@ components:
type: integer
bookedCount:
type: integer
waitlistCount:
type: integer
SessionRosterEntry:
type: object
required: [bookingId, reference, customerName, customerEmail, status, createdAt]
properties:
bookingId:
type: string
format: uuid
reference:
type: string
customerName:
type: string
customerEmail:
type: string
customerPhone:
type: string
status:
type: string
waitlistPosition:
type: integer
createdAt:
type: string
format: date-time
SessionRoster:
type: object
required: [sessionId, templateTitle, startsAt, endsAt, capacity, confirmed, waitlist]
properties:
sessionId:
type: string
format: uuid
templateTitle:
type: string
locationName:
type: string
startsAt:
type: string
format: date-time
endsAt:
type: string
format: date-time
capacity:
type: integer
confirmed:
type: array
items:
$ref: "#/components/schemas/SessionRosterEntry"
waitlist:
type: array
items:
$ref: "#/components/schemas/SessionRosterEntry"
PromoteBookingRequest:
type: object
properties:
allowOverCapacity:
type: boolean
description: Confirm even when the session is full (deliberate overbooking)
CreateClassSessionRequest:
type: object
required: [templateId, startsAt]