@voyantjs/bookings 0.6.8 → 0.7.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/README.md +2 -2
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/pii.d.ts +10 -9
- package/dist/pii.d.ts.map +1 -1
- package/dist/pii.js +33 -33
- package/dist/products-ref.d.ts +93 -1
- package/dist/products-ref.d.ts.map +1 -1
- package/dist/products-ref.js +12 -1
- package/dist/routes-groups.d.ts +25 -5
- package/dist/routes-groups.d.ts.map +1 -1
- package/dist/routes-groups.js +3 -3
- package/dist/routes-public.d.ts +19 -21
- package/dist/routes-public.d.ts.map +1 -1
- package/dist/routes-public.js +1 -1
- package/dist/routes-shared.d.ts +3 -2
- package/dist/routes-shared.d.ts.map +1 -1
- package/dist/routes.d.ts +283 -188
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +89 -102
- package/dist/schema/travel-details.d.ts +27 -27
- package/dist/schema/travel-details.d.ts.map +1 -1
- package/dist/schema/travel-details.js +19 -14
- package/dist/schema-core.d.ts +194 -305
- package/dist/schema-core.d.ts.map +1 -1
- package/dist/schema-core.js +19 -10
- package/dist/schema-items.d.ts +15 -15
- package/dist/schema-items.d.ts.map +1 -1
- package/dist/schema-items.js +12 -12
- package/dist/schema-operations.d.ts +1 -1
- package/dist/schema-operations.js +3 -3
- package/dist/schema-relations.d.ts +26 -9
- package/dist/schema-relations.d.ts.map +1 -1
- package/dist/schema-relations.js +36 -21
- package/dist/schema-shared.d.ts +3 -2
- package/dist/schema-shared.d.ts.map +1 -1
- package/dist/schema-shared.js +4 -5
- package/dist/schema-staff.d.ts +267 -0
- package/dist/schema-staff.d.ts.map +1 -0
- package/dist/schema-staff.js +31 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +1 -0
- package/dist/service-groups.d.ts +3 -7
- package/dist/service-groups.d.ts.map +1 -1
- package/dist/service-groups.js +6 -10
- package/dist/service-public.d.ts +102 -55
- package/dist/service-public.d.ts.map +1 -1
- package/dist/service-public.js +119 -54
- package/dist/service.d.ts +327 -104
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +530 -130
- package/dist/transactions-ref.d.ts +930 -66
- package/dist/transactions-ref.d.ts.map +1 -1
- package/dist/transactions-ref.js +56 -2
- package/dist/validation-public.d.ts +29 -69
- package/dist/validation-public.d.ts.map +1 -1
- package/dist/validation-public.js +21 -20
- package/dist/validation-shared.d.ts +4 -5
- package/dist/validation-shared.d.ts.map +1 -1
- package/dist/validation-shared.js +2 -10
- package/dist/validation.d.ts +248 -44
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +103 -28
- package/package.json +6 -6
package/dist/service-public.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { and, asc, desc, eq, inArray, or } from "drizzle-orm";
|
|
2
2
|
import { optionPriceRulesRef, optionUnitPriceRulesRef, optionUnitTiersRef, priceCatalogsRef, } from "./pricing-ref.js";
|
|
3
3
|
import { optionUnitsRef, productOptionsRef, productsRef } from "./products-ref.js";
|
|
4
|
-
import { bookingAllocations, bookingDocuments, bookingFulfillments,
|
|
4
|
+
import { bookingAllocations, bookingDocuments, bookingFulfillments, bookingItems, bookingItemTravelers, bookingSessionStates, bookings, bookingTravelers, } from "./schema.js";
|
|
5
5
|
import { bookingsService } from "./service.js";
|
|
6
6
|
const travelerParticipantTypes = new Set(["traveler", "occupant"]);
|
|
7
7
|
const WIZARD_STATE_KEY = "wizard";
|
|
@@ -20,6 +20,50 @@ function normalizeDateTime(value) {
|
|
|
20
20
|
}
|
|
21
21
|
return value instanceof Date ? value.toISOString() : value;
|
|
22
22
|
}
|
|
23
|
+
function getRecord(value) {
|
|
24
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
25
|
+
? value
|
|
26
|
+
: null;
|
|
27
|
+
}
|
|
28
|
+
function getNestedRecord(record, keys) {
|
|
29
|
+
for (const key of keys) {
|
|
30
|
+
const value = getRecord(record?.[key]);
|
|
31
|
+
if (value) {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function getRecordString(record, keys) {
|
|
38
|
+
for (const key of keys) {
|
|
39
|
+
const value = record?.[key];
|
|
40
|
+
if (typeof value === "string" && value.length > 0) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
function extractBookingContactFromStatePayload(payload) {
|
|
47
|
+
const root = getRecord(payload);
|
|
48
|
+
const stepData = getNestedRecord(root, ["stepData", "steps"]);
|
|
49
|
+
const billingRecord = getNestedRecord(root, ["billing", "billingContact", "contact"]) ??
|
|
50
|
+
getNestedRecord(stepData, ["billing", "billingContact", "contact"]);
|
|
51
|
+
const billing = getNestedRecord(billingRecord, ["billing", "contact"]) ?? billingRecord;
|
|
52
|
+
if (!billing) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
contactFirstName: getRecordString(billing, ["firstName"]),
|
|
57
|
+
contactLastName: getRecordString(billing, ["lastName"]),
|
|
58
|
+
contactEmail: getRecordString(billing, ["email"]),
|
|
59
|
+
contactPhone: getRecordString(billing, ["phone"]),
|
|
60
|
+
contactCountry: getRecordString(billing, ["country"]),
|
|
61
|
+
contactRegion: getRecordString(billing, ["state", "region"]),
|
|
62
|
+
contactCity: getRecordString(billing, ["city"]),
|
|
63
|
+
contactAddressLine1: getRecordString(billing, ["addressLine1", "address1", "line1"]),
|
|
64
|
+
contactPostalCode: getRecordString(billing, ["postalCode", "postal", "zip"]),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
23
67
|
function countTravelerParticipants(participants) {
|
|
24
68
|
return participants.filter((participant) => travelerParticipantTypes.has(participant.participantType)).length;
|
|
25
69
|
}
|
|
@@ -141,9 +185,9 @@ async function buildOverviewSnapshot(db, query) {
|
|
|
141
185
|
const [participants, items, itemParticipantLinks, documents, fulfillments] = await Promise.all([
|
|
142
186
|
db
|
|
143
187
|
.select()
|
|
144
|
-
.from(
|
|
145
|
-
.where(eq(
|
|
146
|
-
.orderBy(asc(
|
|
188
|
+
.from(bookingTravelers)
|
|
189
|
+
.where(eq(bookingTravelers.bookingId, booking.id))
|
|
190
|
+
.orderBy(asc(bookingTravelers.createdAt)),
|
|
147
191
|
db
|
|
148
192
|
.select()
|
|
149
193
|
.from(bookingItems)
|
|
@@ -151,16 +195,16 @@ async function buildOverviewSnapshot(db, query) {
|
|
|
151
195
|
.orderBy(asc(bookingItems.createdAt)),
|
|
152
196
|
db
|
|
153
197
|
.select({
|
|
154
|
-
id:
|
|
155
|
-
bookingItemId:
|
|
156
|
-
|
|
157
|
-
role:
|
|
158
|
-
isPrimary:
|
|
198
|
+
id: bookingItemTravelers.id,
|
|
199
|
+
bookingItemId: bookingItemTravelers.bookingItemId,
|
|
200
|
+
travelerId: bookingItemTravelers.travelerId,
|
|
201
|
+
role: bookingItemTravelers.role,
|
|
202
|
+
isPrimary: bookingItemTravelers.isPrimary,
|
|
159
203
|
})
|
|
160
|
-
.from(
|
|
161
|
-
.innerJoin(bookingItems, eq(bookingItems.id,
|
|
204
|
+
.from(bookingItemTravelers)
|
|
205
|
+
.innerJoin(bookingItems, eq(bookingItems.id, bookingItemTravelers.bookingItemId))
|
|
162
206
|
.where(eq(bookingItems.bookingId, booking.id))
|
|
163
|
-
.orderBy(asc(
|
|
207
|
+
.orderBy(asc(bookingItemTravelers.createdAt)),
|
|
164
208
|
db
|
|
165
209
|
.select()
|
|
166
210
|
.from(bookingDocuments)
|
|
@@ -184,7 +228,7 @@ async function buildOverviewSnapshot(db, query) {
|
|
|
184
228
|
const existing = itemLinksByItemId.get(link.bookingItemId) ?? [];
|
|
185
229
|
existing.push({
|
|
186
230
|
id: link.id,
|
|
187
|
-
|
|
231
|
+
travelerId: link.travelerId,
|
|
188
232
|
role: link.role,
|
|
189
233
|
isPrimary: link.isPrimary,
|
|
190
234
|
});
|
|
@@ -202,7 +246,7 @@ async function buildOverviewSnapshot(db, query) {
|
|
|
202
246
|
confirmedAt: normalizeDateTime(booking.confirmedAt),
|
|
203
247
|
cancelledAt: normalizeDateTime(booking.cancelledAt),
|
|
204
248
|
completedAt: normalizeDateTime(booking.completedAt),
|
|
205
|
-
|
|
249
|
+
travelers: participants.map((participant) => ({
|
|
206
250
|
id: participant.id,
|
|
207
251
|
participantType: participant.participantType,
|
|
208
252
|
firstName: participant.firstName,
|
|
@@ -230,11 +274,11 @@ async function buildOverviewSnapshot(db, query) {
|
|
|
230
274
|
optionId: item.optionId ?? null,
|
|
231
275
|
optionUnitId: item.optionUnitId ?? null,
|
|
232
276
|
pricingCategoryId: item.pricingCategoryId ?? null,
|
|
233
|
-
|
|
277
|
+
travelerLinks: itemLinksByItemId.get(item.id) ?? [],
|
|
234
278
|
})),
|
|
235
279
|
documents: documents.map((document) => ({
|
|
236
280
|
id: document.id,
|
|
237
|
-
|
|
281
|
+
travelerId: document.travelerId ?? null,
|
|
238
282
|
type: document.type,
|
|
239
283
|
fileName: document.fileName,
|
|
240
284
|
fileUrl: document.fileUrl,
|
|
@@ -242,7 +286,7 @@ async function buildOverviewSnapshot(db, query) {
|
|
|
242
286
|
fulfillments: fulfillments.map((fulfillment) => ({
|
|
243
287
|
id: fulfillment.id,
|
|
244
288
|
bookingItemId: fulfillment.bookingItemId ?? null,
|
|
245
|
-
|
|
289
|
+
travelerId: fulfillment.travelerId ?? null,
|
|
246
290
|
fulfillmentType: fulfillment.fulfillmentType,
|
|
247
291
|
deliveryChannel: fulfillment.deliveryChannel,
|
|
248
292
|
status: fulfillment.status,
|
|
@@ -269,7 +313,19 @@ function buildUnitWarnings(unit, quantity, sessionPax) {
|
|
|
269
313
|
}
|
|
270
314
|
return warnings;
|
|
271
315
|
}
|
|
272
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Resolves the catalog-scoped pricing snapshot for a product (options → option
|
|
318
|
+
* price rules → per-unit price rules → tiers). The snapshot is the same data
|
|
319
|
+
* the storefront booking session uses to compute a total — exposing it as a
|
|
320
|
+
* standalone admin preview lets operator dialogs, tour-sheet exports, and
|
|
321
|
+
* reconciliation flows see the same numbers the customer would see, without
|
|
322
|
+
* creating a throwaway session.
|
|
323
|
+
*
|
|
324
|
+
* Returns `null` when the product isn't publicly visible or there's no active
|
|
325
|
+
* catalog / matching option (caller can decide whether to 404 or surface a
|
|
326
|
+
* "pricing unavailable for this selection" message).
|
|
327
|
+
*/
|
|
328
|
+
export async function resolveSessionPricingSnapshot(db, productId, input) {
|
|
273
329
|
const [product] = await db
|
|
274
330
|
.select({
|
|
275
331
|
id: productsRef.id,
|
|
@@ -388,9 +444,9 @@ async function buildSessionSnapshot(db, bookingId) {
|
|
|
388
444
|
bookingsService.getBookingById(db, bookingId),
|
|
389
445
|
db
|
|
390
446
|
.select()
|
|
391
|
-
.from(
|
|
392
|
-
.where(eq(
|
|
393
|
-
.orderBy(asc(
|
|
447
|
+
.from(bookingTravelers)
|
|
448
|
+
.where(eq(bookingTravelers.bookingId, bookingId))
|
|
449
|
+
.orderBy(asc(bookingTravelers.createdAt)),
|
|
394
450
|
db
|
|
395
451
|
.select()
|
|
396
452
|
.from(bookingItems)
|
|
@@ -403,16 +459,16 @@ async function buildSessionSnapshot(db, bookingId) {
|
|
|
403
459
|
.orderBy(asc(bookingAllocations.createdAt)),
|
|
404
460
|
db
|
|
405
461
|
.select({
|
|
406
|
-
id:
|
|
407
|
-
bookingItemId:
|
|
408
|
-
|
|
409
|
-
role:
|
|
410
|
-
isPrimary:
|
|
462
|
+
id: bookingItemTravelers.id,
|
|
463
|
+
bookingItemId: bookingItemTravelers.bookingItemId,
|
|
464
|
+
travelerId: bookingItemTravelers.travelerId,
|
|
465
|
+
role: bookingItemTravelers.role,
|
|
466
|
+
isPrimary: bookingItemTravelers.isPrimary,
|
|
411
467
|
})
|
|
412
|
-
.from(
|
|
413
|
-
.innerJoin(bookingItems, eq(bookingItems.id,
|
|
468
|
+
.from(bookingItemTravelers)
|
|
469
|
+
.innerJoin(bookingItems, eq(bookingItems.id, bookingItemTravelers.bookingItemId))
|
|
414
470
|
.where(eq(bookingItems.bookingId, bookingId))
|
|
415
|
-
.orderBy(asc(
|
|
471
|
+
.orderBy(asc(bookingItemTravelers.createdAt)),
|
|
416
472
|
getWizardSessionState(db, bookingId),
|
|
417
473
|
]);
|
|
418
474
|
if (!booking) {
|
|
@@ -423,15 +479,15 @@ async function buildSessionSnapshot(db, bookingId) {
|
|
|
423
479
|
const existing = itemLinksByItemId.get(link.bookingItemId) ?? [];
|
|
424
480
|
existing.push({
|
|
425
481
|
id: link.id,
|
|
426
|
-
|
|
482
|
+
travelerId: link.travelerId,
|
|
427
483
|
role: link.role,
|
|
428
484
|
isPrimary: link.isPrimary,
|
|
429
485
|
});
|
|
430
486
|
itemLinksByItemId.set(link.bookingItemId, existing);
|
|
431
487
|
}
|
|
432
|
-
const hasParticipants = participants.length > 0;
|
|
433
488
|
const hasTraveler = countTravelerParticipants(participants) > 0;
|
|
434
|
-
const
|
|
489
|
+
const hasTravelers = participants.length > 0;
|
|
490
|
+
const hasPrimaryTraveler = participants.some((participant) => participant.isPrimary);
|
|
435
491
|
const hasItems = items.length > 0;
|
|
436
492
|
const hasAllocations = allocations.length > 0;
|
|
437
493
|
return {
|
|
@@ -450,7 +506,7 @@ async function buildSessionSnapshot(db, bookingId) {
|
|
|
450
506
|
expiredAt: normalizeDateTime(booking.expiredAt),
|
|
451
507
|
cancelledAt: normalizeDateTime(booking.cancelledAt),
|
|
452
508
|
completedAt: normalizeDateTime(booking.completedAt),
|
|
453
|
-
|
|
509
|
+
travelers: participants.map((participant) => ({
|
|
454
510
|
id: participant.id,
|
|
455
511
|
participantType: participant.participantType,
|
|
456
512
|
travelerCategory: participant.travelerCategory ?? null,
|
|
@@ -485,7 +541,7 @@ async function buildSessionSnapshot(db, bookingId) {
|
|
|
485
541
|
optionId: item.optionId ?? null,
|
|
486
542
|
optionUnitId: item.optionUnitId ?? null,
|
|
487
543
|
pricingCategoryId: item.pricingCategoryId ?? null,
|
|
488
|
-
|
|
544
|
+
travelerLinks: itemLinksByItemId.get(item.id) ?? [],
|
|
489
545
|
})),
|
|
490
546
|
allocations: allocations.map((allocation) => ({
|
|
491
547
|
id: allocation.id,
|
|
@@ -503,15 +559,14 @@ async function buildSessionSnapshot(db, bookingId) {
|
|
|
503
559
|
releasedAt: normalizeDateTime(allocation.releasedAt),
|
|
504
560
|
})),
|
|
505
561
|
checklist: {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
hasPrimaryParticipant,
|
|
562
|
+
hasTravelers,
|
|
563
|
+
hasPrimaryTraveler,
|
|
509
564
|
hasItems,
|
|
510
565
|
hasAllocations,
|
|
511
566
|
readyForConfirmation: booking.status === "on_hold" &&
|
|
512
|
-
|
|
567
|
+
hasTravelers &&
|
|
513
568
|
hasTraveler &&
|
|
514
|
-
|
|
569
|
+
hasPrimaryTraveler &&
|
|
515
570
|
hasItems &&
|
|
516
571
|
hasAllocations,
|
|
517
572
|
},
|
|
@@ -520,7 +575,8 @@ async function buildSessionSnapshot(db, bookingId) {
|
|
|
520
575
|
}
|
|
521
576
|
export const publicBookingsService = {
|
|
522
577
|
async createSession(db, input, userId) {
|
|
523
|
-
const
|
|
578
|
+
const travelers = input.travelers ?? [];
|
|
579
|
+
const travelerCount = countTravelerParticipants(travelers);
|
|
524
580
|
const bookingNumber = await generateBookingNumber(db);
|
|
525
581
|
const result = await bookingsService.reserveBooking(db, {
|
|
526
582
|
bookingNumber,
|
|
@@ -558,8 +614,8 @@ export const publicBookingsService = {
|
|
|
558
614
|
if (!("booking" in result) || !result.booking) {
|
|
559
615
|
return result;
|
|
560
616
|
}
|
|
561
|
-
for (const participant of
|
|
562
|
-
await bookingsService.
|
|
617
|
+
for (const participant of travelers) {
|
|
618
|
+
await bookingsService.createTravelerRecord(db, result.booking.id, {
|
|
563
619
|
participantType: participant.participantType,
|
|
564
620
|
travelerCategory: participant.travelerCategory ?? null,
|
|
565
621
|
firstName: participant.firstName,
|
|
@@ -593,6 +649,13 @@ export const publicBookingsService = {
|
|
|
593
649
|
return { status: "not_found" };
|
|
594
650
|
}
|
|
595
651
|
const state = await upsertWizardSessionState(db, bookingId, input);
|
|
652
|
+
if (!state) {
|
|
653
|
+
return { status: "not_found" };
|
|
654
|
+
}
|
|
655
|
+
const bookingContact = extractBookingContactFromStatePayload(state.payload);
|
|
656
|
+
if (bookingContact) {
|
|
657
|
+
await bookingsService.updateBooking(db, bookingId, bookingContact);
|
|
658
|
+
}
|
|
596
659
|
return { status: "ok", state };
|
|
597
660
|
},
|
|
598
661
|
async updateSession(db, bookingId, input, userId) {
|
|
@@ -609,20 +672,22 @@ export const publicBookingsService = {
|
|
|
609
672
|
pax: input.pax,
|
|
610
673
|
});
|
|
611
674
|
}
|
|
612
|
-
|
|
613
|
-
|
|
675
|
+
const travelers = input.travelers;
|
|
676
|
+
const removedTravelerIds = input.removedTravelerIds ?? [];
|
|
677
|
+
for (const travelerId of removedTravelerIds) {
|
|
678
|
+
const participant = await bookingsService.getTravelerRecordById(db, bookingId, travelerId);
|
|
614
679
|
if (participant) {
|
|
615
|
-
await bookingsService.
|
|
680
|
+
await bookingsService.deleteTravelerRecord(db, participant.id);
|
|
616
681
|
}
|
|
617
682
|
}
|
|
618
|
-
if (
|
|
619
|
-
for (const participant of
|
|
683
|
+
if (travelers) {
|
|
684
|
+
for (const participant of travelers) {
|
|
620
685
|
if (participant.id) {
|
|
621
|
-
const existing = await bookingsService.
|
|
686
|
+
const existing = await bookingsService.getTravelerRecordById(db, bookingId, participant.id);
|
|
622
687
|
if (!existing) {
|
|
623
688
|
return { status: "participant_not_found" };
|
|
624
689
|
}
|
|
625
|
-
await bookingsService.
|
|
690
|
+
await bookingsService.updateTravelerRecord(db, participant.id, {
|
|
626
691
|
participantType: participant.participantType,
|
|
627
692
|
travelerCategory: participant.travelerCategory ?? null,
|
|
628
693
|
firstName: participant.firstName,
|
|
@@ -637,7 +702,7 @@ export const publicBookingsService = {
|
|
|
637
702
|
});
|
|
638
703
|
continue;
|
|
639
704
|
}
|
|
640
|
-
await bookingsService.
|
|
705
|
+
await bookingsService.createTravelerRecord(db, bookingId, {
|
|
641
706
|
participantType: participant.participantType,
|
|
642
707
|
travelerCategory: participant.travelerCategory ?? null,
|
|
643
708
|
firstName: participant.firstName,
|
|
@@ -662,11 +727,11 @@ export const publicBookingsService = {
|
|
|
662
727
|
return holdResult;
|
|
663
728
|
}
|
|
664
729
|
}
|
|
665
|
-
if (input.pax === undefined && (
|
|
730
|
+
if (input.pax === undefined && (travelers || removedTravelerIds.length > 0)) {
|
|
666
731
|
const participants = await db
|
|
667
|
-
.select({ participantType:
|
|
668
|
-
.from(
|
|
669
|
-
.where(eq(
|
|
732
|
+
.select({ participantType: bookingTravelers.participantType })
|
|
733
|
+
.from(bookingTravelers)
|
|
734
|
+
.where(eq(bookingTravelers.bookingId, bookingId));
|
|
670
735
|
const travelerCount = countTravelerParticipants(participants);
|
|
671
736
|
await bookingsService.updateBooking(db, bookingId, {
|
|
672
737
|
pax: travelerCount > 0 ? travelerCount : null,
|