* feat: multi-pet client group booking (closes groombook/groombook#10) (GRO-27) - Add appointment_groups table: links multiple appointments from one client visit - Add group_id FK on appointments (nullable, backward-compatible) - Add GET/POST/PATCH/DELETE /api/appointment-groups endpoints - POST creates group record + one appointment per pet atomically (with conflict checks) - DELETE soft-cancels all appointments in the group - Add GroupBooking.tsx page at /group-bookings with: - Dynamic pet-slot form (min 2 pets, each with their own groomer/service/end time) - Auto-calculates end time from service duration - Group card list showing all pets, groomers, and statuses side-by-side - Client filter and cancel-all action - Wire into nav and routing in App.tsx - Export AppointmentGroup type; add groupId field to Appointment type Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix: remove eslint-disable for uninstalled react-hooks plugin; remove unused clientMap (GRO-27) Co-Authored-By: Paperclip <noreply@paperclip.ing> --------- Co-authored-by: Groom Book CTO <cto@groombook.app> Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit was merged in pull request #31.
This commit is contained in:
committed by
GitHub
parent
e63ce83400
commit
f47717dfd8
@@ -0,0 +1,12 @@
|
||||
-- Appointment groups: link multiple appointments from the same client visit.
|
||||
-- Each appointment in a group is for a different pet and may have a different groomer.
|
||||
CREATE TABLE appointment_groups (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE RESTRICT,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Link appointments to a group (nullable — non-grouped appointments are unaffected)
|
||||
ALTER TABLE appointments ADD COLUMN group_id UUID REFERENCES appointment_groups(id) ON DELETE SET NULL;
|
||||
@@ -102,6 +102,18 @@ export const recurringSeries = pgTable("recurring_series", {
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
// appointmentGroups links multiple appointments from the same client visit.
|
||||
// Each pet in the group gets its own appointment row with its own groomer.
|
||||
export const appointmentGroups = pgTable("appointment_groups", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
clientId: uuid("client_id")
|
||||
.notNull()
|
||||
.references(() => clients.id, { onDelete: "restrict" }),
|
||||
notes: text("notes"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const appointments = pgTable("appointments", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
clientId: uuid("client_id")
|
||||
@@ -127,6 +139,10 @@ export const appointments = pgTable("appointments", {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
seriesIndex: integer("series_index"),
|
||||
// Multi-pet group booking: links this appointment to others in the same visit
|
||||
groupId: uuid("group_id").references(() => appointmentGroups.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user