@voyantjs/products 0.55.0 → 0.55.1

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.
@@ -24,7 +24,7 @@
24
24
  * Phase C+ extensions land on this same handler without re-architecting
25
25
  * the dispatch.
26
26
  */
27
- import { type AddonOffer, type BookingDraftShape, type OwnedBookingHandler, type OwnedHandlerContext, type TravelerFieldRequirement } from "@voyantjs/catalog/booking-engine";
27
+ import { type AddonOffer, type BookingDraftShape, type OwnedBookingHandler, type OwnedHandlerContext, type ProductVariantOption, type TravelerFieldRequirement } from "@voyantjs/catalog/booking-engine";
28
28
  /**
29
29
  * Subset of `bookingsCreate`'s input the bridge builds.
30
30
  * Mirrors the schema in `service-booking-create.ts` — kept
@@ -37,6 +37,10 @@ export interface BookingCreateBridgeInput {
37
37
  bookingNumber: string;
38
38
  personId?: string | null;
39
39
  organizationId?: string | null;
40
+ contactFirstName?: string | null;
41
+ contactLastName?: string | null;
42
+ contactEmail?: string | null;
43
+ contactPhone?: string | null;
40
44
  internalNotes?: string | null;
41
45
  /**
42
46
  * Override the seed sellAmountCents the booking lands at. The owned
@@ -46,6 +50,15 @@ export interface BookingCreateBridgeInput {
46
50
  * docs/architecture/promotions-architecture.md §7.1.
47
51
  */
48
52
  sellAmountCentsOverride?: number | null;
53
+ /**
54
+ * Status the booking lands in. When omitted, `@voyantjs/finance`'s
55
+ * `createBooking` defaults to `"draft"`. Callers that compose bookings
56
+ * via the catalog booking engine (e.g. trip composer reserve, journey
57
+ * commit) typically want `"awaiting_payment"` so the booking shows in
58
+ * the operator's queue with a payable balance. The handler forwards
59
+ * whatever value is passed via `commit`'s `request.parameters.initialStatus`.
60
+ */
61
+ initialStatus?: "draft" | "on_hold" | "awaiting_payment" | "confirmed" | "in_progress" | "completed" | "cancelled" | "expired";
49
62
  travelers?: Array<{
50
63
  firstName: string;
51
64
  lastName: string;
@@ -64,6 +77,10 @@ export interface BookingCreateBridgeInput {
64
77
  amountCents: number;
65
78
  notes?: string | null;
66
79
  }>;
80
+ documentGeneration?: {
81
+ contractDocument: boolean;
82
+ invoiceDocument: boolean;
83
+ };
67
84
  taxLines?: Array<{
68
85
  code?: string | null;
69
86
  name: string;
@@ -76,6 +93,26 @@ export interface BookingCreateBridgeInput {
76
93
  remittanceParty?: string | null;
77
94
  sortOrder?: number;
78
95
  }>;
96
+ itemLines?: Array<{
97
+ optionId?: string | null;
98
+ optionUnitId: string;
99
+ quantity: number;
100
+ title?: string | null;
101
+ description?: string | null;
102
+ unitSellAmountCents?: number | null;
103
+ totalSellAmountCents?: number | null;
104
+ }>;
105
+ extraLines?: Array<{
106
+ productExtraId: string;
107
+ name: string;
108
+ description?: string | null;
109
+ pricingMode?: string | null;
110
+ pricedPerPerson?: boolean | null;
111
+ quantity: number;
112
+ sellCurrency: string;
113
+ unitSellAmountCents?: number | null;
114
+ totalSellAmountCents?: number | null;
115
+ }>;
79
116
  }
80
117
  export interface BookingCreateBridgeResult {
81
118
  status: "ok" | "product_not_found" | string;
@@ -102,6 +139,12 @@ export interface BuildOwnedProductDraftShapeOptions {
102
139
  * `showsAddons` is false.
103
140
  */
104
141
  addonCatalog?: ReadonlyArray<AddonOffer>;
142
+ /**
143
+ * Product options / variants. These select `draft.configure.variantId`
144
+ * and are distinct from extras: one option changes the underlying
145
+ * booking configuration, while extras add optional line items.
146
+ */
147
+ productOptions?: ReadonlyArray<ProductVariantOption>;
105
148
  }
106
149
  export declare function buildOwnedProductDraftShape(options?: BuildOwnedProductDraftShapeOptions): BookingDraftShape;
107
150
  /** A per-unit price within a resolved option price rule, returned by
@@ -152,6 +195,12 @@ export interface OwnedProductsShapeLoaders {
152
195
  * the products package free of an @voyantjs/extras dependency.
153
196
  */
154
197
  loadAddonCatalog?: (ctx: OwnedHandlerContext, productId: string) => Promise<ReadonlyArray<AddonOffer>>;
198
+ /**
199
+ * Resolve product options / variants from the owning products module.
200
+ * Optional for tests and deployments that do not expose option
201
+ * variants in the booking flow.
202
+ */
203
+ loadProductOptions?: (ctx: OwnedHandlerContext, productId: string) => Promise<ReadonlyArray<ProductVariantOption>>;
155
204
  /**
156
205
  * Resolve the tax rate for a given (product, buyer country) pair.
157
206
  * Templates wire this to a function that reads
@@ -1 +1 @@
1
- {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/booking-engine/handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../src/booking-engine/handler.ts"],"names":[],"mappings":"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"}
@@ -31,6 +31,7 @@ export function buildOwnedProductDraftShape(options = {}) {
31
31
  const paxBands = DEFAULT_PAX_BANDS;
32
32
  const fields = options.travelerFields ?? defaultTravelerFields();
33
33
  const addons = options.addonCatalog ?? [];
34
+ const variants = options.productOptions ?? [];
34
35
  const flags = defaultDraftShapeFlags();
35
36
  return {
36
37
  ...flags,
@@ -40,7 +41,10 @@ export function buildOwnedProductDraftShape(options = {}) {
40
41
  travelerFields: fields,
41
42
  bookingFields: defaultBookingFields(),
42
43
  paymentIntents: ["hold", "card"],
43
- configureSubSteps: [{ kind: "occupancy", bands: paxBands }],
44
+ configureSubSteps: [
45
+ ...(variants.length > 0 ? [{ kind: "product-option", options: variants }] : []),
46
+ { kind: "occupancy", bands: paxBands },
47
+ ],
44
48
  addons: addons.length > 0 ? { catalog: addons } : undefined,
45
49
  };
46
50
  }
@@ -61,13 +65,15 @@ export function createProductsBookingHandler(options) {
61
65
  }
62
66
  const draft = (request.draft ?? {});
63
67
  const optionId = draft.configure?.variantId;
68
+ const optionSelections = normalizeOptionSelections(draft.configure?.optionSelections);
64
69
  const slotId = draft.configure?.departureSlotId;
65
70
  // Concurrent enrichment + slot-date lookup. The slot date is
66
71
  // needed before we can call loadResolvedOptionPrice, so it
67
72
  // joins this batch.
68
- const [travelerFields, addonCatalog, taxRate, slotDate] = await Promise.all([
73
+ const [travelerFields, addonCatalog, productOptionCatalog, taxRate, slotDate] = await Promise.all([
69
74
  options.loadTravelerFields?.(ctx, request.entityId) ?? Promise.resolve(undefined),
70
75
  options.loadAddonCatalog?.(ctx, request.entityId) ?? Promise.resolve(undefined),
76
+ options.loadProductOptions?.(ctx, request.entityId) ?? Promise.resolve(undefined),
71
77
  options.loadTaxRate?.(ctx, {
72
78
  productId: request.entityId,
73
79
  buyerCountry: draft.billing?.address?.country,
@@ -77,7 +83,7 @@ export function createProductsBookingHandler(options) {
77
83
  ? options.loadSlotDate(ctx, slotId)
78
84
  : Promise.resolve(draft.configure?.departureDate ?? null),
79
85
  ]);
80
- const resolvedPrice = optionId && slotDate && options.loadResolvedOptionPrice
86
+ const resolvedPrice = optionSelections.length === 0 && optionId && slotDate && options.loadResolvedOptionPrice
81
87
  ? await options.loadResolvedOptionPrice(ctx, {
82
88
  productId: request.entityId,
83
89
  optionId,
@@ -89,10 +95,25 @@ export function createProductsBookingHandler(options) {
89
95
  // single-occupant baseline so the wizard can render a starter
90
96
  // total before the user picks counts.
91
97
  const effectivePax = paxCount > 0 ? paxCount : 1;
92
- const priced = priceQuote({
93
- product,
94
- resolvedPrice,
95
- pax: draft.configure?.pax,
98
+ const priced = optionSelections.length > 0
99
+ ? await priceOptionSelections({
100
+ ctx,
101
+ options,
102
+ product,
103
+ productOptions: productOptionCatalog ?? [],
104
+ selections: optionSelections,
105
+ slotDate,
106
+ })
107
+ : priceQuote({
108
+ product,
109
+ resolvedPrice,
110
+ pax: draft.configure?.pax,
111
+ effectivePax,
112
+ });
113
+ const pricedWithAddons = applyAddonSelections({
114
+ priced,
115
+ addons: draft.addons,
116
+ addonCatalog: addonCatalog ?? [],
96
117
  effectivePax,
97
118
  });
98
119
  // Tax computation. The base is taxable; addons/accommodation
@@ -100,7 +121,7 @@ export function createProductsBookingHandler(options) {
100
121
  // `applies_to` axis on tax_classes.lines) lands in a follow-up
101
122
  // when the catalog actually carries mixed treatments.
102
123
  const taxIsInclusive = taxRate?.priceMode === "inclusive";
103
- const grossCents = priced.totalCents;
124
+ const grossCents = pricedWithAddons.totalCents;
104
125
  const taxCents = taxRate && taxRate.rate > 0
105
126
  ? taxIsInclusive
106
127
  ? Math.round(grossCents - grossCents / (1 + taxRate.rate))
@@ -117,7 +138,7 @@ export function createProductsBookingHandler(options) {
117
138
  surcharges: 0,
118
139
  currency: product.sellCurrency,
119
140
  breakdown: {
120
- lines: priced.lines.map((line) => ({
141
+ lines: pricedWithAddons.lines.map((line) => ({
121
142
  ...line,
122
143
  taxIncluded: taxIsInclusive,
123
144
  })),
@@ -148,6 +169,7 @@ export function createProductsBookingHandler(options) {
148
169
  shape: buildOwnedProductDraftShape({
149
170
  travelerFields,
150
171
  addonCatalog,
172
+ productOptions: productOptionCatalog,
151
173
  }),
152
174
  };
153
175
  },
@@ -223,11 +245,14 @@ export function createProductsBookingHandler(options) {
223
245
  upstreamPayload: { reason: "product_not_found" },
224
246
  };
225
247
  }
226
- const travelers = (draft.travelers ?? []).map((t) => ({
248
+ const partyBilling = extractBillingParty(request.party);
249
+ const partyTravelers = extractPartyTravelers(request.party);
250
+ const travelers = (draft.travelers ?? []).map((t, index) => ({
227
251
  firstName: t.firstName,
228
252
  lastName: t.lastName,
229
253
  email: t.email,
230
254
  phone: t.phone,
255
+ personId: partyTravelers[index]?.personId ?? null,
231
256
  participantType: "traveler",
232
257
  travelerCategory: t.band === "child" || t.band === "infant"
233
258
  ? t.band
@@ -240,15 +265,44 @@ export function createProductsBookingHandler(options) {
240
265
  // subtotal during tax recompute, so derive the override from the
241
266
  // gross breakdown total when an included tax line is present.
242
267
  const sellAmountCentsOverride = resolveSellAmountCentsOverride(request.pricing);
268
+ const optionSelections = normalizeOptionSelections(draft.configure?.optionSelections);
269
+ const selectedOptionIds = [
270
+ ...new Set(optionSelections.map((selection) => selection.optionId)),
271
+ ];
272
+ const primaryOptionId = selectedOptionIds.length === 1
273
+ ? selectedOptionIds[0]
274
+ : optionSelections.length === 0
275
+ ? (draft.configure?.variantId ?? null)
276
+ : null;
243
277
  const bridge = await options.createBooking({
244
278
  productId: product.id,
279
+ optionId: primaryOptionId,
245
280
  bookingNumber: generateNumber(),
246
- personId: extractPersonId(request.party),
247
- organizationId: extractOrganizationId(request.party),
281
+ personId: partyBilling.personId,
282
+ organizationId: partyBilling.organizationId,
283
+ contactFirstName: partyBilling.contactFirstName,
284
+ contactLastName: partyBilling.contactLastName,
285
+ contactEmail: partyBilling.contactEmail,
286
+ contactPhone: partyBilling.contactPhone,
248
287
  internalNotes: extractInternalNotes(request.party),
249
288
  travelers: travelers.length > 0 ? travelers : undefined,
289
+ paymentSchedules: draft.paymentSchedules,
290
+ documentGeneration: draft.documentGeneration
291
+ ? {
292
+ contractDocument: draft.documentGeneration.contractDocument === true,
293
+ invoiceDocument: draft.documentGeneration.invoiceDocument === true,
294
+ }
295
+ : undefined,
250
296
  sellAmountCentsOverride,
251
297
  taxLines: extractTaxLines(request.pricing),
298
+ itemLines: bookingItemLinesFromOptionSelections(optionSelections),
299
+ extraLines: bookingExtraLinesFromAddonSelections({
300
+ addons: draft.addons,
301
+ addonCatalog: await options.loadAddonCatalog?.(ctx, product.id),
302
+ currency: product.sellCurrency,
303
+ quantityMultiplier: Math.max(1, travelers.length || 1),
304
+ }),
305
+ initialStatus: readInitialStatus(request.parameters),
252
306
  });
253
307
  if (bridge.status !== "ok" || !bridge.bookingId) {
254
308
  return {
@@ -288,6 +342,135 @@ function sumPax(pax) {
288
342
  }
289
343
  return total;
290
344
  }
345
+ function normalizeOptionSelections(selections) {
346
+ if (!Array.isArray(selections))
347
+ return [];
348
+ return selections.flatMap((selection) => {
349
+ if (!selection ||
350
+ typeof selection !== "object" ||
351
+ typeof selection.optionId !== "string" ||
352
+ selection.optionId.length === 0) {
353
+ return [];
354
+ }
355
+ const quantity = typeof selection.quantity === "number" && Number.isFinite(selection.quantity)
356
+ ? Math.floor(selection.quantity)
357
+ : 0;
358
+ if (quantity <= 0)
359
+ return [];
360
+ return [
361
+ {
362
+ optionId: selection.optionId,
363
+ ...(typeof selection.optionUnitId === "string" && selection.optionUnitId.length > 0
364
+ ? { optionUnitId: selection.optionUnitId }
365
+ : {}),
366
+ quantity,
367
+ },
368
+ ];
369
+ });
370
+ }
371
+ async function priceOptionSelections(input) {
372
+ const lines = [];
373
+ let totalCents = 0;
374
+ const optionsById = new Map(input.productOptions.map((option) => [option.id, option]));
375
+ for (const selection of input.selections) {
376
+ const resolvedPrice = input.slotDate && input.options.loadResolvedOptionPrice
377
+ ? await input.options.loadResolvedOptionPrice(input.ctx, {
378
+ productId: input.product.id,
379
+ optionId: selection.optionId,
380
+ date: input.slotDate,
381
+ })
382
+ : null;
383
+ const unitPrice = selection.optionUnitId && resolvedPrice?.unitPrices
384
+ ? resolvedPrice.unitPrices.find((unit) => unit.unitId === selection.optionUnitId)
385
+ ?.sellAmountCents
386
+ : null;
387
+ const unitAmount = unitPrice ?? resolvedPrice?.baseSellAmountCents ?? input.product.sellAmountCents ?? 0;
388
+ if (unitAmount <= 0)
389
+ continue;
390
+ const totalAmount = unitAmount * selection.quantity;
391
+ totalCents += totalAmount;
392
+ lines.push({
393
+ kind: "base",
394
+ label: optionsById.get(selection.optionId)?.name ?? input.product.name,
395
+ quantity: selection.quantity,
396
+ unitAmount,
397
+ totalAmount,
398
+ });
399
+ }
400
+ return { totalCents, lines };
401
+ }
402
+ function bookingItemLinesFromOptionSelections(selections) {
403
+ const lines = selections.flatMap((selection) => selection.optionUnitId
404
+ ? [
405
+ {
406
+ optionId: selection.optionId,
407
+ optionUnitId: selection.optionUnitId,
408
+ quantity: selection.quantity,
409
+ },
410
+ ]
411
+ : []);
412
+ return lines.length > 0 ? lines : undefined;
413
+ }
414
+ function applyAddonSelections(input) {
415
+ const extraLines = bookingExtraLinesFromAddonSelections({
416
+ addons: input.addons,
417
+ addonCatalog: input.addonCatalog,
418
+ currency: "EUR",
419
+ });
420
+ if (!extraLines?.length)
421
+ return input.priced;
422
+ const lines = [...input.priced.lines];
423
+ let totalCents = input.priced.totalCents;
424
+ for (const extra of extraLines) {
425
+ const unitAmount = extra.unitSellAmountCents ?? 0;
426
+ const quantity = extra.pricingMode === "per_person" || extra.pricedPerPerson
427
+ ? Math.max(1, input.effectivePax) * extra.quantity
428
+ : extra.quantity;
429
+ const totalAmount = unitAmount * quantity;
430
+ if (totalAmount <= 0)
431
+ continue;
432
+ totalCents += totalAmount;
433
+ lines.push({
434
+ kind: "addon",
435
+ label: extra.name,
436
+ quantity,
437
+ unitAmount,
438
+ totalAmount,
439
+ });
440
+ }
441
+ return { totalCents, lines };
442
+ }
443
+ function bookingExtraLinesFromAddonSelections(input) {
444
+ if (!Array.isArray(input.addons) || input.addons.length === 0)
445
+ return undefined;
446
+ const catalogById = new Map((input.addonCatalog ?? []).map((offer) => [offer.id, offer]));
447
+ const lines = input.addons.flatMap((selection) => {
448
+ const offer = catalogById.get(selection.extraId);
449
+ const quantity = typeof selection.quantity === "number" && Number.isFinite(selection.quantity)
450
+ ? Math.floor(selection.quantity)
451
+ : 0;
452
+ if (!offer || quantity <= 0)
453
+ return [];
454
+ const unitSellAmountCents = offer.unitAmountCents ?? null;
455
+ const chargedQuantity = offer.pricingMode === "per_person" || offer.pricedPerPerson
456
+ ? quantity * Math.max(1, input.quantityMultiplier ?? 1)
457
+ : quantity;
458
+ return [
459
+ {
460
+ productExtraId: offer.id,
461
+ name: offer.name,
462
+ description: offer.description ?? null,
463
+ pricingMode: offer.pricingMode ?? null,
464
+ pricedPerPerson: offer.pricedPerPerson ?? null,
465
+ quantity,
466
+ sellCurrency: offer.currency ?? input.currency,
467
+ unitSellAmountCents,
468
+ totalSellAmountCents: unitSellAmountCents == null ? null : unitSellAmountCents * chargedQuantity,
469
+ },
470
+ ];
471
+ });
472
+ return lines.length > 0 ? lines : undefined;
473
+ }
291
474
  /**
292
475
  * Three-way price computation:
293
476
  *
@@ -362,17 +545,21 @@ function priceQuote(input) {
362
545
  ],
363
546
  };
364
547
  }
365
- function extractPersonId(party) {
366
- if (!party)
367
- return undefined;
368
- const v = party.personId;
369
- return typeof v === "string" && v.length > 0 ? v : undefined;
370
- }
371
- function extractOrganizationId(party) {
372
- if (!party)
373
- return undefined;
374
- const v = party.organizationId;
375
- return typeof v === "string" && v.length > 0 ? v : undefined;
548
+ function readInitialStatus(parameters) {
549
+ const allowed = [
550
+ "draft",
551
+ "on_hold",
552
+ "awaiting_payment",
553
+ "confirmed",
554
+ "in_progress",
555
+ "completed",
556
+ "cancelled",
557
+ "expired",
558
+ ];
559
+ const raw = parameters?.initialStatus;
560
+ return typeof raw === "string" && allowed.includes(raw)
561
+ ? raw
562
+ : undefined;
376
563
  }
377
564
  function extractInternalNotes(party) {
378
565
  if (!party)
@@ -380,6 +567,36 @@ function extractInternalNotes(party) {
380
567
  const v = party.internalNotes;
381
568
  return typeof v === "string" && v.length > 0 ? v : undefined;
382
569
  }
570
+ function extractBillingParty(party) {
571
+ const directBilling = asRecord(party?.billing);
572
+ const travelerParty = asRecord(party?.travelerParty);
573
+ const envelopeBilling = asRecord(travelerParty?.billing);
574
+ const billing = envelopeBilling ?? directBilling;
575
+ const contact = asRecord(billing?.contact);
576
+ return {
577
+ personId: stringValue(party?.personId) ?? stringValue(billing?.personId),
578
+ organizationId: stringValue(party?.organizationId) ?? stringValue(billing?.organizationId),
579
+ contactFirstName: stringValue(contact?.firstName),
580
+ contactLastName: stringValue(contact?.lastName),
581
+ contactEmail: stringValue(contact?.email),
582
+ contactPhone: stringValue(contact?.phone),
583
+ };
584
+ }
585
+ function extractPartyTravelers(party) {
586
+ const travelerParty = asRecord(party?.travelerParty);
587
+ const travelers = Array.isArray(travelerParty?.travelers) ? travelerParty.travelers : [];
588
+ return travelers.map((traveler) => ({
589
+ personId: stringValue(asRecord(traveler)?.personId),
590
+ }));
591
+ }
592
+ function asRecord(value) {
593
+ return value && typeof value === "object" && !Array.isArray(value)
594
+ ? value
595
+ : null;
596
+ }
597
+ function stringValue(value) {
598
+ return typeof value === "string" && value.trim() ? value.trim() : null;
599
+ }
383
600
  function extractTaxLines(pricing) {
384
601
  const breakdown = pricing?.breakdown;
385
602
  if (!breakdown || typeof breakdown !== "object" || Array.isArray(breakdown))
@@ -9,8 +9,8 @@
9
9
  * scheduled departures.
10
10
  * - Travelers: per-pax fields (first / last / email; passport when
11
11
  * the supplier requires it — surfaced via overlay when known).
12
- * - Add-ons: the product's own `options[]` projected as add-on
13
- * offers.
12
+ * - Product options: the product's own `options[]` projected as a
13
+ * configure sub-step, setting `draft.configure.variantId`.
14
14
  *
15
15
  * No accommodation sub-step today (multi-day tours w/ rooms route
16
16
  * through accommodations, not products). Pricing flows through
@@ -1 +1 @@
1
- {"version":3,"file":"draft-shape.d.ts","sourceRoot":"","sources":["../src/draft-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAEL,KAAK,iBAAiB,EAKtB,KAAK,WAAW,EAEjB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,MAAM,WAAW,6BAA6B;IAC5C,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;IACrC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACpD;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,6BAAkC,GAC1C,iBAAiB,CA0BnB"}
1
+ {"version":3,"file":"draft-shape.d.ts","sourceRoot":"","sources":["../src/draft-shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EACL,KAAK,iBAAiB,EAKtB,KAAK,WAAW,EAEjB,MAAM,kCAAkC,CAAA;AAEzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD,MAAM,WAAW,6BAA6B;IAC5C,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;IACrC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACpD;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,6BAAkC,GAC1C,iBAAiB,CAwBnB"}
@@ -9,8 +9,8 @@
9
9
  * scheduled departures.
10
10
  * - Travelers: per-pax fields (first / last / email; passport when
11
11
  * the supplier requires it — surfaced via overlay when known).
12
- * - Add-ons: the product's own `options[]` projected as add-on
13
- * offers.
12
+ * - Product options: the product's own `options[]` projected as a
13
+ * configure sub-step, setting `draft.configure.variantId`.
14
14
  *
15
15
  * No accommodation sub-step today (multi-day tours w/ rooms route
16
16
  * through accommodations, not products). Pricing flows through
@@ -22,25 +22,23 @@ import { DEFAULT_PAX_BANDS, defaultBookingFields, defaultDraftShapeFlags, defaul
22
22
  export function buildProductDraftShape(content, options = {}) {
23
23
  const paxBands = options.paxBands ?? DEFAULT_PAX_BANDS;
24
24
  const total = options.paxBandsAllowedTotal ?? paxBandsAllowedTotalFrom(paxBands);
25
- // Project the product's own options into add-on offers. Each
26
- // option becomes an extras-type add-on; verticals with grouped
27
- // catalogs (cruise excursions) override.
28
- const addonItems = content.options.map((opt) => ({
25
+ const productOptions = content.options.map((opt) => ({
29
26
  id: opt.id,
30
27
  name: opt.name,
31
28
  description: opt.description ?? null,
32
- kind: "extras",
33
- pricingMode: null,
34
29
  }));
35
30
  return {
36
31
  ...defaultDraftShapeFlags(),
37
- showsAddons: addonItems.length > 0,
38
32
  paxBands,
39
33
  paxBandsAllowedTotal: total,
40
34
  travelerFields: defaultTravelerFields(),
41
35
  bookingFields: defaultBookingFields(),
42
- addons: addonItems.length > 0 ? { catalog: addonItems } : undefined,
43
36
  paymentIntents: ["hold", "card"],
44
- configureSubSteps: [{ kind: "occupancy", bands: paxBands }],
37
+ configureSubSteps: [
38
+ ...(productOptions.length > 0
39
+ ? [{ kind: "product-option", options: productOptions }]
40
+ : []),
41
+ { kind: "occupancy", bands: paxBands },
42
+ ],
45
43
  };
46
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/products",
3
- "version": "0.55.0",
3
+ "version": "0.55.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -132,13 +132,13 @@
132
132
  "pdf-lib": "^1.17.1",
133
133
  "sanitize-html": "^2.17.4",
134
134
  "zod": "^4.3.6",
135
- "@voyantjs/action-ledger": "0.55.0",
136
- "@voyantjs/core": "0.55.0",
137
- "@voyantjs/db": "0.55.0",
138
- "@voyantjs/hono": "0.55.0",
139
- "@voyantjs/utils": "0.55.0",
140
- "@voyantjs/catalog": "0.55.0",
141
- "@voyantjs/storage": "0.55.0"
135
+ "@voyantjs/action-ledger": "0.55.1",
136
+ "@voyantjs/core": "0.55.1",
137
+ "@voyantjs/db": "0.55.1",
138
+ "@voyantjs/hono": "0.55.1",
139
+ "@voyantjs/utils": "0.55.1",
140
+ "@voyantjs/catalog": "0.55.1",
141
+ "@voyantjs/storage": "0.55.1"
142
142
  },
143
143
  "devDependencies": {
144
144
  "@types/sanitize-html": "^2.16.1",