@voyantjs/products 0.27.0 → 0.28.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.
|
@@ -96,6 +96,25 @@ export interface BuildOwnedProductDraftShapeOptions {
|
|
|
96
96
|
addonCatalog?: ReadonlyArray<AddonOffer>;
|
|
97
97
|
}
|
|
98
98
|
export declare function buildOwnedProductDraftShape(options?: BuildOwnedProductDraftShapeOptions): BookingDraftShape;
|
|
99
|
+
/** A per-unit price within a resolved option price rule, returned by
|
|
100
|
+
* `loadResolvedOptionPrice`. The handler matches `travelerCategory`
|
|
101
|
+
* to the draft's pax-band codes ("adult" / "child" / "infant" /
|
|
102
|
+
* "senior") to compute per-band totals. Units that don't map to a
|
|
103
|
+
* band — or whose band has zero count — are dropped. */
|
|
104
|
+
export interface ResolvedUnitPrice {
|
|
105
|
+
unitId: string;
|
|
106
|
+
unitType: "person" | "room" | "vehicle" | "service" | "group" | "other" | string;
|
|
107
|
+
travelerCategory: "adult" | "child" | "infant" | "senior" | null;
|
|
108
|
+
sellAmountCents: number | null;
|
|
109
|
+
}
|
|
110
|
+
/** Output of `loadResolvedOptionPrice`. The handler prefers
|
|
111
|
+
* `unitPrices` (per-band pricing) when present and any unit matches a
|
|
112
|
+
* pax band; otherwise falls back to `baseSellAmountCents × paxCount`
|
|
113
|
+
* for per-booking rules; otherwise back to `product.sellAmountCents`. */
|
|
114
|
+
export interface ResolvedOptionPrice {
|
|
115
|
+
baseSellAmountCents: number | null;
|
|
116
|
+
unitPrices: ReadonlyArray<ResolvedUnitPrice>;
|
|
117
|
+
}
|
|
99
118
|
/** A resolved tax-rate decision — resolved from `tax_classes` ×
|
|
100
119
|
* `tax_regimes` × buyer country at quote time. */
|
|
101
120
|
export interface ResolvedTaxRate {
|
|
@@ -139,6 +158,33 @@ export interface OwnedProductsShapeLoaders {
|
|
|
139
158
|
buyerCountry?: string;
|
|
140
159
|
buyerType?: "B2C" | "B2B";
|
|
141
160
|
}) => Promise<ResolvedTaxRate | null>;
|
|
161
|
+
/**
|
|
162
|
+
* Resolve the option price rule for a given (product, option, date)
|
|
163
|
+
* — typically backed by `@voyantjs/pricing`'s
|
|
164
|
+
* `resolveOptionPriceRulesForDate` plus a join into per-unit prices.
|
|
165
|
+
* Returns null when no rule applies or the resolver can't run; the
|
|
166
|
+
* handler then falls back to `product.sellAmountCents × pax`.
|
|
167
|
+
*
|
|
168
|
+
* Caller-supplied so `@voyantjs/products` does not import
|
|
169
|
+
* `@voyantjs/pricing` (the dependency direction is pricing →
|
|
170
|
+
* products, not the reverse).
|
|
171
|
+
*/
|
|
172
|
+
loadResolvedOptionPrice?: (ctx: OwnedHandlerContext, args: {
|
|
173
|
+
productId: string;
|
|
174
|
+
optionId: string;
|
|
175
|
+
/** ISO yyyy-mm-dd in the slot's local timezone. */
|
|
176
|
+
date: string;
|
|
177
|
+
catalogId?: string;
|
|
178
|
+
}) => Promise<ResolvedOptionPrice | null>;
|
|
179
|
+
/**
|
|
180
|
+
* Look up the local date of a departure slot (`availability_slots`).
|
|
181
|
+
* Caller-supplied so the products package does not import
|
|
182
|
+
* `@voyantjs/availability`. Returns null when the slot is missing.
|
|
183
|
+
*
|
|
184
|
+
* Used together with `loadResolvedOptionPrice` to convert a draft's
|
|
185
|
+
* `departureSlotId` into a date the resolver can match against.
|
|
186
|
+
*/
|
|
187
|
+
loadSlotDate?: (ctx: OwnedHandlerContext, slotId: string) => Promise<string | null>;
|
|
142
188
|
}
|
|
143
189
|
/**
|
|
144
190
|
* Caller-supplied availability-hold bridge — keeps the products
|
|
@@ -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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"}
|
|
@@ -60,9 +60,12 @@ export function createProductsBookingHandler(options) {
|
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
const draft = (request.draft ?? {});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
const optionId = draft.configure?.variantId;
|
|
64
|
+
const slotId = draft.configure?.departureSlotId;
|
|
65
|
+
// Concurrent enrichment + slot-date lookup. The slot date is
|
|
66
|
+
// needed before we can call loadResolvedOptionPrice, so it
|
|
67
|
+
// joins this batch.
|
|
68
|
+
const [travelerFields, addonCatalog, taxRate, slotDate] = await Promise.all([
|
|
66
69
|
options.loadTravelerFields?.(ctx, request.entityId) ?? Promise.resolve(undefined),
|
|
67
70
|
options.loadAddonCatalog?.(ctx, request.entityId) ?? Promise.resolve(undefined),
|
|
68
71
|
options.loadTaxRate?.(ctx, {
|
|
@@ -70,19 +73,34 @@ export function createProductsBookingHandler(options) {
|
|
|
70
73
|
buyerCountry: draft.billing?.address?.country,
|
|
71
74
|
buyerType: draft.billing?.buyerType,
|
|
72
75
|
}) ?? Promise.resolve(null),
|
|
76
|
+
slotId && options.loadSlotDate
|
|
77
|
+
? options.loadSlotDate(ctx, slotId)
|
|
78
|
+
: Promise.resolve(draft.configure?.departureDate ?? null),
|
|
73
79
|
]);
|
|
80
|
+
const resolvedPrice = optionId && slotDate && options.loadResolvedOptionPrice
|
|
81
|
+
? await options.loadResolvedOptionPrice(ctx, {
|
|
82
|
+
productId: request.entityId,
|
|
83
|
+
optionId,
|
|
84
|
+
date: slotDate,
|
|
85
|
+
})
|
|
86
|
+
: null;
|
|
74
87
|
const paxCount = sumPax(draft.configure?.pax);
|
|
75
88
|
// Per-pax pricing fallback: when no pax is supplied yet, quote a
|
|
76
89
|
// single-occupant baseline so the wizard can render a starter
|
|
77
90
|
// total before the user picks counts.
|
|
78
91
|
const effectivePax = paxCount > 0 ? paxCount : 1;
|
|
79
|
-
const
|
|
80
|
-
|
|
92
|
+
const priced = priceQuote({
|
|
93
|
+
product,
|
|
94
|
+
resolvedPrice,
|
|
95
|
+
pax: draft.configure?.pax,
|
|
96
|
+
effectivePax,
|
|
97
|
+
});
|
|
81
98
|
// Tax computation. The base is taxable; addons/accommodation
|
|
82
99
|
// get the same rate in this MVP cut. Per-line override (the
|
|
83
100
|
// `applies_to` axis on tax_classes.lines) lands in a follow-up
|
|
84
101
|
// when the catalog actually carries mixed treatments.
|
|
85
102
|
const taxIsInclusive = taxRate?.priceMode === "inclusive";
|
|
103
|
+
const grossCents = priced.totalCents;
|
|
86
104
|
const taxCents = taxRate && taxRate.rate > 0
|
|
87
105
|
? taxIsInclusive
|
|
88
106
|
? Math.round(grossCents - grossCents / (1 + taxRate.rate))
|
|
@@ -90,7 +108,8 @@ export function createProductsBookingHandler(options) {
|
|
|
90
108
|
: 0;
|
|
91
109
|
const netCents = taxIsInclusive ? grossCents - taxCents : grossCents;
|
|
92
110
|
const payableCents = taxIsInclusive ? grossCents : netCents + taxCents;
|
|
93
|
-
const
|
|
111
|
+
const available = grossCents > 0;
|
|
112
|
+
const pricing = available
|
|
94
113
|
? {
|
|
95
114
|
base_amount: netCents,
|
|
96
115
|
taxes: taxCents,
|
|
@@ -98,16 +117,10 @@ export function createProductsBookingHandler(options) {
|
|
|
98
117
|
surcharges: 0,
|
|
99
118
|
currency: product.sellCurrency,
|
|
100
119
|
breakdown: {
|
|
101
|
-
lines:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
quantity: effectivePax,
|
|
106
|
-
unitAmount: unitCents,
|
|
107
|
-
totalAmount: grossCents,
|
|
108
|
-
taxIncluded: taxIsInclusive,
|
|
109
|
-
},
|
|
110
|
-
],
|
|
120
|
+
lines: priced.lines.map((line) => ({
|
|
121
|
+
...line,
|
|
122
|
+
taxIncluded: taxIsInclusive,
|
|
123
|
+
})),
|
|
111
124
|
taxes: taxRate && taxCents > 0
|
|
112
125
|
? [
|
|
113
126
|
{
|
|
@@ -129,8 +142,8 @@ export function createProductsBookingHandler(options) {
|
|
|
129
142
|
}
|
|
130
143
|
: undefined;
|
|
131
144
|
return {
|
|
132
|
-
available
|
|
133
|
-
invalidReason:
|
|
145
|
+
available,
|
|
146
|
+
invalidReason: available ? undefined : "no_sell_amount_configured",
|
|
134
147
|
pricing,
|
|
135
148
|
shape: buildOwnedProductDraftShape({
|
|
136
149
|
travelerFields,
|
|
@@ -267,6 +280,80 @@ function sumPax(pax) {
|
|
|
267
280
|
}
|
|
268
281
|
return total;
|
|
269
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Three-way price computation:
|
|
285
|
+
*
|
|
286
|
+
* 1. **Per-band** (preferred): when `resolvedPrice.unitPrices` matches
|
|
287
|
+
* at least one band with positive count, sum `pax[band] ×
|
|
288
|
+
* unit.sellAmountCents` for each matching band. One breakdown line
|
|
289
|
+
* per band.
|
|
290
|
+
*
|
|
291
|
+
* 2. **Per-booking**: when no per-band match but `baseSellAmountCents`
|
|
292
|
+
* is set, charge a single `base × paxCount` line.
|
|
293
|
+
*
|
|
294
|
+
* 3. **Fallback**: `product.sellAmountCents × paxCount`. Same shape as
|
|
295
|
+
* Phase A behavior, kept for bookings without an option/slot
|
|
296
|
+
* configured yet.
|
|
297
|
+
*/
|
|
298
|
+
function priceQuote(input) {
|
|
299
|
+
const { product, resolvedPrice, pax, effectivePax } = input;
|
|
300
|
+
if (resolvedPrice && resolvedPrice.unitPrices.length > 0) {
|
|
301
|
+
const bandLines = [];
|
|
302
|
+
let total = 0;
|
|
303
|
+
for (const unit of resolvedPrice.unitPrices) {
|
|
304
|
+
if (!unit.travelerCategory)
|
|
305
|
+
continue;
|
|
306
|
+
const count = pax?.[unit.travelerCategory] ?? 0;
|
|
307
|
+
if (count <= 0)
|
|
308
|
+
continue;
|
|
309
|
+
const sell = unit.sellAmountCents ?? 0;
|
|
310
|
+
if (sell <= 0)
|
|
311
|
+
continue;
|
|
312
|
+
const lineTotal = sell * count;
|
|
313
|
+
total += lineTotal;
|
|
314
|
+
bandLines.push({
|
|
315
|
+
kind: "base",
|
|
316
|
+
label: `${product.name} — ${unit.travelerCategory}`,
|
|
317
|
+
quantity: count,
|
|
318
|
+
unitAmount: sell,
|
|
319
|
+
totalAmount: lineTotal,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (bandLines.length > 0) {
|
|
323
|
+
return { totalCents: total, lines: bandLines };
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (resolvedPrice && resolvedPrice.baseSellAmountCents !== null) {
|
|
327
|
+
const unitCents = resolvedPrice.baseSellAmountCents;
|
|
328
|
+
const totalCents = unitCents * effectivePax;
|
|
329
|
+
return {
|
|
330
|
+
totalCents,
|
|
331
|
+
lines: [
|
|
332
|
+
{
|
|
333
|
+
kind: "base",
|
|
334
|
+
label: product.name,
|
|
335
|
+
quantity: effectivePax,
|
|
336
|
+
unitAmount: unitCents,
|
|
337
|
+
totalAmount: totalCents,
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const unitCents = product.sellAmountCents ?? 0;
|
|
343
|
+
const totalCents = unitCents * effectivePax;
|
|
344
|
+
return {
|
|
345
|
+
totalCents,
|
|
346
|
+
lines: [
|
|
347
|
+
{
|
|
348
|
+
kind: "base",
|
|
349
|
+
label: product.name,
|
|
350
|
+
quantity: effectivePax,
|
|
351
|
+
unitAmount: unitCents,
|
|
352
|
+
totalAmount: totalCents,
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
};
|
|
356
|
+
}
|
|
270
357
|
function extractPersonId(party) {
|
|
271
358
|
if (!party)
|
|
272
359
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/products",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
"hono": "^4.12.10",
|
|
56
56
|
"pdf-lib": "^1.17.1",
|
|
57
57
|
"zod": "^4.3.6",
|
|
58
|
-
"@voyantjs/core": "0.
|
|
59
|
-
"@voyantjs/db": "0.
|
|
60
|
-
"@voyantjs/hono": "0.
|
|
61
|
-
"@voyantjs/utils": "0.
|
|
62
|
-
"@voyantjs/catalog": "0.
|
|
63
|
-
"@voyantjs/storage": "0.
|
|
58
|
+
"@voyantjs/core": "0.28.0",
|
|
59
|
+
"@voyantjs/db": "0.28.0",
|
|
60
|
+
"@voyantjs/hono": "0.28.0",
|
|
61
|
+
"@voyantjs/utils": "0.28.0",
|
|
62
|
+
"@voyantjs/catalog": "0.28.0",
|
|
63
|
+
"@voyantjs/storage": "0.28.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"typescript": "^6.0.2",
|