mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
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:
1 parent
a82a48e1c8
commit
1cd5b1ba91
18 files changed
+1022
-59
No files matched your search
@@ -303,6 +303,22 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/catalog/bookings/{bookingID}/promote": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
post: operations["promoteDashboardBooking"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/catalog/class-sessions": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -335,6 +351,22 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/catalog/class-sessions/{sessionID}/roster": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["getSessionRoster"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/catalog/class-templates": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -1024,6 +1056,7 @@ export interface components {
|
||||
templateTitle: string;
|
||||
/** Format: uuid */
|
||||
tenantId: string;
|
||||
waitlistCount: number;
|
||||
};
|
||||
ClassTemplate: {
|
||||
capacity: number;
|
||||
@@ -1196,6 +1229,10 @@ export interface components {
|
||||
/** Format: uri */
|
||||
url: string;
|
||||
};
|
||||
PromoteBookingRequest: {
|
||||
/** @description Confirm even when the session is full (deliberate overbooking) */
|
||||
allowOverCapacity?: boolean;
|
||||
};
|
||||
PublicAvailabilityResponse: {
|
||||
/** @enum {string} */
|
||||
locale: "cs" | "en";
|
||||
@@ -1244,6 +1281,31 @@ export interface components {
|
||||
/** Format: uuid */
|
||||
tenantId: string;
|
||||
};
|
||||
SessionRoster: {
|
||||
capacity: number;
|
||||
confirmed: components["schemas"]["SessionRosterEntry"][];
|
||||
/** Format: date-time */
|
||||
endsAt: string;
|
||||
locationName?: string;
|
||||
/** Format: uuid */
|
||||
sessionId: string;
|
||||
/** Format: date-time */
|
||||
startsAt: string;
|
||||
templateTitle: string;
|
||||
waitlist: components["schemas"]["SessionRosterEntry"][];
|
||||
};
|
||||
SessionRosterEntry: {
|
||||
/** Format: uuid */
|
||||
bookingId: string;
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
customerEmail: string;
|
||||
customerName: string;
|
||||
customerPhone?: string;
|
||||
reference: string;
|
||||
status: string;
|
||||
waitlistPosition?: number;
|
||||
};
|
||||
StaffMember: {
|
||||
active: boolean;
|
||||
/** Format: date-time */
|
||||
@@ -1334,6 +1396,8 @@ export interface components {
|
||||
siteId?: string;
|
||||
};
|
||||
UpcomingBooking: {
|
||||
/** Format: uuid */
|
||||
classSessionId?: string;
|
||||
customerEmail?: string;
|
||||
customerName?: string;
|
||||
/** Format: date-time */
|
||||
@@ -1348,6 +1412,8 @@ export interface components {
|
||||
/** Format: date-time */
|
||||
startsAt?: string;
|
||||
status?: string;
|
||||
/** @description 1-based queue spot for waitlisted class bookings; absent otherwise */
|
||||
waitlistPosition?: number;
|
||||
};
|
||||
UpdateBrandRequest: {
|
||||
logoUrl?: string;
|
||||
@@ -2084,6 +2150,51 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
promoteDashboardBooking: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
bookingID: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["PromoteBookingRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Waitlisted booking confirmed; customer notified */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Booking is not on a waitlist */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Booking not found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Session at capacity; retry with allowOverCapacity to override */
|
||||
409: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
listClassSessions: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -2169,6 +2280,35 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
getSessionRoster: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
sessionID: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Confirmed attendees and FIFO waitlist for the session */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["SessionRoster"];
|
||||
};
|
||||
};
|
||||
/** @description Class session not found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
listClassTemplates: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Reference in new issue
Block a user