@voyantjs/bookings 0.9.0 → 0.11.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.
Files changed (51) hide show
  1. package/dist/index.d.ts +3 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/markets-ref.d.ts +151 -0
  5. package/dist/markets-ref.d.ts.map +1 -0
  6. package/dist/markets-ref.js +19 -0
  7. package/dist/pii-redaction.d.ts +89 -0
  8. package/dist/pii-redaction.d.ts.map +1 -0
  9. package/dist/pii-redaction.js +120 -0
  10. package/dist/pii.d.ts +1 -0
  11. package/dist/pii.d.ts.map +1 -1
  12. package/dist/pii.js +20 -1
  13. package/dist/routes-groups.d.ts +3 -2
  14. package/dist/routes-groups.d.ts.map +1 -1
  15. package/dist/routes-public.d.ts +11 -13
  16. package/dist/routes-public.d.ts.map +1 -1
  17. package/dist/routes-public.js +3 -3
  18. package/dist/routes.d.ts +232 -22
  19. package/dist/routes.d.ts.map +1 -1
  20. package/dist/routes.js +100 -24
  21. package/dist/schema/travel-details.d.ts +37 -0
  22. package/dist/schema/travel-details.d.ts.map +1 -1
  23. package/dist/schema/travel-details.js +6 -0
  24. package/dist/schema-core.d.ts +17 -17
  25. package/dist/schema-core.d.ts.map +1 -1
  26. package/dist/schema-core.js +8 -2
  27. package/dist/schema-items.d.ts.map +1 -1
  28. package/dist/schema-items.js +6 -1
  29. package/dist/schema-operations.d.ts +2 -2
  30. package/dist/schema-shared.d.ts +1 -1
  31. package/dist/schema-shared.d.ts.map +1 -1
  32. package/dist/schema-shared.js +3 -0
  33. package/dist/service-public.d.ts +0 -6
  34. package/dist/service-public.d.ts.map +1 -1
  35. package/dist/service-public.js +0 -4
  36. package/dist/service.d.ts +232 -56
  37. package/dist/service.d.ts.map +1 -1
  38. package/dist/service.js +469 -137
  39. package/dist/state-machine.d.ts +29 -0
  40. package/dist/state-machine.d.ts.map +1 -0
  41. package/dist/state-machine.js +39 -0
  42. package/dist/validation-public.d.ts +0 -6
  43. package/dist/validation-public.d.ts.map +1 -1
  44. package/dist/validation-public.js +0 -2
  45. package/dist/validation.d.ts +25 -16
  46. package/dist/validation.d.ts.map +1 -1
  47. package/dist/validation.js +17 -6
  48. package/dist/workflows/refund-booking.d.ts +87 -0
  49. package/dist/workflows/refund-booking.d.ts.map +1 -0
  50. package/dist/workflows/refund-booking.js +210 -0
  51. package/package.json +7 -6
@@ -0,0 +1,29 @@
1
+ import type { bookings } from "./schema-core.js";
2
+ export type BookingStatus = (typeof bookings.$inferSelect)["status"];
3
+ export declare const BOOKING_TRANSITIONS: {
4
+ readonly draft: readonly ["on_hold", "confirmed", "cancelled"];
5
+ readonly on_hold: readonly ["confirmed", "expired", "cancelled"];
6
+ readonly confirmed: readonly ["in_progress", "cancelled"];
7
+ readonly in_progress: readonly ["completed", "cancelled"];
8
+ readonly completed: readonly [];
9
+ readonly expired: readonly [];
10
+ readonly cancelled: readonly [];
11
+ };
12
+ export declare class BookingTransitionError extends Error {
13
+ readonly from: BookingStatus;
14
+ readonly to: BookingStatus;
15
+ readonly code = "INVALID_BOOKING_TRANSITION";
16
+ constructor(from: BookingStatus, to: BookingStatus);
17
+ }
18
+ export declare function canTransitionBooking(from: BookingStatus, to: BookingStatus): boolean;
19
+ export interface BookingStatusPatch {
20
+ status: BookingStatus;
21
+ confirmedAt?: Date;
22
+ expiredAt?: Date;
23
+ cancelledAt?: Date;
24
+ completedAt?: Date;
25
+ }
26
+ export declare function transitionBooking(from: BookingStatus, to: BookingStatus, opts?: {
27
+ now?: Date;
28
+ }): BookingStatusPatch;
29
+ //# sourceMappingURL=state-machine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../src/state-machine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAEhD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,QAAQ,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,CAAA;AAEpE,eAAO,MAAM,mBAAmB;;;;;;;;CAQoC,CAAA;AAEpE,qBAAa,sBAAuB,SAAQ,KAAK;IAI7C,QAAQ,CAAC,IAAI,EAAE,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,aAAa;IAJ5B,QAAQ,CAAC,IAAI,gCAA+B;gBAGjC,IAAI,EAAE,aAAa,EACnB,EAAE,EAAE,aAAa;CAK7B;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,EAAE,aAAa,GAAG,OAAO,CAEpF;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,aAAa,CAAA;IACrB,WAAW,CAAC,EAAE,IAAI,CAAA;IAClB,SAAS,CAAC,EAAE,IAAI,CAAA;IAChB,WAAW,CAAC,EAAE,IAAI,CAAA;IAClB,WAAW,CAAC,EAAE,IAAI,CAAA;CACnB;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,aAAa,EACnB,EAAE,EAAE,aAAa,EACjB,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,IAAI,CAAA;CAAO,GACxB,kBAAkB,CAcpB"}
@@ -0,0 +1,39 @@
1
+ export const BOOKING_TRANSITIONS = {
2
+ draft: ["on_hold", "confirmed", "cancelled"],
3
+ on_hold: ["confirmed", "expired", "cancelled"],
4
+ confirmed: ["in_progress", "cancelled"],
5
+ in_progress: ["completed", "cancelled"],
6
+ completed: [],
7
+ expired: [],
8
+ cancelled: [],
9
+ };
10
+ export class BookingTransitionError extends Error {
11
+ from;
12
+ to;
13
+ code = "INVALID_BOOKING_TRANSITION";
14
+ constructor(from, to) {
15
+ super(`Illegal booking status transition: ${from} → ${to}`);
16
+ this.from = from;
17
+ this.to = to;
18
+ this.name = "BookingTransitionError";
19
+ }
20
+ }
21
+ export function canTransitionBooking(from, to) {
22
+ return BOOKING_TRANSITIONS[from].includes(to);
23
+ }
24
+ export function transitionBooking(from, to, opts = {}) {
25
+ if (!canTransitionBooking(from, to)) {
26
+ throw new BookingTransitionError(from, to);
27
+ }
28
+ const now = opts.now ?? new Date();
29
+ const patch = { status: to };
30
+ if (to === "confirmed")
31
+ patch.confirmedAt = now;
32
+ if (to === "expired")
33
+ patch.expiredAt = now;
34
+ if (to === "cancelled")
35
+ patch.cancelledAt = now;
36
+ if (to === "completed")
37
+ patch.completedAt = now;
38
+ return patch;
39
+ }
@@ -17,7 +17,6 @@ export declare const publicBookingSessionTravelerInputSchema: z.ZodObject<{
17
17
  email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
18
18
  phone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
19
19
  preferredLanguage: z.ZodOptional<z.ZodNullable<z.ZodString>>;
20
- accessibilityNeeds: z.ZodOptional<z.ZodNullable<z.ZodString>>;
21
20
  specialRequests: z.ZodOptional<z.ZodNullable<z.ZodString>>;
22
21
  isPrimary: z.ZodDefault<z.ZodBoolean>;
23
22
  notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -127,7 +126,6 @@ export declare const publicCreateBookingSessionSchema: z.ZodObject<{
127
126
  email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
128
127
  phone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
129
128
  preferredLanguage: z.ZodOptional<z.ZodNullable<z.ZodString>>;
130
- accessibilityNeeds: z.ZodOptional<z.ZodNullable<z.ZodString>>;
131
129
  specialRequests: z.ZodOptional<z.ZodNullable<z.ZodString>>;
132
130
  isPrimary: z.ZodDefault<z.ZodBoolean>;
133
131
  notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -157,7 +155,6 @@ export declare const publicUpdateBookingSessionSchema: z.ZodObject<{
157
155
  email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
158
156
  phone: z.ZodOptional<z.ZodNullable<z.ZodString>>;
159
157
  preferredLanguage: z.ZodOptional<z.ZodNullable<z.ZodString>>;
160
- accessibilityNeeds: z.ZodOptional<z.ZodNullable<z.ZodString>>;
161
158
  specialRequests: z.ZodOptional<z.ZodNullable<z.ZodString>>;
162
159
  isPrimary: z.ZodDefault<z.ZodBoolean>;
163
160
  notes: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -230,7 +227,6 @@ export declare const publicBookingSessionTravelerSchema: z.ZodObject<{
230
227
  email: z.ZodNullable<z.ZodString>;
231
228
  phone: z.ZodNullable<z.ZodString>;
232
229
  preferredLanguage: z.ZodNullable<z.ZodString>;
233
- accessibilityNeeds: z.ZodNullable<z.ZodString>;
234
230
  specialRequests: z.ZodNullable<z.ZodString>;
235
231
  isPrimary: z.ZodBoolean;
236
232
  notes: z.ZodNullable<z.ZodString>;
@@ -373,7 +369,6 @@ export declare const publicBookingSessionSchema: z.ZodObject<{
373
369
  email: z.ZodNullable<z.ZodString>;
374
370
  phone: z.ZodNullable<z.ZodString>;
375
371
  preferredLanguage: z.ZodNullable<z.ZodString>;
376
- accessibilityNeeds: z.ZodNullable<z.ZodString>;
377
372
  specialRequests: z.ZodNullable<z.ZodString>;
378
373
  isPrimary: z.ZodBoolean;
379
374
  notes: z.ZodNullable<z.ZodString>;
@@ -578,7 +573,6 @@ export declare const publicBookingSessionRepriceResultSchema: z.ZodObject<{
578
573
  email: z.ZodNullable<z.ZodString>;
579
574
  phone: z.ZodNullable<z.ZodString>;
580
575
  preferredLanguage: z.ZodNullable<z.ZodString>;
581
- accessibilityNeeds: z.ZodNullable<z.ZodString>;
582
576
  specialRequests: z.ZodNullable<z.ZodString>;
583
577
  isPrimary: z.ZodBoolean;
584
578
  notes: z.ZodNullable<z.ZodString>;
@@ -1 +1 @@
1
- {"version":3,"file":"validation-public.d.ts","sourceRoot":"","sources":["../src/validation-public.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validation-public.d.ts","sourceRoot":"","sources":["../src/validation-public.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,6 @@ export const publicBookingSessionTravelerInputSchema = z.object({
13
13
  email: z.string().email().nullable().optional(),
14
14
  phone: z.string().max(50).nullable().optional(),
15
15
  preferredLanguage: z.string().max(35).nullable().optional(),
16
- accessibilityNeeds: z.string().nullable().optional(),
17
16
  specialRequests: z.string().nullable().optional(),
18
17
  isPrimary: z.boolean().default(false),
19
18
  notes: z.string().nullable().optional(),
@@ -130,7 +129,6 @@ export const publicBookingSessionTravelerSchema = z.object({
130
129
  email: z.string().nullable(),
131
130
  phone: z.string().nullable(),
132
131
  preferredLanguage: z.string().nullable(),
133
- accessibilityNeeds: z.string().nullable(),
134
132
  specialRequests: z.string().nullable(),
135
133
  isPrimary: z.boolean(),
136
134
  notes: z.string().nullable(),
@@ -191,18 +191,6 @@ export declare const pricingPreviewSchema: z.ZodObject<{
191
191
  optionId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
192
192
  catalogId: z.ZodNullable<z.ZodOptional<z.ZodString>>;
193
193
  }, z.core.$strip>;
194
- export declare const updateBookingStatusSchema: z.ZodObject<{
195
- status: z.ZodEnum<{
196
- cancelled: "cancelled";
197
- draft: "draft";
198
- on_hold: "on_hold";
199
- confirmed: "confirmed";
200
- in_progress: "in_progress";
201
- completed: "completed";
202
- expired: "expired";
203
- }>;
204
- note: z.ZodNullable<z.ZodOptional<z.ZodString>>;
205
- }, z.core.$strip>;
206
194
  export declare const reserveBookingItemSchema: z.ZodObject<{
207
195
  title: z.ZodString;
208
196
  description: z.ZodNullable<z.ZodOptional<z.ZodString>>;
@@ -333,6 +321,31 @@ export declare const expireStaleBookingsSchema: z.ZodObject<{
333
321
  before: z.ZodNullable<z.ZodOptional<z.ZodString>>;
334
322
  note: z.ZodNullable<z.ZodOptional<z.ZodString>>;
335
323
  }, z.core.$strip>;
324
+ export declare const startBookingSchema: z.ZodObject<{
325
+ note: z.ZodNullable<z.ZodOptional<z.ZodString>>;
326
+ }, z.core.$strip>;
327
+ export declare const completeBookingSchema: z.ZodObject<{
328
+ note: z.ZodNullable<z.ZodOptional<z.ZodString>>;
329
+ }, z.core.$strip>;
330
+ /**
331
+ * Admin-only override: skips the transition graph. `reason` is required —
332
+ * the operator has to explain why they're bypassing lifecycle laws. Use the
333
+ * verb-specific endpoints (/confirm, /cancel, /start, /complete, /expire) for
334
+ * normal state changes; this is for data-correction and exceptional cases.
335
+ */
336
+ export declare const overrideBookingStatusSchema: z.ZodObject<{
337
+ status: z.ZodEnum<{
338
+ cancelled: "cancelled";
339
+ draft: "draft";
340
+ on_hold: "on_hold";
341
+ confirmed: "confirmed";
342
+ in_progress: "in_progress";
343
+ completed: "completed";
344
+ expired: "expired";
345
+ }>;
346
+ reason: z.ZodString;
347
+ note: z.ZodNullable<z.ZodOptional<z.ZodString>>;
348
+ }, z.core.$strip>;
336
349
  export declare const reserveBookingFromTransactionSchema: z.ZodObject<{
337
350
  bookingNumber: z.ZodString;
338
351
  contactFirstName: z.ZodNullable<z.ZodOptional<z.ZodString>>;
@@ -366,7 +379,6 @@ export declare const insertTravelerSchema: z.ZodObject<{
366
379
  email: z.ZodNullable<z.ZodOptional<z.ZodString>>;
367
380
  phone: z.ZodNullable<z.ZodOptional<z.ZodString>>;
368
381
  preferredLanguage: z.ZodNullable<z.ZodOptional<z.ZodString>>;
369
- accessibilityNeeds: z.ZodNullable<z.ZodOptional<z.ZodString>>;
370
382
  specialRequests: z.ZodNullable<z.ZodOptional<z.ZodString>>;
371
383
  travelerCategory: z.ZodNullable<z.ZodOptional<z.ZodEnum<{
372
384
  other: "other";
@@ -384,7 +396,6 @@ export declare const updateTravelerSchema: z.ZodObject<{
384
396
  email: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
385
397
  phone: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
386
398
  preferredLanguage: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
387
- accessibilityNeeds: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
388
399
  specialRequests: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
389
400
  travelerCategory: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodEnum<{
390
401
  other: "other";
@@ -415,7 +426,6 @@ export declare const insertTravelerRecordSchema: z.ZodObject<{
415
426
  email: z.ZodNullable<z.ZodOptional<z.ZodString>>;
416
427
  phone: z.ZodNullable<z.ZodOptional<z.ZodString>>;
417
428
  preferredLanguage: z.ZodNullable<z.ZodOptional<z.ZodString>>;
418
- accessibilityNeeds: z.ZodNullable<z.ZodOptional<z.ZodString>>;
419
429
  specialRequests: z.ZodNullable<z.ZodOptional<z.ZodString>>;
420
430
  isPrimary: z.ZodDefault<z.ZodBoolean>;
421
431
  notes: z.ZodNullable<z.ZodOptional<z.ZodString>>;
@@ -439,7 +449,6 @@ export declare const updateTravelerRecordSchema: z.ZodObject<{
439
449
  email: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
440
450
  phone: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
441
451
  preferredLanguage: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
442
- accessibilityNeeds: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
443
452
  specialRequests: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
444
453
  isPrimary: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
445
454
  notes: z.ZodOptional<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA2DvB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAoB,CAAA;AACpD,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAA8B,CAAA;AAE9D,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAW5B,CAAA;AAEJ,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;iBASjC,CAAA;AAEF,eAAO,MAAM,4BAA4B;;;iBAGvC,CAAA;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;iBAQ/B,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;iBAI/B,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;;;;;;;;;iBAGpC,CAAA;AAEF,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqBnC,CAAA;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmB7B,CAAA;AAEJ,eAAO,MAAM,uBAAuB;;;iBAYhC,CAAA;AAEJ,eAAO,MAAM,oBAAoB;;iBAE/B,CAAA;AAEF,eAAO,MAAM,mBAAmB;;iBAE9B,CAAA;AAEF,eAAO,MAAM,mBAAmB;;iBAE9B,CAAA;AAEF,eAAO,MAAM,yBAAyB;;;iBAGpC,CAAA;AAEF,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2B5C,CAAA;AAkCJ,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;iBAAqB,CAAA;AACtD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;iBAA+B,CAAA;AAChE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;iBAA2B,CAAA;AAClE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;iBAAqC,CAAA;AAI5E,eAAO,MAAM,iCAAiC;;;;;;;iBAO5C,CAAA;AA6BF,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAwB,CAAA;AAC5D,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAkC,CAAA;AAEtE,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;iBAcxC,CAAA;AAEF,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;iBAA0C,CAAA;AAgBpF,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAK1C,CAAA;AAED,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAKtC,CAAA;AAIL,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAarC,CAAA;AAIL,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;GAavC,CAAA;AAEL,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;;;GAAkC,CAAA;AAcjF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;iBAA2B,CAAA;AAClE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;iBAErC,CAAA;AAIF,eAAO,MAAM,uBAAuB;;iBAElC,CAAA;AAIF,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;GAYnC,CAAA;AAEL,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;GAA8B,CAAA;AAI9E,eAAO,MAAM,sBAAsB;;;EAAmC,CAAA;AACtE,eAAO,MAAM,4BAA4B;;;EAAgC,CAAA;AAWzE,eAAO,MAAM,wBAAwB;;;;;;;;;;iBAAyB,CAAA;AAC9D,eAAO,MAAM,wBAAwB;;;;;;;;;;iBAAmC,CAAA;AAExE,eAAO,MAAM,2BAA2B;;;;;;iBAGtC,CAAA;AAEF,eAAO,MAAM,2BAA2B;;;;;;;;;iBAMtC,CAAA;AAEF,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
@@ -84,10 +84,6 @@ export const pricingPreviewSchema = z.object({
84
84
  optionId: z.string().optional().nullable(),
85
85
  catalogId: z.string().optional().nullable(),
86
86
  });
87
- export const updateBookingStatusSchema = z.object({
88
- status: bookingStatusSchema,
89
- note: z.string().optional().nullable(),
90
- });
91
87
  export const reserveBookingItemSchema = z.object({
92
88
  title: z.string().min(1).max(255),
93
89
  description: z.string().optional().nullable(),
@@ -156,6 +152,23 @@ export const expireStaleBookingsSchema = z.object({
156
152
  before: z.string().datetime().optional().nullable(),
157
153
  note: z.string().optional().nullable(),
158
154
  });
155
+ export const startBookingSchema = z.object({
156
+ note: z.string().optional().nullable(),
157
+ });
158
+ export const completeBookingSchema = z.object({
159
+ note: z.string().optional().nullable(),
160
+ });
161
+ /**
162
+ * Admin-only override: skips the transition graph. `reason` is required —
163
+ * the operator has to explain why they're bypassing lifecycle laws. Use the
164
+ * verb-specific endpoints (/confirm, /cancel, /start, /complete, /expire) for
165
+ * normal state changes; this is for data-correction and exceptional cases.
166
+ */
167
+ export const overrideBookingStatusSchema = z.object({
168
+ status: bookingStatusSchema,
169
+ reason: z.string().min(1).max(2000),
170
+ note: z.string().optional().nullable(),
171
+ });
159
172
  export const reserveBookingFromTransactionSchema = bookingCoreSchema
160
173
  .pick({
161
174
  bookingNumber: true,
@@ -194,7 +207,6 @@ const travelerRecordCoreSchema = z.object({
194
207
  email: z.string().email().optional().nullable(),
195
208
  phone: z.string().max(50).optional().nullable(),
196
209
  preferredLanguage: z.string().max(35).optional().nullable(),
197
- accessibilityNeeds: z.string().optional().nullable(),
198
210
  specialRequests: z.string().optional().nullable(),
199
211
  isPrimary: z.boolean().default(false),
200
212
  notes: z.string().optional().nullable(),
@@ -206,7 +218,6 @@ const travelerCoreSchema = z.object({
206
218
  email: z.string().email().optional().nullable(),
207
219
  phone: z.string().max(50).optional().nullable(),
208
220
  preferredLanguage: z.string().max(35).optional().nullable(),
209
- accessibilityNeeds: z.string().optional().nullable(),
210
221
  specialRequests: z.string().optional().nullable(),
211
222
  travelerCategory: bookingTravelerCategorySchema.optional().nullable(),
212
223
  isPrimary: z.boolean().optional().nullable(),
@@ -0,0 +1,87 @@
1
+ import { type EventBus } from "@voyantjs/core";
2
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
+ /**
4
+ * Input passed when starting a refund.
5
+ */
6
+ export interface RefundBookingInput {
7
+ bookingId: string;
8
+ /** Free-form audit reason. Required for ops + customer comms. */
9
+ reason: string;
10
+ /**
11
+ * Refund amount in cents. Pass `null` to refund the booking's full
12
+ * `sellAmountCents`; pass a smaller value for a partial refund.
13
+ */
14
+ amountCents?: number | null;
15
+ /** User triggering the refund (for audit). */
16
+ userId?: string;
17
+ }
18
+ /**
19
+ * Side-effect dependencies — supplied by the caller. Decouples the saga
20
+ * from finance + transactions + notifications packages so this can ship
21
+ * without those modules being imported.
22
+ *
23
+ * - `createCreditNote` is expected to be transactional internally and to
24
+ * return a credit note id. Pass a no-op when there's no payment to refund.
25
+ * - `voidCreditNote` is the compensation; it should mark the credit note
26
+ * void (or delete it) so a retry of the saga doesn't double-credit.
27
+ * - `reverseSupplierOffer` updates linked supplier offer/order rows. May
28
+ * be a no-op if no transaction link exists.
29
+ * - `notifyCustomer` is fire-and-forget; failures don't trigger
30
+ * compensation (notifications fail closed elsewhere).
31
+ */
32
+ export interface RefundBookingDeps {
33
+ db: PostgresJsDatabase;
34
+ eventBus?: EventBus;
35
+ createCreditNote: (args: {
36
+ bookingId: string;
37
+ amountCents: number;
38
+ reason: string;
39
+ }) => Promise<{
40
+ creditNoteId: string;
41
+ } | null>;
42
+ voidCreditNote?: (args: {
43
+ creditNoteId: string;
44
+ reason: string;
45
+ }) => Promise<void>;
46
+ reverseSupplierOffer?: (args: {
47
+ bookingId: string;
48
+ reason: string;
49
+ }) => Promise<void>;
50
+ notifyCustomer?: (args: {
51
+ bookingId: string;
52
+ reason: string;
53
+ amountCents: number;
54
+ }) => Promise<void>;
55
+ }
56
+ /**
57
+ * Build the refund saga for a booking.
58
+ *
59
+ * **Steps**
60
+ *
61
+ * 1. `validate-state` — load the booking, ensure it's in a refundable
62
+ * status (`confirmed`, `in_progress`, or `on_hold`), compute the refund
63
+ * amount. Compensation: none — this step doesn't mutate.
64
+ * 2. `create-credit-note` — call into finance via the injected dep.
65
+ * Compensation: void the credit note.
66
+ * 3. `release-inventory` — release any held allocations + the slot
67
+ * capacity (only when the booking hasn't started yet). Compensation:
68
+ * re-acquire (best-effort; if inventory has since been re-sold,
69
+ * that's a known limitation logged for ops triage).
70
+ * 4. `reverse-supplier-offer` — best-effort call into transactions.
71
+ * Compensation: none (idempotent re-call expected if needed).
72
+ * 5. `transition-booking` — flip the booking to `cancelled`. Last
73
+ * DB-touching step so a failure here doesn't leave the booking
74
+ * cancelled with no credit-note rollback.
75
+ * 6. `emit` — fire `booking.refunded` after-commit. Best-effort; no
76
+ * compensation.
77
+ * 7. `notify` — async-style notification. Best-effort; no compensation.
78
+ *
79
+ * The saga always runs in a single host process — durability for async
80
+ * notifications is the deployment's responsibility (see `JobRunner`).
81
+ */
82
+ export declare function buildRefundBookingWorkflow(deps: RefundBookingDeps): import("@voyantjs/core").WorkflowDefinition;
83
+ /**
84
+ * Convenience wrapper: build + run the saga in one call.
85
+ */
86
+ export declare function refundBooking(input: RefundBookingInput, deps: RefundBookingDeps): Promise<import("@voyantjs/core").WorkflowResult>;
87
+ //# sourceMappingURL=refund-booking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refund-booking.d.ts","sourceRoot":"","sources":["../../src/workflows/refund-booking.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAQ,MAAM,gBAAgB,CAAA;AAEpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAMjE;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAA;IACjB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,kBAAkB,CAAA;IACtB,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,gBAAgB,EAAE,CAAC,IAAI,EAAE;QACvB,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,MAAM,EAAE,MAAM,CAAA;KACf,KAAK,OAAO,CAAC;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IAC9C,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAClF,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACrF,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;QACtB,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,MAAM,CAAA;QACd,WAAW,EAAE,MAAM,CAAA;KACpB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACpB;AAuBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,iBAAiB,+CAgMjE;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,iBAAiB,oDAGrF"}
@@ -0,0 +1,210 @@
1
+ import { createWorkflow, step } from "@voyantjs/core";
2
+ import { eq, sql } from "drizzle-orm";
3
+ import { availabilitySlotsRef } from "../availability-ref.js";
4
+ import { bookingActivityLog, bookingAllocations, bookingItems, bookings } from "../schema.js";
5
+ import { transitionBooking } from "../state-machine.js";
6
+ /**
7
+ * Build the refund saga for a booking.
8
+ *
9
+ * **Steps**
10
+ *
11
+ * 1. `validate-state` — load the booking, ensure it's in a refundable
12
+ * status (`confirmed`, `in_progress`, or `on_hold`), compute the refund
13
+ * amount. Compensation: none — this step doesn't mutate.
14
+ * 2. `create-credit-note` — call into finance via the injected dep.
15
+ * Compensation: void the credit note.
16
+ * 3. `release-inventory` — release any held allocations + the slot
17
+ * capacity (only when the booking hasn't started yet). Compensation:
18
+ * re-acquire (best-effort; if inventory has since been re-sold,
19
+ * that's a known limitation logged for ops triage).
20
+ * 4. `reverse-supplier-offer` — best-effort call into transactions.
21
+ * Compensation: none (idempotent re-call expected if needed).
22
+ * 5. `transition-booking` — flip the booking to `cancelled`. Last
23
+ * DB-touching step so a failure here doesn't leave the booking
24
+ * cancelled with no credit-note rollback.
25
+ * 6. `emit` — fire `booking.refunded` after-commit. Best-effort; no
26
+ * compensation.
27
+ * 7. `notify` — async-style notification. Best-effort; no compensation.
28
+ *
29
+ * The saga always runs in a single host process — durability for async
30
+ * notifications is the deployment's responsibility (see `JobRunner`).
31
+ */
32
+ export function buildRefundBookingWorkflow(deps) {
33
+ return createWorkflow("refund-booking", [
34
+ step("validate-state").run(async (input) => {
35
+ const [row] = await deps.db
36
+ .select({
37
+ id: bookings.id,
38
+ bookingNumber: bookings.bookingNumber,
39
+ status: bookings.status,
40
+ sellAmountCents: bookings.sellAmountCents,
41
+ })
42
+ .from(bookings)
43
+ .where(eq(bookings.id, input.bookingId))
44
+ .limit(1);
45
+ if (!row) {
46
+ throw new Error(`refund-booking: booking ${input.bookingId} not found`);
47
+ }
48
+ if (row.status !== "confirmed" && row.status !== "in_progress" && row.status !== "on_hold") {
49
+ throw new Error(`refund-booking: booking ${input.bookingId} is in ${row.status}, not refundable`);
50
+ }
51
+ const fullRefundAmount = row.sellAmountCents ?? 0;
52
+ const requested = input.amountCents ?? fullRefundAmount;
53
+ if (requested < 0 || requested > fullRefundAmount) {
54
+ throw new Error(`refund-booking: requested amount ${requested} out of range [0, ${fullRefundAmount}]`);
55
+ }
56
+ return {
57
+ bookingId: row.id,
58
+ bookingNumber: row.bookingNumber,
59
+ previousStatus: row.status,
60
+ fullRefundAmount,
61
+ refundAmount: requested,
62
+ };
63
+ }),
64
+ step("create-credit-note")
65
+ .run(async (input, ctx) => {
66
+ const validate = ctx.results["validate-state"];
67
+ if (validate.refundAmount === 0) {
68
+ // Draft / unpaid booking — no payment to refund. Short-circuit.
69
+ return { creditNoteId: null, amountCents: 0 };
70
+ }
71
+ const created = await deps.createCreditNote({
72
+ bookingId: input.bookingId,
73
+ amountCents: validate.refundAmount,
74
+ reason: input.reason,
75
+ });
76
+ return {
77
+ creditNoteId: created?.creditNoteId ?? null,
78
+ amountCents: validate.refundAmount,
79
+ };
80
+ })
81
+ .compensate(async (output) => {
82
+ if (output.creditNoteId && deps.voidCreditNote) {
83
+ await deps.voidCreditNote({
84
+ creditNoteId: output.creditNoteId,
85
+ reason: "refund-saga rolled back",
86
+ });
87
+ }
88
+ }),
89
+ step("release-inventory")
90
+ .run(async (input) => {
91
+ return await deps.db.transaction(async (tx) => {
92
+ const allocs = await tx
93
+ .select()
94
+ .from(bookingAllocations)
95
+ .where(eq(bookingAllocations.bookingId, input.bookingId));
96
+ const releaseable = allocs.filter((a) => a.status === "held" || a.status === "confirmed");
97
+ for (const allocation of releaseable) {
98
+ if (allocation.availabilitySlotId) {
99
+ // Best-effort capacity release — restore the held quantity.
100
+ await tx
101
+ .update(availabilitySlotsRef)
102
+ .set({
103
+ remainingPax: sql `${availabilitySlotsRef.remainingPax} + ${allocation.quantity}`,
104
+ })
105
+ .where(eq(availabilitySlotsRef.id, allocation.availabilitySlotId));
106
+ }
107
+ }
108
+ if (releaseable.length > 0) {
109
+ const releaseableIds = releaseable.map((a) => a.id);
110
+ await tx
111
+ .update(bookingAllocations)
112
+ .set({ status: "released", releasedAt: new Date(), updatedAt: new Date() })
113
+ .where(sql `${bookingAllocations.id} IN (${sql.join(releaseableIds.map((id) => sql `${id}`), sql `, `)})`);
114
+ }
115
+ await tx
116
+ .update(bookingItems)
117
+ .set({ status: "cancelled", updatedAt: new Date() })
118
+ .where(eq(bookingItems.bookingId, input.bookingId));
119
+ return {
120
+ releasedAllocationIds: releaseable.map((a) => a.id),
121
+ slotIds: releaseable
122
+ .map((a) => a.availabilitySlotId)
123
+ .filter((id) => Boolean(id)),
124
+ };
125
+ });
126
+ })
127
+ .compensate(async (output) => {
128
+ // Re-decrement the slots we restored. Note: if the slot has since
129
+ // been re-sold this will fail loudly — that's intentional, an
130
+ // operator must intervene.
131
+ if (output.slotIds.length === 0)
132
+ return;
133
+ await deps.db.transaction(async (tx) => {
134
+ for (const slotId of output.slotIds) {
135
+ await tx
136
+ .update(availabilitySlotsRef)
137
+ .set({ remainingPax: sql `${availabilitySlotsRef.remainingPax} - 1` })
138
+ .where(eq(availabilitySlotsRef.id, slotId));
139
+ }
140
+ });
141
+ }),
142
+ step("reverse-supplier-offer").run(async (input) => {
143
+ if (!deps.reverseSupplierOffer)
144
+ return { reversed: false };
145
+ await deps.reverseSupplierOffer({ bookingId: input.bookingId, reason: input.reason });
146
+ return { reversed: true };
147
+ }),
148
+ step("transition-booking").run(async (input, ctx) => {
149
+ const validate = ctx.results["validate-state"];
150
+ const patch = transitionBooking(validate.previousStatus, "cancelled");
151
+ await deps.db.transaction(async (tx) => {
152
+ await tx
153
+ .update(bookings)
154
+ .set({ ...patch, updatedAt: new Date() })
155
+ .where(eq(bookings.id, input.bookingId));
156
+ await tx.insert(bookingActivityLog).values({
157
+ bookingId: input.bookingId,
158
+ actorId: input.userId ?? "system",
159
+ activityType: "status_change",
160
+ description: `Refunded from ${validate.previousStatus}: ${input.reason}`,
161
+ metadata: {
162
+ oldStatus: validate.previousStatus,
163
+ newStatus: "cancelled",
164
+ refundAmountCents: validate.refundAmount,
165
+ reason: input.reason,
166
+ },
167
+ });
168
+ });
169
+ return { status: "cancelled" };
170
+ }),
171
+ step("emit").run(async (input, ctx) => {
172
+ const validate = ctx.results["validate-state"];
173
+ if (!deps.eventBus)
174
+ return { emitted: false };
175
+ await deps.eventBus.emit("booking.refunded", {
176
+ bookingId: input.bookingId,
177
+ bookingNumber: validate.bookingNumber,
178
+ previousStatus: validate.previousStatus,
179
+ refundAmountCents: validate.refundAmount,
180
+ reason: input.reason,
181
+ actorId: input.userId ?? null,
182
+ }, { category: "domain", source: "service" });
183
+ return { emitted: true };
184
+ }),
185
+ step("notify").run(async (input, ctx) => {
186
+ if (!deps.notifyCustomer)
187
+ return { notified: false };
188
+ const validate = ctx.results["validate-state"];
189
+ try {
190
+ await deps.notifyCustomer({
191
+ bookingId: input.bookingId,
192
+ reason: input.reason,
193
+ amountCents: validate.refundAmount,
194
+ });
195
+ }
196
+ catch {
197
+ // Notifications are best-effort. Log via the deployment's logger.
198
+ return { notified: false };
199
+ }
200
+ return { notified: true };
201
+ }),
202
+ ]);
203
+ }
204
+ /**
205
+ * Convenience wrapper: build + run the saga in one call.
206
+ */
207
+ export async function refundBooking(input, deps) {
208
+ const wf = buildRefundBookingWorkflow(deps);
209
+ return wf.run({ input });
210
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/bookings",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "license": "FSL-1.1-Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -49,14 +49,15 @@
49
49
  "drizzle-orm": "^0.45.2",
50
50
  "hono": "^4.12.10",
51
51
  "zod": "^4.3.6",
52
- "@voyantjs/core": "0.9.0",
53
- "@voyantjs/db": "0.9.0",
54
- "@voyantjs/hono": "0.9.0",
55
- "@voyantjs/utils": "0.9.0"
52
+ "@voyantjs/core": "0.11.0",
53
+ "@voyantjs/db": "0.11.0",
54
+ "@voyantjs/hono": "0.11.0",
55
+ "@voyantjs/utils": "0.11.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "typescript": "^6.0.2",
59
- "@voyantjs/products": "0.9.0",
59
+ "@voyantjs/markets": "0.11.0",
60
+ "@voyantjs/products": "0.11.0",
60
61
  "@voyantjs/voyant-typescript-config": "0.1.0"
61
62
  },
62
63
  "files": [