@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.
Files changed (66) hide show
  1. package/README.md +2 -2
  2. package/dist/index.d.ts +8 -8
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +5 -5
  5. package/dist/pii.d.ts +10 -9
  6. package/dist/pii.d.ts.map +1 -1
  7. package/dist/pii.js +33 -33
  8. package/dist/products-ref.d.ts +93 -1
  9. package/dist/products-ref.d.ts.map +1 -1
  10. package/dist/products-ref.js +12 -1
  11. package/dist/routes-groups.d.ts +25 -5
  12. package/dist/routes-groups.d.ts.map +1 -1
  13. package/dist/routes-groups.js +3 -3
  14. package/dist/routes-public.d.ts +19 -21
  15. package/dist/routes-public.d.ts.map +1 -1
  16. package/dist/routes-public.js +1 -1
  17. package/dist/routes-shared.d.ts +3 -2
  18. package/dist/routes-shared.d.ts.map +1 -1
  19. package/dist/routes.d.ts +283 -188
  20. package/dist/routes.d.ts.map +1 -1
  21. package/dist/routes.js +89 -102
  22. package/dist/schema/travel-details.d.ts +27 -27
  23. package/dist/schema/travel-details.d.ts.map +1 -1
  24. package/dist/schema/travel-details.js +19 -14
  25. package/dist/schema-core.d.ts +194 -305
  26. package/dist/schema-core.d.ts.map +1 -1
  27. package/dist/schema-core.js +19 -10
  28. package/dist/schema-items.d.ts +15 -15
  29. package/dist/schema-items.d.ts.map +1 -1
  30. package/dist/schema-items.js +12 -12
  31. package/dist/schema-operations.d.ts +1 -1
  32. package/dist/schema-operations.js +3 -3
  33. package/dist/schema-relations.d.ts +26 -9
  34. package/dist/schema-relations.d.ts.map +1 -1
  35. package/dist/schema-relations.js +36 -21
  36. package/dist/schema-shared.d.ts +3 -2
  37. package/dist/schema-shared.d.ts.map +1 -1
  38. package/dist/schema-shared.js +4 -5
  39. package/dist/schema-staff.d.ts +267 -0
  40. package/dist/schema-staff.d.ts.map +1 -0
  41. package/dist/schema-staff.js +31 -0
  42. package/dist/schema.d.ts +1 -0
  43. package/dist/schema.d.ts.map +1 -1
  44. package/dist/schema.js +1 -0
  45. package/dist/service-groups.d.ts +3 -7
  46. package/dist/service-groups.d.ts.map +1 -1
  47. package/dist/service-groups.js +6 -10
  48. package/dist/service-public.d.ts +102 -55
  49. package/dist/service-public.d.ts.map +1 -1
  50. package/dist/service-public.js +119 -54
  51. package/dist/service.d.ts +327 -104
  52. package/dist/service.d.ts.map +1 -1
  53. package/dist/service.js +530 -130
  54. package/dist/transactions-ref.d.ts +930 -66
  55. package/dist/transactions-ref.d.ts.map +1 -1
  56. package/dist/transactions-ref.js +56 -2
  57. package/dist/validation-public.d.ts +29 -69
  58. package/dist/validation-public.d.ts.map +1 -1
  59. package/dist/validation-public.js +21 -20
  60. package/dist/validation-shared.d.ts +4 -5
  61. package/dist/validation-shared.d.ts.map +1 -1
  62. package/dist/validation-shared.js +2 -10
  63. package/dist/validation.d.ts +248 -44
  64. package/dist/validation.d.ts.map +1 -1
  65. package/dist/validation.js +103 -28
  66. package/package.json +6 -6
@@ -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, bookingItemParticipants, bookingItems, bookingParticipants, bookingSessionStates, bookings, } from "./schema.js";
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(bookingParticipants)
145
- .where(eq(bookingParticipants.bookingId, booking.id))
146
- .orderBy(asc(bookingParticipants.createdAt)),
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: bookingItemParticipants.id,
155
- bookingItemId: bookingItemParticipants.bookingItemId,
156
- participantId: bookingItemParticipants.participantId,
157
- role: bookingItemParticipants.role,
158
- isPrimary: bookingItemParticipants.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(bookingItemParticipants)
161
- .innerJoin(bookingItems, eq(bookingItems.id, bookingItemParticipants.bookingItemId))
204
+ .from(bookingItemTravelers)
205
+ .innerJoin(bookingItems, eq(bookingItems.id, bookingItemTravelers.bookingItemId))
162
206
  .where(eq(bookingItems.bookingId, booking.id))
163
- .orderBy(asc(bookingItemParticipants.createdAt)),
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
- participantId: link.participantId,
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
- participants: participants.map((participant) => ({
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
- participantLinks: itemLinksByItemId.get(item.id) ?? [],
277
+ travelerLinks: itemLinksByItemId.get(item.id) ?? [],
234
278
  })),
235
279
  documents: documents.map((document) => ({
236
280
  id: document.id,
237
- participantId: document.participantId ?? null,
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
- participantId: fulfillment.participantId ?? null,
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
- async function resolveSessionPricingSnapshot(db, productId, input) {
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(bookingParticipants)
392
- .where(eq(bookingParticipants.bookingId, bookingId))
393
- .orderBy(asc(bookingParticipants.createdAt)),
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: bookingItemParticipants.id,
407
- bookingItemId: bookingItemParticipants.bookingItemId,
408
- participantId: bookingItemParticipants.participantId,
409
- role: bookingItemParticipants.role,
410
- isPrimary: bookingItemParticipants.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(bookingItemParticipants)
413
- .innerJoin(bookingItems, eq(bookingItems.id, bookingItemParticipants.bookingItemId))
468
+ .from(bookingItemTravelers)
469
+ .innerJoin(bookingItems, eq(bookingItems.id, bookingItemTravelers.bookingItemId))
414
470
  .where(eq(bookingItems.bookingId, bookingId))
415
- .orderBy(asc(bookingItemParticipants.createdAt)),
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
- participantId: link.participantId,
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 hasPrimaryParticipant = participants.some((participant) => participant.isPrimary);
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
- participants: participants.map((participant) => ({
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
- participantLinks: itemLinksByItemId.get(item.id) ?? [],
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
- hasParticipants,
507
- hasTraveler,
508
- hasPrimaryParticipant,
562
+ hasTravelers,
563
+ hasPrimaryTraveler,
509
564
  hasItems,
510
565
  hasAllocations,
511
566
  readyForConfirmation: booking.status === "on_hold" &&
512
- hasParticipants &&
567
+ hasTravelers &&
513
568
  hasTraveler &&
514
- hasPrimaryParticipant &&
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 travelerCount = countTravelerParticipants(input.participants);
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 input.participants) {
562
- await bookingsService.createParticipant(db, result.booking.id, {
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
- for (const participantId of input.removedParticipantIds) {
613
- const participant = await bookingsService.getParticipantById(db, bookingId, participantId);
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.deleteParticipant(db, participant.id);
680
+ await bookingsService.deleteTravelerRecord(db, participant.id);
616
681
  }
617
682
  }
618
- if (input.participants) {
619
- for (const participant of input.participants) {
683
+ if (travelers) {
684
+ for (const participant of travelers) {
620
685
  if (participant.id) {
621
- const existing = await bookingsService.getParticipantById(db, bookingId, participant.id);
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.updateParticipant(db, participant.id, {
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.createParticipant(db, bookingId, {
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 && (input.participants || input.removedParticipantIds.length > 0)) {
730
+ if (input.pax === undefined && (travelers || removedTravelerIds.length > 0)) {
666
731
  const participants = await db
667
- .select({ participantType: bookingParticipants.participantType })
668
- .from(bookingParticipants)
669
- .where(eq(bookingParticipants.bookingId, bookingId));
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,