@voyantjs/bookings 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,261 @@
1
+ export declare const bookingGroupKindEnum: import("drizzle-orm/pg-core").PgEnum<["shared_room", "other"]>;
2
+ export declare const bookingGroupMemberRoleEnum: import("drizzle-orm/pg-core").PgEnum<["primary", "shared"]>;
3
+ export declare const bookingGroups: import("drizzle-orm/pg-core").PgTableWithColumns<{
4
+ name: "booking_groups";
5
+ schema: undefined;
6
+ columns: {
7
+ id: import("drizzle-orm/pg-core").PgColumn<{
8
+ name: string;
9
+ tableName: "booking_groups";
10
+ dataType: "string";
11
+ columnType: "PgText";
12
+ data: string;
13
+ driverParam: string;
14
+ notNull: true;
15
+ hasDefault: true;
16
+ isPrimaryKey: true;
17
+ isAutoincrement: false;
18
+ hasRuntimeDefault: true;
19
+ enumValues: [string, ...string[]];
20
+ baseColumn: never;
21
+ identity: undefined;
22
+ generated: undefined;
23
+ }, {}, {}>;
24
+ kind: import("drizzle-orm/pg-core").PgColumn<{
25
+ name: "kind";
26
+ tableName: "booking_groups";
27
+ dataType: "string";
28
+ columnType: "PgEnumColumn";
29
+ data: "other" | "shared_room";
30
+ driverParam: string;
31
+ notNull: true;
32
+ hasDefault: true;
33
+ isPrimaryKey: false;
34
+ isAutoincrement: false;
35
+ hasRuntimeDefault: false;
36
+ enumValues: ["shared_room", "other"];
37
+ baseColumn: never;
38
+ identity: undefined;
39
+ generated: undefined;
40
+ }, {}, {}>;
41
+ label: import("drizzle-orm/pg-core").PgColumn<{
42
+ name: "label";
43
+ tableName: "booking_groups";
44
+ dataType: "string";
45
+ columnType: "PgText";
46
+ data: string;
47
+ driverParam: string;
48
+ notNull: true;
49
+ hasDefault: false;
50
+ isPrimaryKey: false;
51
+ isAutoincrement: false;
52
+ hasRuntimeDefault: false;
53
+ enumValues: [string, ...string[]];
54
+ baseColumn: never;
55
+ identity: undefined;
56
+ generated: undefined;
57
+ }, {}, {}>;
58
+ primaryBookingId: import("drizzle-orm/pg-core").PgColumn<{
59
+ name: string;
60
+ tableName: "booking_groups";
61
+ dataType: "string";
62
+ columnType: "PgText";
63
+ data: string;
64
+ driverParam: string;
65
+ notNull: false;
66
+ hasDefault: false;
67
+ isPrimaryKey: false;
68
+ isAutoincrement: false;
69
+ hasRuntimeDefault: false;
70
+ enumValues: [string, ...string[]];
71
+ baseColumn: never;
72
+ identity: undefined;
73
+ generated: undefined;
74
+ }, {}, {}>;
75
+ productId: import("drizzle-orm/pg-core").PgColumn<{
76
+ name: "product_id";
77
+ tableName: "booking_groups";
78
+ dataType: "string";
79
+ columnType: "PgText";
80
+ data: string;
81
+ driverParam: string;
82
+ notNull: false;
83
+ hasDefault: false;
84
+ isPrimaryKey: false;
85
+ isAutoincrement: false;
86
+ hasRuntimeDefault: false;
87
+ enumValues: [string, ...string[]];
88
+ baseColumn: never;
89
+ identity: undefined;
90
+ generated: undefined;
91
+ }, {}, {}>;
92
+ optionUnitId: import("drizzle-orm/pg-core").PgColumn<{
93
+ name: "option_unit_id";
94
+ tableName: "booking_groups";
95
+ dataType: "string";
96
+ columnType: "PgText";
97
+ data: string;
98
+ driverParam: string;
99
+ notNull: false;
100
+ hasDefault: false;
101
+ isPrimaryKey: false;
102
+ isAutoincrement: false;
103
+ hasRuntimeDefault: false;
104
+ enumValues: [string, ...string[]];
105
+ baseColumn: never;
106
+ identity: undefined;
107
+ generated: undefined;
108
+ }, {}, {}>;
109
+ metadata: import("drizzle-orm/pg-core").PgColumn<{
110
+ name: "metadata";
111
+ tableName: "booking_groups";
112
+ dataType: "json";
113
+ columnType: "PgJsonb";
114
+ data: Record<string, unknown>;
115
+ driverParam: unknown;
116
+ notNull: false;
117
+ hasDefault: false;
118
+ isPrimaryKey: false;
119
+ isAutoincrement: false;
120
+ hasRuntimeDefault: false;
121
+ enumValues: undefined;
122
+ baseColumn: never;
123
+ identity: undefined;
124
+ generated: undefined;
125
+ }, {}, {
126
+ $type: Record<string, unknown>;
127
+ }>;
128
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
129
+ name: "created_at";
130
+ tableName: "booking_groups";
131
+ dataType: "date";
132
+ columnType: "PgTimestamp";
133
+ data: Date;
134
+ driverParam: string;
135
+ notNull: true;
136
+ hasDefault: true;
137
+ isPrimaryKey: false;
138
+ isAutoincrement: false;
139
+ hasRuntimeDefault: false;
140
+ enumValues: undefined;
141
+ baseColumn: never;
142
+ identity: undefined;
143
+ generated: undefined;
144
+ }, {}, {}>;
145
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
146
+ name: "updated_at";
147
+ tableName: "booking_groups";
148
+ dataType: "date";
149
+ columnType: "PgTimestamp";
150
+ data: Date;
151
+ driverParam: string;
152
+ notNull: true;
153
+ hasDefault: true;
154
+ isPrimaryKey: false;
155
+ isAutoincrement: false;
156
+ hasRuntimeDefault: false;
157
+ enumValues: undefined;
158
+ baseColumn: never;
159
+ identity: undefined;
160
+ generated: undefined;
161
+ }, {}, {}>;
162
+ };
163
+ dialect: "pg";
164
+ }>;
165
+ export declare const bookingGroupMembers: import("drizzle-orm/pg-core").PgTableWithColumns<{
166
+ name: "booking_group_members";
167
+ schema: undefined;
168
+ columns: {
169
+ id: import("drizzle-orm/pg-core").PgColumn<{
170
+ name: string;
171
+ tableName: "booking_group_members";
172
+ dataType: "string";
173
+ columnType: "PgText";
174
+ data: string;
175
+ driverParam: string;
176
+ notNull: true;
177
+ hasDefault: true;
178
+ isPrimaryKey: true;
179
+ isAutoincrement: false;
180
+ hasRuntimeDefault: true;
181
+ enumValues: [string, ...string[]];
182
+ baseColumn: never;
183
+ identity: undefined;
184
+ generated: undefined;
185
+ }, {}, {}>;
186
+ groupId: import("drizzle-orm/pg-core").PgColumn<{
187
+ name: string;
188
+ tableName: "booking_group_members";
189
+ dataType: "string";
190
+ columnType: "PgText";
191
+ data: string;
192
+ driverParam: string;
193
+ notNull: true;
194
+ hasDefault: false;
195
+ isPrimaryKey: false;
196
+ isAutoincrement: false;
197
+ hasRuntimeDefault: false;
198
+ enumValues: [string, ...string[]];
199
+ baseColumn: never;
200
+ identity: undefined;
201
+ generated: undefined;
202
+ }, {}, {}>;
203
+ bookingId: import("drizzle-orm/pg-core").PgColumn<{
204
+ name: string;
205
+ tableName: "booking_group_members";
206
+ dataType: "string";
207
+ columnType: "PgText";
208
+ data: string;
209
+ driverParam: string;
210
+ notNull: true;
211
+ hasDefault: false;
212
+ isPrimaryKey: false;
213
+ isAutoincrement: false;
214
+ hasRuntimeDefault: false;
215
+ enumValues: [string, ...string[]];
216
+ baseColumn: never;
217
+ identity: undefined;
218
+ generated: undefined;
219
+ }, {}, {}>;
220
+ role: import("drizzle-orm/pg-core").PgColumn<{
221
+ name: "role";
222
+ tableName: "booking_group_members";
223
+ dataType: "string";
224
+ columnType: "PgEnumColumn";
225
+ data: "primary" | "shared";
226
+ driverParam: string;
227
+ notNull: true;
228
+ hasDefault: true;
229
+ isPrimaryKey: false;
230
+ isAutoincrement: false;
231
+ hasRuntimeDefault: false;
232
+ enumValues: ["primary", "shared"];
233
+ baseColumn: never;
234
+ identity: undefined;
235
+ generated: undefined;
236
+ }, {}, {}>;
237
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
238
+ name: "created_at";
239
+ tableName: "booking_group_members";
240
+ dataType: "date";
241
+ columnType: "PgTimestamp";
242
+ data: Date;
243
+ driverParam: string;
244
+ notNull: true;
245
+ hasDefault: true;
246
+ isPrimaryKey: false;
247
+ isAutoincrement: false;
248
+ hasRuntimeDefault: false;
249
+ enumValues: undefined;
250
+ baseColumn: never;
251
+ identity: undefined;
252
+ generated: undefined;
253
+ }, {}, {}>;
254
+ };
255
+ dialect: "pg";
256
+ }>;
257
+ export type BookingGroup = typeof bookingGroups.$inferSelect;
258
+ export type NewBookingGroup = typeof bookingGroups.$inferInsert;
259
+ export type BookingGroupMember = typeof bookingGroupMembers.$inferSelect;
260
+ export type NewBookingGroupMember = typeof bookingGroupMembers.$inferInsert;
261
+ //# sourceMappingURL=schema-groups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-groups.d.ts","sourceRoot":"","sources":["../src/schema-groups.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,oBAAoB,gEAAyD,CAAA;AAE1F,eAAO,MAAM,0BAA0B,6DAA6D,CAAA;AAEpG,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkBzB,CAAA;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB/B,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,OAAO,aAAa,CAAC,YAAY,CAAA;AAC5D,MAAM,MAAM,eAAe,GAAG,OAAO,aAAa,CAAC,YAAY,CAAA;AAC/D,MAAM,MAAM,kBAAkB,GAAG,OAAO,mBAAmB,CAAC,YAAY,CAAA;AACxE,MAAM,MAAM,qBAAqB,GAAG,OAAO,mBAAmB,CAAC,YAAY,CAAA"}
@@ -0,0 +1,34 @@
1
+ import { typeId, typeIdRef } from "@voyantjs/db/lib/typeid-column";
2
+ import { index, jsonb, pgEnum, pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
3
+ import { bookings } from "./schema-core";
4
+ export const bookingGroupKindEnum = pgEnum("booking_group_kind", ["shared_room", "other"]);
5
+ export const bookingGroupMemberRoleEnum = pgEnum("booking_group_member_role", ["primary", "shared"]);
6
+ export const bookingGroups = pgTable("booking_groups", {
7
+ id: typeId("booking_groups"),
8
+ kind: bookingGroupKindEnum("kind").notNull().default("shared_room"),
9
+ label: text("label").notNull(),
10
+ primaryBookingId: typeIdRef("primary_booking_id"),
11
+ productId: text("product_id"),
12
+ optionUnitId: text("option_unit_id"),
13
+ metadata: jsonb("metadata").$type(),
14
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
15
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
16
+ }, (table) => [
17
+ index("idx_booking_groups_kind").on(table.kind),
18
+ index("idx_booking_groups_product").on(table.productId),
19
+ index("idx_booking_groups_option_unit").on(table.optionUnitId),
20
+ ]);
21
+ export const bookingGroupMembers = pgTable("booking_group_members", {
22
+ id: typeId("booking_group_members"),
23
+ groupId: typeIdRef("group_id")
24
+ .notNull()
25
+ .references(() => bookingGroups.id, { onDelete: "cascade" }),
26
+ bookingId: typeIdRef("booking_id")
27
+ .notNull()
28
+ .references(() => bookings.id, { onDelete: "cascade" }),
29
+ role: bookingGroupMemberRoleEnum("role").notNull().default("shared"),
30
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
31
+ }, (table) => [
32
+ uniqueIndex("booking_group_members_booking_unique").on(table.bookingId),
33
+ index("idx_booking_group_members_group").on(table.groupId),
34
+ ]);
@@ -9,6 +9,14 @@ export declare const bookingsRelations: import("drizzle-orm").Relations<"booking
9
9
  redemptionEvents: import("drizzle-orm").Many<"booking_redemption_events">;
10
10
  items: import("drizzle-orm").Many<"booking_items">;
11
11
  allocations: import("drizzle-orm").Many<"booking_allocations">;
12
+ groupMemberships: import("drizzle-orm").Many<"booking_group_members">;
13
+ }>;
14
+ export declare const bookingGroupsRelations: import("drizzle-orm").Relations<"booking_groups", {
15
+ members: import("drizzle-orm").Many<"booking_group_members">;
16
+ }>;
17
+ export declare const bookingGroupMembersRelations: import("drizzle-orm").Relations<"booking_group_members", {
18
+ group: import("drizzle-orm").One<"booking_groups", true>;
19
+ booking: import("drizzle-orm").One<"bookings", true>;
12
20
  }>;
13
21
  export declare const bookingParticipantsRelations: import("drizzle-orm").Relations<"booking_participants", {
14
22
  booking: import("drizzle-orm").One<"bookings", true>;
@@ -1 +1 @@
1
- {"version":3,"file":"schema-relations.d.ts","sourceRoot":"","sources":["../src/schema-relations.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,iBAAiB;;;;;;;;;;;EAW3B,CAAA;AAEH,eAAO,MAAM,4BAA4B;;;;;;EAMtC,CAAA;AAEH,eAAO,MAAM,qBAAqB;;;;;;EAM/B,CAAA;AAEH,eAAO,MAAM,2BAA2B;;;;EAUrC,CAAA;AAEH,eAAO,MAAM,gCAAgC;;;EAS1C,CAAA;AAEH,eAAO,MAAM,gCAAgC;;EAK1C,CAAA;AAEH,eAAO,MAAM,4BAA4B;;;;EAUtC,CAAA;AAEH,eAAO,MAAM,gCAAgC;;;;EAa1C,CAAA;AAEH,eAAO,MAAM,2BAA2B;;EAErC,CAAA;AAEH,eAAO,MAAM,6BAA6B;;EAKvC,CAAA;AAEH,eAAO,MAAM,qBAAqB;;EAE/B,CAAA;AAEH,eAAO,MAAM,yBAAyB;;;EAMnC,CAAA"}
1
+ {"version":3,"file":"schema-relations.d.ts","sourceRoot":"","sources":["../src/schema-relations.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;EAY3B,CAAA;AAEH,eAAO,MAAM,sBAAsB;;EAEhC,CAAA;AAEH,eAAO,MAAM,4BAA4B;;;EAStC,CAAA;AAEH,eAAO,MAAM,4BAA4B;;;;;;EAMtC,CAAA;AAEH,eAAO,MAAM,qBAAqB;;;;;;EAM/B,CAAA;AAEH,eAAO,MAAM,2BAA2B;;;;EAUrC,CAAA;AAEH,eAAO,MAAM,gCAAgC;;;EAS1C,CAAA;AAEH,eAAO,MAAM,gCAAgC;;EAK1C,CAAA;AAEH,eAAO,MAAM,4BAA4B;;;;EAUtC,CAAA;AAEH,eAAO,MAAM,gCAAgC;;;;EAa1C,CAAA;AAEH,eAAO,MAAM,2BAA2B;;EAErC,CAAA;AAEH,eAAO,MAAM,6BAA6B;;EAKvC,CAAA;AAEH,eAAO,MAAM,qBAAqB;;EAE/B,CAAA;AAEH,eAAO,MAAM,yBAAyB;;;EAMnC,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { relations } from "drizzle-orm";
2
2
  import { availabilitySlotsRef } from "./availability-ref.js";
3
3
  import { bookingParticipants, bookings } from "./schema-core";
4
+ import { bookingGroupMembers, bookingGroups } from "./schema-groups";
4
5
  import { bookingAllocations, bookingFulfillments, bookingItemParticipants, bookingItems, bookingRedemptionEvents, } from "./schema-items";
5
6
  import { bookingActivityLog, bookingDocuments, bookingNotes, bookingSessionStates, bookingSupplierStatuses, } from "./schema-operations";
6
7
  export const bookingsRelations = relations(bookings, ({ many }) => ({
@@ -14,6 +15,20 @@ export const bookingsRelations = relations(bookings, ({ many }) => ({
14
15
  redemptionEvents: many(bookingRedemptionEvents),
15
16
  items: many(bookingItems),
16
17
  allocations: many(bookingAllocations),
18
+ groupMemberships: many(bookingGroupMembers),
19
+ }));
20
+ export const bookingGroupsRelations = relations(bookingGroups, ({ many }) => ({
21
+ members: many(bookingGroupMembers),
22
+ }));
23
+ export const bookingGroupMembersRelations = relations(bookingGroupMembers, ({ one }) => ({
24
+ group: one(bookingGroups, {
25
+ fields: [bookingGroupMembers.groupId],
26
+ references: [bookingGroups.id],
27
+ }),
28
+ booking: one(bookings, {
29
+ fields: [bookingGroupMembers.bookingId],
30
+ references: [bookings.id],
31
+ }),
17
32
  }));
18
33
  export const bookingParticipantsRelations = relations(bookingParticipants, ({ one, many }) => ({
19
34
  booking: one(bookings, { fields: [bookingParticipants.bookingId], references: [bookings.id] }),
package/dist/schema.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./schema-core";
2
+ export * from "./schema-groups";
2
3
  export * from "./schema-items";
3
4
  export * from "./schema-operations";
4
5
  export * from "./schema-relations";
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,iBAAiB,CAAA"}
package/dist/schema.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./schema-core";
2
+ export * from "./schema-groups";
2
3
  export * from "./schema-items";
3
4
  export * from "./schema-operations";
4
5
  export * from "./schema-relations";
@@ -0,0 +1,69 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { z } from "zod";
3
+ import { type Booking, type BookingPassenger } from "./schema-core.js";
4
+ import { type BookingGroup, type BookingGroupMember } from "./schema-groups.js";
5
+ import type { addBookingGroupMemberSchema, bookingGroupListQuerySchema, insertBookingGroupSchema, updateBookingGroupSchema } from "./validation.js";
6
+ export type BookingGroupListQuery = z.infer<typeof bookingGroupListQuerySchema>;
7
+ export type CreateBookingGroupInput = z.infer<typeof insertBookingGroupSchema>;
8
+ export type UpdateBookingGroupInput = z.infer<typeof updateBookingGroupSchema>;
9
+ export type AddBookingGroupMemberInput = z.infer<typeof addBookingGroupMemberSchema>;
10
+ export type BookingGroupMemberWithBooking = BookingGroupMember & {
11
+ booking: Booking | null;
12
+ };
13
+ export declare function listBookingGroups(db: PostgresJsDatabase, query: BookingGroupListQuery): Promise<{
14
+ data: BookingGroup[];
15
+ total: number;
16
+ limit: number;
17
+ offset: number;
18
+ }>;
19
+ export declare function getBookingGroupById(db: PostgresJsDatabase, id: string): Promise<BookingGroup | null>;
20
+ export declare function createBookingGroup(db: PostgresJsDatabase, data: CreateBookingGroupInput): Promise<BookingGroup>;
21
+ export declare function updateBookingGroup(db: PostgresJsDatabase, id: string, data: UpdateBookingGroupInput): Promise<BookingGroup | null>;
22
+ export declare function deleteBookingGroup(db: PostgresJsDatabase, id: string): Promise<{
23
+ id: string;
24
+ } | null>;
25
+ export declare function listGroupMembers(db: PostgresJsDatabase, groupId: string): Promise<BookingGroupMemberWithBooking[]>;
26
+ export declare function getBookingGroupForBooking(db: PostgresJsDatabase, bookingId: string): Promise<(BookingGroup & {
27
+ membership: BookingGroupMember;
28
+ }) | null>;
29
+ export declare function addGroupMember(db: PostgresJsDatabase, groupId: string, data: AddBookingGroupMemberInput): Promise<{
30
+ status: "ok";
31
+ member: BookingGroupMember;
32
+ } | {
33
+ status: "group_not_found";
34
+ } | {
35
+ status: "booking_not_found";
36
+ } | {
37
+ status: "already_in_group";
38
+ currentGroupId: string;
39
+ }>;
40
+ export declare function removeGroupMember(db: PostgresJsDatabase, groupId: string, bookingId: string): Promise<{
41
+ id: string;
42
+ } | null>;
43
+ /**
44
+ * Dissolve or slim down a booking group after a member is cancelled.
45
+ *
46
+ * - 3+ active members remain: no-op (group stays intact)
47
+ * - 1 active member remains: dissolve (delete last member + group)
48
+ * - 0 active members remain: delete the group
49
+ */
50
+ export declare function cleanupGroupOnBookingCancelled(db: PostgresJsDatabase, bookingId: string): Promise<void>;
51
+ /**
52
+ * Returns all passengers across every member booking in a group.
53
+ * Used by rooming-list exports to show shared-room occupants as one unit.
54
+ */
55
+ export declare function listGroupBookingPassengers(db: PostgresJsDatabase, groupId: string): Promise<BookingPassenger[]>;
56
+ export declare const bookingGroupsService: {
57
+ listBookingGroups: typeof listBookingGroups;
58
+ getBookingGroupById: typeof getBookingGroupById;
59
+ createBookingGroup: typeof createBookingGroup;
60
+ updateBookingGroup: typeof updateBookingGroup;
61
+ deleteBookingGroup: typeof deleteBookingGroup;
62
+ listGroupMembers: typeof listGroupMembers;
63
+ getBookingGroupForBooking: typeof getBookingGroupForBooking;
64
+ addGroupMember: typeof addGroupMember;
65
+ removeGroupMember: typeof removeGroupMember;
66
+ cleanupGroupOnBookingCancelled: typeof cleanupGroupOnBookingCancelled;
67
+ listGroupBookingPassengers: typeof listGroupBookingPassengers;
68
+ };
69
+ //# sourceMappingURL=service-groups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-groups.d.ts","sourceRoot":"","sources":["../src/service-groups.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,199 @@
1
+ import { and, asc, eq, inArray, sql } from "drizzle-orm";
2
+ import { bookingPassengers, bookings } from "./schema-core.js";
3
+ import { bookingGroupMembers, bookingGroups, } from "./schema-groups.js";
4
+ export async function listBookingGroups(db, query) {
5
+ const conditions = [];
6
+ if (query.kind)
7
+ conditions.push(eq(bookingGroups.kind, query.kind));
8
+ if (query.productId)
9
+ conditions.push(eq(bookingGroups.productId, query.productId));
10
+ if (query.optionUnitId)
11
+ conditions.push(eq(bookingGroups.optionUnitId, query.optionUnitId));
12
+ const where = conditions.length ? and(...conditions) : undefined;
13
+ const [rows, countResult] = await Promise.all([
14
+ db
15
+ .select()
16
+ .from(bookingGroups)
17
+ .where(where)
18
+ .limit(query.limit)
19
+ .offset(query.offset)
20
+ .orderBy(asc(bookingGroups.createdAt)),
21
+ db.select({ count: sql `count(*)::int` }).from(bookingGroups).where(where),
22
+ ]);
23
+ return {
24
+ data: rows,
25
+ total: countResult[0]?.count ?? 0,
26
+ limit: query.limit,
27
+ offset: query.offset,
28
+ };
29
+ }
30
+ export async function getBookingGroupById(db, id) {
31
+ const [row] = await db.select().from(bookingGroups).where(eq(bookingGroups.id, id)).limit(1);
32
+ return row ?? null;
33
+ }
34
+ export async function createBookingGroup(db, data) {
35
+ const [row] = await db.insert(bookingGroups).values(data).returning();
36
+ if (!row)
37
+ throw new Error("Failed to create booking group");
38
+ return row;
39
+ }
40
+ export async function updateBookingGroup(db, id, data) {
41
+ const [row] = await db
42
+ .update(bookingGroups)
43
+ .set({ ...data, updatedAt: new Date() })
44
+ .where(eq(bookingGroups.id, id))
45
+ .returning();
46
+ return row ?? null;
47
+ }
48
+ export async function deleteBookingGroup(db, id) {
49
+ const [row] = await db
50
+ .delete(bookingGroups)
51
+ .where(eq(bookingGroups.id, id))
52
+ .returning({ id: bookingGroups.id });
53
+ return row ?? null;
54
+ }
55
+ export async function listGroupMembers(db, groupId) {
56
+ const rows = await db
57
+ .select()
58
+ .from(bookingGroupMembers)
59
+ .where(eq(bookingGroupMembers.groupId, groupId))
60
+ .orderBy(asc(bookingGroupMembers.createdAt));
61
+ if (rows.length === 0)
62
+ return [];
63
+ const bookingIds = rows.map((r) => r.bookingId);
64
+ const bookingRows = await db.select().from(bookings).where(inArray(bookings.id, bookingIds));
65
+ const bookingMap = new Map(bookingRows.map((b) => [b.id, b]));
66
+ return rows.map((member) => ({
67
+ ...member,
68
+ booking: bookingMap.get(member.bookingId) ?? null,
69
+ }));
70
+ }
71
+ export async function getBookingGroupForBooking(db, bookingId) {
72
+ const [membership] = await db
73
+ .select()
74
+ .from(bookingGroupMembers)
75
+ .where(eq(bookingGroupMembers.bookingId, bookingId))
76
+ .limit(1);
77
+ if (!membership)
78
+ return null;
79
+ const [group] = await db
80
+ .select()
81
+ .from(bookingGroups)
82
+ .where(eq(bookingGroups.id, membership.groupId))
83
+ .limit(1);
84
+ if (!group)
85
+ return null;
86
+ return { ...group, membership };
87
+ }
88
+ export async function addGroupMember(db, groupId, data) {
89
+ const [group] = await db
90
+ .select({ id: bookingGroups.id })
91
+ .from(bookingGroups)
92
+ .where(eq(bookingGroups.id, groupId))
93
+ .limit(1);
94
+ if (!group)
95
+ return { status: "group_not_found" };
96
+ const [booking] = await db
97
+ .select({ id: bookings.id })
98
+ .from(bookings)
99
+ .where(eq(bookings.id, data.bookingId))
100
+ .limit(1);
101
+ if (!booking)
102
+ return { status: "booking_not_found" };
103
+ const [existing] = await db
104
+ .select({ groupId: bookingGroupMembers.groupId })
105
+ .from(bookingGroupMembers)
106
+ .where(eq(bookingGroupMembers.bookingId, data.bookingId))
107
+ .limit(1);
108
+ if (existing) {
109
+ return { status: "already_in_group", currentGroupId: existing.groupId };
110
+ }
111
+ const [member] = await db
112
+ .insert(bookingGroupMembers)
113
+ .values({
114
+ groupId,
115
+ bookingId: data.bookingId,
116
+ role: data.role,
117
+ })
118
+ .returning();
119
+ if (!member)
120
+ throw new Error("Failed to add group member");
121
+ return { status: "ok", member };
122
+ }
123
+ export async function removeGroupMember(db, groupId, bookingId) {
124
+ const [row] = await db
125
+ .delete(bookingGroupMembers)
126
+ .where(and(eq(bookingGroupMembers.groupId, groupId), eq(bookingGroupMembers.bookingId, bookingId)))
127
+ .returning({ id: bookingGroupMembers.id });
128
+ return row ?? null;
129
+ }
130
+ /**
131
+ * Dissolve or slim down a booking group after a member is cancelled.
132
+ *
133
+ * - 3+ active members remain: no-op (group stays intact)
134
+ * - 1 active member remains: dissolve (delete last member + group)
135
+ * - 0 active members remain: delete the group
136
+ */
137
+ export async function cleanupGroupOnBookingCancelled(db, bookingId) {
138
+ const [membership] = await db
139
+ .select()
140
+ .from(bookingGroupMembers)
141
+ .where(eq(bookingGroupMembers.bookingId, bookingId))
142
+ .limit(1);
143
+ if (!membership)
144
+ return;
145
+ // Remove the cancelled booking's membership
146
+ await db.delete(bookingGroupMembers).where(eq(bookingGroupMembers.id, membership.id));
147
+ // Count remaining members with an active (not-cancelled) booking
148
+ const remaining = await db
149
+ .select({
150
+ memberId: bookingGroupMembers.id,
151
+ bookingId: bookingGroupMembers.bookingId,
152
+ status: bookings.status,
153
+ })
154
+ .from(bookingGroupMembers)
155
+ .innerJoin(bookings, eq(bookings.id, bookingGroupMembers.bookingId))
156
+ .where(eq(bookingGroupMembers.groupId, membership.groupId));
157
+ const active = remaining.filter((r) => r.status !== "cancelled");
158
+ if (active.length === 0) {
159
+ // No active members — delete the group (cascades any non-active members)
160
+ await db.delete(bookingGroups).where(eq(bookingGroups.id, membership.groupId));
161
+ return;
162
+ }
163
+ if (active.length === 1) {
164
+ // Only one active member left — dissolve the group
165
+ await db.delete(bookingGroupMembers).where(eq(bookingGroupMembers.groupId, membership.groupId));
166
+ await db.delete(bookingGroups).where(eq(bookingGroups.id, membership.groupId));
167
+ }
168
+ }
169
+ /**
170
+ * Returns all passengers across every member booking in a group.
171
+ * Used by rooming-list exports to show shared-room occupants as one unit.
172
+ */
173
+ export async function listGroupBookingPassengers(db, groupId) {
174
+ const members = await db
175
+ .select({ bookingId: bookingGroupMembers.bookingId })
176
+ .from(bookingGroupMembers)
177
+ .where(eq(bookingGroupMembers.groupId, groupId));
178
+ if (members.length === 0)
179
+ return [];
180
+ const bookingIds = members.map((m) => m.bookingId);
181
+ return db
182
+ .select()
183
+ .from(bookingPassengers)
184
+ .where(inArray(bookingPassengers.bookingId, bookingIds))
185
+ .orderBy(asc(bookingPassengers.createdAt));
186
+ }
187
+ export const bookingGroupsService = {
188
+ listBookingGroups,
189
+ getBookingGroupById,
190
+ createBookingGroup,
191
+ updateBookingGroup,
192
+ deleteBookingGroup,
193
+ listGroupMembers,
194
+ getBookingGroupForBooking,
195
+ addGroupMember,
196
+ removeGroupMember,
197
+ cleanupGroupOnBookingCancelled,
198
+ listGroupBookingPassengers,
199
+ };
package/dist/service.d.ts CHANGED
@@ -2620,6 +2620,7 @@ export declare const bookingsService: {
2620
2620
  metadata: Record<string, unknown> | null;
2621
2621
  sellCurrency: string;
2622
2622
  bookingId: string;
2623
+ optionUnitId: string | null;
2623
2624
  title: string;
2624
2625
  description: string | null;
2625
2626
  itemType: "other" | "unit" | "extra" | "service" | "fee" | "tax" | "discount" | "adjustment" | "accommodation" | "transport";
@@ -2630,7 +2631,6 @@ export declare const bookingsService: {
2630
2631
  costCurrency: string | null;
2631
2632
  unitCostAmountCents: number | null;
2632
2633
  totalCostAmountCents: number | null;
2633
- optionUnitId: string | null;
2634
2634
  pricingCategoryId: string | null;
2635
2635
  sourceSnapshotId: string | null;
2636
2636
  sourceOfferId: string | null;