mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 07:41:16 +00:00
feat(catalog): add service and staff management end-to-end
Services and staff existed as schema + booking FK targets but had no management surface. Adds full CRUD: repository (pgx + in-memory with matching FK semantics), catalog service validation with tenant isolation, MaxStaff entitlement enforcement, protected API routes, OpenAPI schemas, and dashboard screens for both entities. Dashboard summary now resolves staff names on bookings and computes setup completion from real configured state. Booking modal selects from the tenant's actual services; blocked days can be scoped per staff member. 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
7a6223c07b
commit
09f780e7a6
21 files changed
+2422
-15
No files matched your search
@@ -351,6 +351,70 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/catalog/services": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["listServices"];
|
||||
put?: never;
|
||||
post: operations["createService"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/catalog/services/{serviceID}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put: operations["updateService"];
|
||||
post?: never;
|
||||
delete: operations["deleteService"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/catalog/staff": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["listStaff"];
|
||||
put?: never;
|
||||
post: operations["createStaff"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/catalog/staff/{staffID}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put: operations["updateStaff"];
|
||||
post?: never;
|
||||
delete: operations["deleteStaff"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/v1/catalog/working-hours": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -976,6 +1040,35 @@ export interface components {
|
||||
environment: string;
|
||||
neonAuthEnabled: boolean;
|
||||
};
|
||||
Service: {
|
||||
/** @description Blocked time after the appointment (0-480) */
|
||||
bufferAfterMinutes: number;
|
||||
/** @description Blocked time before the appointment (0-480) */
|
||||
bufferBeforeMinutes: number;
|
||||
/** Format: date-time */
|
||||
createdAt?: string;
|
||||
/** @description Appointment length in minutes (1-1440) */
|
||||
durationMinutes: number;
|
||||
/** Format: uuid */
|
||||
id: string;
|
||||
name: string;
|
||||
/** @description Price in the tenant's currency minor units */
|
||||
priceCents: number;
|
||||
/** Format: uuid */
|
||||
tenantId: string;
|
||||
};
|
||||
StaffMember: {
|
||||
active: boolean;
|
||||
/** Format: date-time */
|
||||
createdAt?: string;
|
||||
displayName: string;
|
||||
/** Format: uuid */
|
||||
id: string;
|
||||
/** Format: uuid */
|
||||
locationId?: string | null;
|
||||
/** Format: uuid */
|
||||
tenantId: string;
|
||||
};
|
||||
SubscriptionSnapshot: {
|
||||
cancelAtPeriodEnd: boolean;
|
||||
checkoutUrlAvailable: boolean;
|
||||
@@ -1109,6 +1202,22 @@ export interface components {
|
||||
/** @enum {string} */
|
||||
type?: "room" | "private" | "hall";
|
||||
};
|
||||
UpsertServiceRequest: {
|
||||
bufferAfterMinutes?: number;
|
||||
bufferBeforeMinutes?: number;
|
||||
durationMinutes: number;
|
||||
name: string;
|
||||
priceCents?: number;
|
||||
};
|
||||
UpsertStaffRequest: {
|
||||
active?: boolean;
|
||||
displayName: string;
|
||||
/**
|
||||
* Format: uuid
|
||||
* @description Omit to keep the current assignment; empty string clears it
|
||||
*/
|
||||
locationId?: string | null;
|
||||
};
|
||||
WidgetSnippet: {
|
||||
code?: string;
|
||||
kind?: string;
|
||||
@@ -1127,6 +1236,8 @@ export interface components {
|
||||
blockedDayID: string;
|
||||
customerID: string;
|
||||
locationID: string;
|
||||
serviceID: string;
|
||||
staffID: string;
|
||||
};
|
||||
requestBodies: never;
|
||||
headers: never;
|
||||
@@ -1978,6 +2089,263 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
listServices: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Tenant services */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["Service"][];
|
||||
};
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
createService: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["UpsertServiceRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Service created */
|
||||
201: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["Service"];
|
||||
};
|
||||
};
|
||||
/** @description Invalid request */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
updateService: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
serviceID: components["parameters"]["serviceID"];
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["UpsertServiceRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Service updated */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["Service"];
|
||||
};
|
||||
};
|
||||
/** @description Invalid request */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Service not found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
deleteService: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
serviceID: components["parameters"]["serviceID"];
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Service deleted */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Service not found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
listStaff: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Tenant staff */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["StaffMember"][];
|
||||
};
|
||||
};
|
||||
/** @description Unauthorized */
|
||||
401: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
createStaff: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["UpsertStaffRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Staff member created */
|
||||
201: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["StaffMember"];
|
||||
};
|
||||
};
|
||||
/** @description Invalid request */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Plan limit reached */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
updateStaff: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
staffID: components["parameters"]["staffID"];
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["UpsertStaffRequest"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Staff member updated */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["StaffMember"];
|
||||
};
|
||||
};
|
||||
/** @description Invalid request */
|
||||
400: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Staff member not found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
deleteStaff: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
staffID: components["parameters"]["staffID"];
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Staff member deleted */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Staff member not found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
listWorkingHours: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Reference in new issue
Block a user