@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.
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/markets-ref.d.ts +151 -0
- package/dist/markets-ref.d.ts.map +1 -0
- package/dist/markets-ref.js +19 -0
- package/dist/pii-redaction.d.ts +89 -0
- package/dist/pii-redaction.d.ts.map +1 -0
- package/dist/pii-redaction.js +120 -0
- package/dist/pii.d.ts +1 -0
- package/dist/pii.d.ts.map +1 -1
- package/dist/pii.js +20 -1
- package/dist/routes-groups.d.ts +3 -2
- package/dist/routes-groups.d.ts.map +1 -1
- package/dist/routes-public.d.ts +11 -13
- package/dist/routes-public.d.ts.map +1 -1
- package/dist/routes-public.js +3 -3
- package/dist/routes.d.ts +232 -22
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +100 -24
- package/dist/schema/travel-details.d.ts +37 -0
- package/dist/schema/travel-details.d.ts.map +1 -1
- package/dist/schema/travel-details.js +6 -0
- package/dist/schema-core.d.ts +17 -17
- package/dist/schema-core.d.ts.map +1 -1
- package/dist/schema-core.js +8 -2
- package/dist/schema-items.d.ts.map +1 -1
- package/dist/schema-items.js +6 -1
- package/dist/schema-operations.d.ts +2 -2
- package/dist/schema-shared.d.ts +1 -1
- package/dist/schema-shared.d.ts.map +1 -1
- package/dist/schema-shared.js +3 -0
- package/dist/service-public.d.ts +0 -6
- package/dist/service-public.d.ts.map +1 -1
- package/dist/service-public.js +0 -4
- package/dist/service.d.ts +232 -56
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +469 -137
- package/dist/state-machine.d.ts +29 -0
- package/dist/state-machine.d.ts.map +1 -0
- package/dist/state-machine.js +39 -0
- package/dist/validation-public.d.ts +0 -6
- package/dist/validation-public.d.ts.map +1 -1
- package/dist/validation-public.js +0 -2
- package/dist/validation.d.ts +25 -16
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +17 -6
- package/dist/workflows/refund-booking.d.ts +87 -0
- package/dist/workflows/refund-booking.d.ts.map +1 -0
- package/dist/workflows/refund-booking.js +210 -0
- 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":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAqBvB,eAAO,MAAM,uCAAuC
|
|
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(),
|
package/dist/validation.d.ts
CHANGED
|
@@ -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>>>;
|
package/dist/validation.d.ts.map
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
package/dist/validation.js
CHANGED
|
@@ -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.
|
|
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.
|
|
53
|
-
"@voyantjs/db": "0.
|
|
54
|
-
"@voyantjs/hono": "0.
|
|
55
|
-
"@voyantjs/utils": "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/
|
|
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": [
|