@voyant-travel/inventory 0.1.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/LICENSE +201 -0
- package/dist/action-ledger-drift.d.ts +29 -0
- package/dist/action-ledger-drift.d.ts.map +1 -0
- package/dist/action-ledger-drift.js +338 -0
- package/dist/action-ledger.d.ts +104 -0
- package/dist/action-ledger.d.ts.map +1 -0
- package/dist/action-ledger.js +100 -0
- package/dist/authoring/builder.d.ts +37 -0
- package/dist/authoring/builder.d.ts.map +1 -0
- package/dist/authoring/builder.js +248 -0
- package/dist/authoring/clone-content.d.ts +38 -0
- package/dist/authoring/clone-content.d.ts.map +1 -0
- package/dist/authoring/clone-content.js +367 -0
- package/dist/authoring/clone-pricing.d.ts +9 -0
- package/dist/authoring/clone-pricing.d.ts.map +1 -0
- package/dist/authoring/clone-pricing.js +242 -0
- package/dist/authoring/clone.d.ts +45 -0
- package/dist/authoring/clone.d.ts.map +1 -0
- package/dist/authoring/clone.js +142 -0
- package/dist/authoring/errors.d.ts +21 -0
- package/dist/authoring/errors.d.ts.map +1 -0
- package/dist/authoring/errors.js +13 -0
- package/dist/authoring/extension.d.ts +248 -0
- package/dist/authoring/extension.d.ts.map +1 -0
- package/dist/authoring/extension.js +116 -0
- package/dist/authoring/index.d.ts +12 -0
- package/dist/authoring/index.d.ts.map +1 -0
- package/dist/authoring/index.js +11 -0
- package/dist/authoring/schema.d.ts +85 -0
- package/dist/authoring/schema.d.ts.map +1 -0
- package/dist/authoring/schema.js +16 -0
- package/dist/authoring/service.d.ts +28 -0
- package/dist/authoring/service.d.ts.map +1 -0
- package/dist/authoring/service.js +66 -0
- package/dist/authoring/spec.d.ts +524 -0
- package/dist/authoring/spec.d.ts.map +1 -0
- package/dist/authoring/spec.js +167 -0
- package/dist/authoring/validate.d.ts +17 -0
- package/dist/authoring/validate.d.ts.map +1 -0
- package/dist/authoring/validate.js +83 -0
- package/dist/authoring.d.ts +2 -0
- package/dist/authoring.d.ts.map +1 -0
- package/dist/authoring.js +1 -0
- package/dist/booking-engine/handler-support.d.ts +91 -0
- package/dist/booking-engine/handler-support.d.ts.map +1 -0
- package/dist/booking-engine/handler-support.js +355 -0
- package/dist/booking-engine/handler.d.ts +404 -0
- package/dist/booking-engine/handler.d.ts.map +1 -0
- package/dist/booking-engine/handler.js +398 -0
- package/dist/booking-engine/index.d.ts +8 -0
- package/dist/booking-engine/index.d.ts.map +1 -0
- package/dist/booking-engine/index.js +7 -0
- package/dist/booking-engine.d.ts +2 -0
- package/dist/booking-engine.d.ts.map +1 -0
- package/dist/booking-engine.js +1 -0
- package/dist/booking-extension.d.ts +278 -0
- package/dist/booking-extension.d.ts.map +1 -0
- package/dist/booking-extension.js +161 -0
- package/dist/catalog-policy-departures.d.ts +52 -0
- package/dist/catalog-policy-departures.d.ts.map +1 -0
- package/dist/catalog-policy-departures.js +169 -0
- package/dist/catalog-policy-destinations.d.ts +43 -0
- package/dist/catalog-policy-destinations.d.ts.map +1 -0
- package/dist/catalog-policy-destinations.js +165 -0
- package/dist/catalog-policy-pricing.d.ts +55 -0
- package/dist/catalog-policy-pricing.d.ts.map +1 -0
- package/dist/catalog-policy-pricing.js +109 -0
- package/dist/catalog-policy-promotions.d.ts +52 -0
- package/dist/catalog-policy-promotions.d.ts.map +1 -0
- package/dist/catalog-policy-promotions.js +270 -0
- package/dist/catalog-policy-taxonomy.d.ts +51 -0
- package/dist/catalog-policy-taxonomy.d.ts.map +1 -0
- package/dist/catalog-policy-taxonomy.js +191 -0
- package/dist/catalog-policy.d.ts +33 -0
- package/dist/catalog-policy.d.ts.map +1 -0
- package/dist/catalog-policy.js +733 -0
- package/dist/content-shape.d.ts +15 -0
- package/dist/content-shape.d.ts.map +1 -0
- package/dist/content-shape.js +28 -0
- package/dist/draft-shape.d.ts +43 -0
- package/dist/draft-shape.d.ts.map +1 -0
- package/dist/draft-shape.js +48 -0
- package/dist/events.d.ts +37 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +32 -0
- package/dist/extras/catalog-policy.d.ts +30 -0
- package/dist/extras/catalog-policy.d.ts.map +1 -0
- package/dist/extras/catalog-policy.js +319 -0
- package/dist/extras/content-shape.d.ts +5 -0
- package/dist/extras/content-shape.d.ts.map +1 -0
- package/dist/extras/content-shape.js +13 -0
- package/dist/extras/draft-shape.d.ts +34 -0
- package/dist/extras/draft-shape.d.ts.map +1 -0
- package/dist/extras/draft-shape.js +69 -0
- package/dist/extras/routes.d.ts +380 -0
- package/dist/extras/routes.d.ts.map +1 -0
- package/dist/extras/routes.js +59 -0
- package/dist/extras/schema-sourced-content.d.ts +254 -0
- package/dist/extras/schema-sourced-content.d.ts.map +1 -0
- package/dist/extras/schema-sourced-content.js +45 -0
- package/dist/extras/schema.d.ts +628 -0
- package/dist/extras/schema.d.ts.map +1 -0
- package/dist/extras/schema.js +87 -0
- package/dist/extras/service-catalog-plane.d.ts +77 -0
- package/dist/extras/service-catalog-plane.d.ts.map +1 -0
- package/dist/extras/service-catalog-plane.js +219 -0
- package/dist/extras/service-content-synthesizer.d.ts +41 -0
- package/dist/extras/service-content-synthesizer.d.ts.map +1 -0
- package/dist/extras/service-content-synthesizer.js +138 -0
- package/dist/extras/service-content.d.ts +48 -0
- package/dist/extras/service-content.d.ts.map +1 -0
- package/dist/extras/service-content.js +253 -0
- package/dist/extras/service.d.ts +185 -0
- package/dist/extras/service.d.ts.map +1 -0
- package/dist/extras/service.js +96 -0
- package/dist/extras/validation.d.ts +437 -0
- package/dist/extras/validation.d.ts.map +1 -0
- package/dist/extras/validation.js +149 -0
- package/dist/extras.d.ts +267 -0
- package/dist/extras.d.ts.map +1 -0
- package/dist/extras.js +19 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/interface.d.ts +5869 -0
- package/dist/interface.d.ts.map +1 -0
- package/dist/interface.js +54 -0
- package/dist/public-routes.d.ts +2 -0
- package/dist/public-routes.d.ts.map +1 -0
- package/dist/public-routes.js +1 -0
- package/dist/public-validation.d.ts +2 -0
- package/dist/public-validation.d.ts.map +1 -0
- package/dist/public-validation.js +1 -0
- package/dist/read-model.d.ts +25 -0
- package/dist/read-model.d.ts.map +1 -0
- package/dist/read-model.js +99 -0
- package/dist/route-env.d.ts +22 -0
- package/dist/route-env.d.ts.map +1 -0
- package/dist/route-env.js +1 -0
- package/dist/routes-associations.d.ts +164 -0
- package/dist/routes-associations.d.ts.map +1 -0
- package/dist/routes-associations.js +100 -0
- package/dist/routes-catalog.d.ts +436 -0
- package/dist/routes-catalog.d.ts.map +1 -0
- package/dist/routes-catalog.js +104 -0
- package/dist/routes-configuration.d.ts +773 -0
- package/dist/routes-configuration.d.ts.map +1 -0
- package/dist/routes-configuration.js +364 -0
- package/dist/routes-content.d.ts +74 -0
- package/dist/routes-content.d.ts.map +1 -0
- package/dist/routes-content.js +117 -0
- package/dist/routes-core.d.ts +331 -0
- package/dist/routes-core.d.ts.map +1 -0
- package/dist/routes-core.js +95 -0
- package/dist/routes-itinerary.d.ts +759 -0
- package/dist/routes-itinerary.d.ts.map +1 -0
- package/dist/routes-itinerary.js +387 -0
- package/dist/routes-maintenance.d.ts +32 -0
- package/dist/routes-maintenance.d.ts.map +1 -0
- package/dist/routes-maintenance.js +14 -0
- package/dist/routes-media.d.ts +634 -0
- package/dist/routes-media.d.ts.map +1 -0
- package/dist/routes-media.js +245 -0
- package/dist/routes-merchandising.d.ts +1120 -0
- package/dist/routes-merchandising.d.ts.map +1 -0
- package/dist/routes-merchandising.js +377 -0
- package/dist/routes-options.d.ts +363 -0
- package/dist/routes-options.d.ts.map +1 -0
- package/dist/routes-options.js +173 -0
- package/dist/routes-public.d.ts +776 -0
- package/dist/routes-public.d.ts.map +1 -0
- package/dist/routes-public.js +119 -0
- package/dist/routes-translations.d.ts +489 -0
- package/dist/routes-translations.d.ts.map +1 -0
- package/dist/routes-translations.js +258 -0
- package/dist/routes.d.ts +5097 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +64 -0
- package/dist/schema-core.d.ts +1238 -0
- package/dist/schema-core.d.ts.map +1 -0
- package/dist/schema-core.js +157 -0
- package/dist/schema-itinerary.d.ts +1169 -0
- package/dist/schema-itinerary.d.ts.map +1 -0
- package/dist/schema-itinerary.js +130 -0
- package/dist/schema-relations.d.ts +117 -0
- package/dist/schema-relations.d.ts.map +1 -0
- package/dist/schema-relations.js +192 -0
- package/dist/schema-settings.d.ts +1800 -0
- package/dist/schema-settings.d.ts.map +1 -0
- package/dist/schema-settings.js +220 -0
- package/dist/schema-shared.d.ts +15 -0
- package/dist/schema-shared.d.ts.map +1 -0
- package/dist/schema-shared.js +91 -0
- package/dist/schema-sourced-content.d.ts +262 -0
- package/dist/schema-sourced-content.d.ts.map +1 -0
- package/dist/schema-sourced-content.js +69 -0
- package/dist/schema-taxonomy.d.ts +1363 -0
- package/dist/schema-taxonomy.d.ts.map +1 -0
- package/dist/schema-taxonomy.js +203 -0
- package/dist/schema.d.ts +10 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +9 -0
- package/dist/service-aggregates.d.ts +29 -0
- package/dist/service-aggregates.d.ts.map +1 -0
- package/dist/service-aggregates.js +56 -0
- package/dist/service-catalog-plane-destinations.d.ts +30 -0
- package/dist/service-catalog-plane-destinations.d.ts.map +1 -0
- package/dist/service-catalog-plane-destinations.js +143 -0
- package/dist/service-catalog-plane-taxonomy.d.ts +73 -0
- package/dist/service-catalog-plane-taxonomy.d.ts.map +1 -0
- package/dist/service-catalog-plane-taxonomy.js +242 -0
- package/dist/service-catalog-plane.d.ts +179 -0
- package/dist/service-catalog-plane.d.ts.map +1 -0
- package/dist/service-catalog-plane.js +431 -0
- package/dist/service-catalog.d.ts +251 -0
- package/dist/service-catalog.d.ts.map +1 -0
- package/dist/service-catalog.js +517 -0
- package/dist/service-configuration.d.ts +261 -0
- package/dist/service-configuration.d.ts.map +1 -0
- package/dist/service-configuration.js +343 -0
- package/dist/service-content-owned.d.ts +68 -0
- package/dist/service-content-owned.d.ts.map +1 -0
- package/dist/service-content-owned.js +329 -0
- package/dist/service-content-synthesizer.d.ts +90 -0
- package/dist/service-content-synthesizer.d.ts.map +1 -0
- package/dist/service-content-synthesizer.js +178 -0
- package/dist/service-content.d.ts +106 -0
- package/dist/service-content.d.ts.map +1 -0
- package/dist/service-content.js +388 -0
- package/dist/service-core.d.ts +194 -0
- package/dist/service-core.d.ts.map +1 -0
- package/dist/service-core.js +213 -0
- package/dist/service-delivery-formats.d.ts +58 -0
- package/dist/service-delivery-formats.d.ts.map +1 -0
- package/dist/service-delivery-formats.js +107 -0
- package/dist/service-destinations.d.ts +223 -0
- package/dist/service-destinations.d.ts.map +1 -0
- package/dist/service-destinations.js +310 -0
- package/dist/service-itinerary-history.d.ts +457 -0
- package/dist/service-itinerary-history.d.ts.map +1 -0
- package/dist/service-itinerary-history.js +135 -0
- package/dist/service-itinerary.d.ts +1149 -0
- package/dist/service-itinerary.d.ts.map +1 -0
- package/dist/service-itinerary.js +419 -0
- package/dist/service-media.d.ts +272 -0
- package/dist/service-media.d.ts.map +1 -0
- package/dist/service-media.js +320 -0
- package/dist/service-merchandising.d.ts +184 -0
- package/dist/service-merchandising.d.ts.map +1 -0
- package/dist/service-merchandising.js +181 -0
- package/dist/service-option-translations.d.ts +268 -0
- package/dist/service-option-translations.d.ts.map +1 -0
- package/dist/service-option-translations.js +300 -0
- package/dist/service-options.d.ts +181 -0
- package/dist/service-options.d.ts.map +1 -0
- package/dist/service-options.js +179 -0
- package/dist/service-product-destinations.d.ts +37 -0
- package/dist/service-product-destinations.d.ts.map +1 -0
- package/dist/service-product-destinations.js +94 -0
- package/dist/service-public.d.ts +664 -0
- package/dist/service-public.d.ts.map +1 -0
- package/dist/service-public.js +374 -0
- package/dist/service-taxonomy.d.ts +197 -0
- package/dist/service-taxonomy.d.ts.map +1 -0
- package/dist/service-taxonomy.js +221 -0
- package/dist/service.d.ts +3929 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +28 -0
- package/dist/tasks/brochure-printers.d.ts +31 -0
- package/dist/tasks/brochure-printers.d.ts.map +1 -0
- package/dist/tasks/brochure-printers.js +149 -0
- package/dist/tasks/brochure-templates.d.ts +36 -0
- package/dist/tasks/brochure-templates.d.ts.map +1 -0
- package/dist/tasks/brochure-templates.js +110 -0
- package/dist/tasks/brochures.d.ts +43 -0
- package/dist/tasks/brochures.d.ts.map +1 -0
- package/dist/tasks/brochures.js +72 -0
- package/dist/tasks/generate-pdf.d.ts +8 -0
- package/dist/tasks/generate-pdf.d.ts.map +1 -0
- package/dist/tasks/generate-pdf.js +106 -0
- package/dist/tasks/index.d.ts +5 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +4 -0
- package/dist/tasks/pdf-text.d.ts +2 -0
- package/dist/tasks/pdf-text.d.ts.map +1 -0
- package/dist/tasks/pdf-text.js +40 -0
- package/dist/tasks.d.ts +2 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +1 -0
- package/dist/validation-catalog.d.ts +2 -0
- package/dist/validation-catalog.d.ts.map +1 -0
- package/dist/validation-catalog.js +3 -0
- package/dist/validation-config.d.ts +2 -0
- package/dist/validation-config.d.ts.map +1 -0
- package/dist/validation-config.js +3 -0
- package/dist/validation-content.d.ts +2 -0
- package/dist/validation-content.d.ts.map +1 -0
- package/dist/validation-content.js +3 -0
- package/dist/validation-core.d.ts +2 -0
- package/dist/validation-core.d.ts.map +1 -0
- package/dist/validation-core.js +3 -0
- package/dist/validation-public.d.ts +2 -0
- package/dist/validation-public.d.ts.map +1 -0
- package/dist/validation-public.js +3 -0
- package/dist/validation-shared.d.ts +2 -0
- package/dist/validation-shared.d.ts.map +1 -0
- package/dist/validation-shared.js +3 -0
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +3 -0
- package/package.json +204 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import { products } from "../schema-core.js";
|
|
3
|
+
export async function loadProduct(db, productId) {
|
|
4
|
+
const drizzle = db;
|
|
5
|
+
const rows = (await drizzle
|
|
6
|
+
.select()
|
|
7
|
+
.from(products)
|
|
8
|
+
.where(eq(products.id, productId))
|
|
9
|
+
.limit(1));
|
|
10
|
+
return rows[0];
|
|
11
|
+
}
|
|
12
|
+
export function sumPax(pax) {
|
|
13
|
+
if (!pax)
|
|
14
|
+
return 0;
|
|
15
|
+
let total = 0;
|
|
16
|
+
for (const v of Object.values(pax)) {
|
|
17
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0)
|
|
18
|
+
total += v;
|
|
19
|
+
}
|
|
20
|
+
return total;
|
|
21
|
+
}
|
|
22
|
+
export function normalizeOptionSelections(selections) {
|
|
23
|
+
if (!Array.isArray(selections))
|
|
24
|
+
return [];
|
|
25
|
+
return selections.flatMap((selection) => {
|
|
26
|
+
if (!selection ||
|
|
27
|
+
typeof selection !== "object" ||
|
|
28
|
+
typeof selection.optionId !== "string" ||
|
|
29
|
+
selection.optionId.length === 0) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const quantity = typeof selection.quantity === "number" && Number.isFinite(selection.quantity)
|
|
33
|
+
? Math.floor(selection.quantity)
|
|
34
|
+
: 0;
|
|
35
|
+
if (quantity <= 0)
|
|
36
|
+
return [];
|
|
37
|
+
return [
|
|
38
|
+
{
|
|
39
|
+
optionId: selection.optionId,
|
|
40
|
+
...(typeof selection.optionUnitId === "string" && selection.optionUnitId.length > 0
|
|
41
|
+
? { optionUnitId: selection.optionUnitId }
|
|
42
|
+
: {}),
|
|
43
|
+
...(typeof selection.optionName === "string" ? { optionName: selection.optionName } : {}),
|
|
44
|
+
...(typeof selection.optionUnitName === "string"
|
|
45
|
+
? { optionUnitName: selection.optionUnitName }
|
|
46
|
+
: {}),
|
|
47
|
+
quantity,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export async function priceOptionSelections(input) {
|
|
53
|
+
const lines = [];
|
|
54
|
+
let totalCents = 0;
|
|
55
|
+
const optionsById = new Map(input.productOptions.map((option) => [option.id, option]));
|
|
56
|
+
for (const selection of input.selections) {
|
|
57
|
+
const resolvedPrice = input.slotDate && input.options.loadResolvedOptionPrice
|
|
58
|
+
? await input.options.loadResolvedOptionPrice(input.ctx, {
|
|
59
|
+
productId: input.product.id,
|
|
60
|
+
optionId: selection.optionId,
|
|
61
|
+
date: input.slotDate,
|
|
62
|
+
})
|
|
63
|
+
: null;
|
|
64
|
+
const unitPrice = selection.optionUnitId && resolvedPrice?.unitPrices
|
|
65
|
+
? resolvedPrice.unitPrices.find((unit) => unit.unitId === selection.optionUnitId)
|
|
66
|
+
?.sellAmountCents
|
|
67
|
+
: null;
|
|
68
|
+
const unitAmount = unitPrice ?? resolvedPrice?.baseSellAmountCents ?? input.product.sellAmountCents ?? 0;
|
|
69
|
+
if (unitAmount <= 0)
|
|
70
|
+
continue;
|
|
71
|
+
const totalAmount = unitAmount * selection.quantity;
|
|
72
|
+
totalCents += totalAmount;
|
|
73
|
+
lines.push({
|
|
74
|
+
kind: "base",
|
|
75
|
+
// Prefer the specific room/unit name ("Standard - Single"); fall back to
|
|
76
|
+
// the option name, then the product name.
|
|
77
|
+
label: selection.optionUnitName ?? optionsById.get(selection.optionId)?.name ?? input.product.name,
|
|
78
|
+
quantity: selection.quantity,
|
|
79
|
+
unitAmount,
|
|
80
|
+
totalAmount,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return { totalCents, lines };
|
|
84
|
+
}
|
|
85
|
+
export function bookingItemLinesFromOptionSelections(selections) {
|
|
86
|
+
const lines = selections.flatMap((selection) => selection.optionUnitId
|
|
87
|
+
? [
|
|
88
|
+
{
|
|
89
|
+
optionId: selection.optionId,
|
|
90
|
+
optionUnitId: selection.optionUnitId,
|
|
91
|
+
quantity: selection.quantity,
|
|
92
|
+
},
|
|
93
|
+
]
|
|
94
|
+
: []);
|
|
95
|
+
return lines.length > 0 ? lines : undefined;
|
|
96
|
+
}
|
|
97
|
+
export function applyAddonSelections(input) {
|
|
98
|
+
const extraLines = bookingExtraLinesFromAddonSelections({
|
|
99
|
+
addons: input.addons,
|
|
100
|
+
addonCatalog: input.addonCatalog,
|
|
101
|
+
currency: "EUR",
|
|
102
|
+
});
|
|
103
|
+
if (!extraLines?.length)
|
|
104
|
+
return input.priced;
|
|
105
|
+
const lines = [...input.priced.lines];
|
|
106
|
+
let totalCents = input.priced.totalCents;
|
|
107
|
+
for (const extra of extraLines) {
|
|
108
|
+
const unitAmount = extra.unitSellAmountCents ?? 0;
|
|
109
|
+
const quantity = extra.pricingMode === "per_person" || extra.pricedPerPerson
|
|
110
|
+
? Math.max(1, input.effectivePax) * extra.quantity
|
|
111
|
+
: extra.quantity;
|
|
112
|
+
const totalAmount = unitAmount * quantity;
|
|
113
|
+
if (totalAmount <= 0)
|
|
114
|
+
continue;
|
|
115
|
+
totalCents += totalAmount;
|
|
116
|
+
lines.push({
|
|
117
|
+
kind: "addon",
|
|
118
|
+
label: extra.name,
|
|
119
|
+
quantity,
|
|
120
|
+
unitAmount,
|
|
121
|
+
totalAmount,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return { totalCents, lines };
|
|
125
|
+
}
|
|
126
|
+
export function bookingExtraLinesFromAddonSelections(input) {
|
|
127
|
+
if (!Array.isArray(input.addons) || input.addons.length === 0)
|
|
128
|
+
return undefined;
|
|
129
|
+
const catalogById = new Map((input.addonCatalog ?? []).map((offer) => [offer.id, offer]));
|
|
130
|
+
const lines = input.addons.flatMap((selection) => {
|
|
131
|
+
const offer = catalogById.get(selection.extraId);
|
|
132
|
+
const quantity = typeof selection.quantity === "number" && Number.isFinite(selection.quantity)
|
|
133
|
+
? Math.floor(selection.quantity)
|
|
134
|
+
: 0;
|
|
135
|
+
if (!offer || quantity <= 0)
|
|
136
|
+
return [];
|
|
137
|
+
const unitSellAmountCents = offer.unitAmountCents ?? null;
|
|
138
|
+
const chargedQuantity = offer.pricingMode === "per_person" || offer.pricedPerPerson
|
|
139
|
+
? quantity * Math.max(1, input.quantityMultiplier ?? 1)
|
|
140
|
+
: quantity;
|
|
141
|
+
return [
|
|
142
|
+
{
|
|
143
|
+
productExtraId: offer.id,
|
|
144
|
+
name: offer.name,
|
|
145
|
+
description: offer.description ?? null,
|
|
146
|
+
pricingMode: offer.pricingMode ?? null,
|
|
147
|
+
pricedPerPerson: offer.pricedPerPerson ?? null,
|
|
148
|
+
quantity,
|
|
149
|
+
sellCurrency: offer.currency ?? input.currency,
|
|
150
|
+
unitSellAmountCents,
|
|
151
|
+
totalSellAmountCents: unitSellAmountCents == null ? null : unitSellAmountCents * chargedQuantity,
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
});
|
|
155
|
+
return lines.length > 0 ? lines : undefined;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Three-way price computation:
|
|
159
|
+
*
|
|
160
|
+
* 1. **Per-band** (preferred): when `resolvedPrice.unitPrices` matches
|
|
161
|
+
* at least one band with positive count, sum `pax[band] ×
|
|
162
|
+
* unit.sellAmountCents` for each matching band. One breakdown line
|
|
163
|
+
* per band.
|
|
164
|
+
*
|
|
165
|
+
* 2. **Per-booking**: when no per-band match but `baseSellAmountCents`
|
|
166
|
+
* is set, charge a single `base × paxCount` line.
|
|
167
|
+
*
|
|
168
|
+
* 3. **Fallback**: `product.sellAmountCents × paxCount`. Same shape as
|
|
169
|
+
* Phase A behavior, kept for bookings without an option/slot
|
|
170
|
+
* configured yet.
|
|
171
|
+
*/
|
|
172
|
+
export function priceQuote(input) {
|
|
173
|
+
const { product, resolvedPrice, pax, effectivePax } = input;
|
|
174
|
+
if (resolvedPrice && resolvedPrice.unitPrices.length > 0) {
|
|
175
|
+
const bandLines = [];
|
|
176
|
+
let total = 0;
|
|
177
|
+
for (const unit of resolvedPrice.unitPrices) {
|
|
178
|
+
if (!unit.travelerCategory)
|
|
179
|
+
continue;
|
|
180
|
+
const count = pax?.[unit.travelerCategory] ?? 0;
|
|
181
|
+
if (count <= 0)
|
|
182
|
+
continue;
|
|
183
|
+
const sell = unit.sellAmountCents ?? 0;
|
|
184
|
+
if (sell <= 0)
|
|
185
|
+
continue;
|
|
186
|
+
const lineTotal = sell * count;
|
|
187
|
+
total += lineTotal;
|
|
188
|
+
bandLines.push({
|
|
189
|
+
kind: "base",
|
|
190
|
+
label: `${product.name} — ${unit.travelerCategory}`,
|
|
191
|
+
quantity: count,
|
|
192
|
+
unitAmount: sell,
|
|
193
|
+
totalAmount: lineTotal,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (bandLines.length > 0) {
|
|
197
|
+
return { totalCents: total, lines: bandLines };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (resolvedPrice && resolvedPrice.baseSellAmountCents !== null) {
|
|
201
|
+
const unitCents = resolvedPrice.baseSellAmountCents;
|
|
202
|
+
const totalCents = unitCents * effectivePax;
|
|
203
|
+
return {
|
|
204
|
+
totalCents,
|
|
205
|
+
lines: [
|
|
206
|
+
{
|
|
207
|
+
kind: "base",
|
|
208
|
+
label: product.name,
|
|
209
|
+
quantity: effectivePax,
|
|
210
|
+
unitAmount: unitCents,
|
|
211
|
+
totalAmount: totalCents,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const unitCents = product.sellAmountCents ?? 0;
|
|
217
|
+
const totalCents = unitCents * effectivePax;
|
|
218
|
+
return {
|
|
219
|
+
totalCents,
|
|
220
|
+
lines: [
|
|
221
|
+
{
|
|
222
|
+
kind: "base",
|
|
223
|
+
label: product.name,
|
|
224
|
+
quantity: effectivePax,
|
|
225
|
+
unitAmount: unitCents,
|
|
226
|
+
totalAmount: totalCents,
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
export function readInitialStatus(parameters) {
|
|
232
|
+
const allowed = [
|
|
233
|
+
"draft",
|
|
234
|
+
"on_hold",
|
|
235
|
+
"awaiting_payment",
|
|
236
|
+
"confirmed",
|
|
237
|
+
"in_progress",
|
|
238
|
+
"completed",
|
|
239
|
+
"cancelled",
|
|
240
|
+
"expired",
|
|
241
|
+
];
|
|
242
|
+
const raw = parameters?.initialStatus;
|
|
243
|
+
return typeof raw === "string" && allowed.includes(raw)
|
|
244
|
+
? raw
|
|
245
|
+
: undefined;
|
|
246
|
+
}
|
|
247
|
+
export function extractInternalNotes(party) {
|
|
248
|
+
if (!party)
|
|
249
|
+
return undefined;
|
|
250
|
+
const v = party.internalNotes;
|
|
251
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
252
|
+
}
|
|
253
|
+
export function extractBillingParty(party) {
|
|
254
|
+
const directBilling = asRecord(party?.billing);
|
|
255
|
+
const travelerParty = asRecord(party?.travelerParty);
|
|
256
|
+
const envelopeBilling = asRecord(travelerParty?.billing);
|
|
257
|
+
const billing = envelopeBilling ?? directBilling;
|
|
258
|
+
const contact = asRecord(billing?.contact);
|
|
259
|
+
return {
|
|
260
|
+
personId: stringValue(party?.personId) ?? stringValue(billing?.personId),
|
|
261
|
+
organizationId: stringValue(party?.organizationId) ?? stringValue(billing?.organizationId),
|
|
262
|
+
contactFirstName: stringValue(contact?.firstName),
|
|
263
|
+
contactLastName: stringValue(contact?.lastName),
|
|
264
|
+
contactEmail: stringValue(contact?.email),
|
|
265
|
+
contactPhone: stringValue(contact?.phone),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
export function extractPartyTravelers(party) {
|
|
269
|
+
const travelerParty = asRecord(party?.travelerParty);
|
|
270
|
+
const travelers = Array.isArray(travelerParty?.travelers) ? travelerParty.travelers : [];
|
|
271
|
+
return travelers.map((traveler) => ({
|
|
272
|
+
personId: stringValue(asRecord(traveler)?.personId),
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
export function asRecord(value) {
|
|
276
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
277
|
+
? value
|
|
278
|
+
: null;
|
|
279
|
+
}
|
|
280
|
+
export function stringValue(value) {
|
|
281
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
282
|
+
}
|
|
283
|
+
export function extractTaxLines(pricing) {
|
|
284
|
+
const breakdown = pricing?.breakdown;
|
|
285
|
+
if (!breakdown || typeof breakdown !== "object" || Array.isArray(breakdown))
|
|
286
|
+
return undefined;
|
|
287
|
+
const taxes = breakdown.taxes;
|
|
288
|
+
if (!Array.isArray(taxes))
|
|
289
|
+
return undefined;
|
|
290
|
+
const lines = [];
|
|
291
|
+
for (const [index, tax] of taxes.entries()) {
|
|
292
|
+
if (!tax || typeof tax !== "object" || Array.isArray(tax))
|
|
293
|
+
continue;
|
|
294
|
+
const row = tax;
|
|
295
|
+
const amountCents = asFiniteInteger(row.amount);
|
|
296
|
+
const rate = typeof row.rate === "number" && Number.isFinite(row.rate) ? row.rate : null;
|
|
297
|
+
const currency = typeof pricing?.currency === "string" && pricing.currency.length === 3
|
|
298
|
+
? pricing.currency
|
|
299
|
+
: "EUR";
|
|
300
|
+
const name = typeof row.label === "string" && row.label.length > 0 ? row.label : "Tax";
|
|
301
|
+
if (!amountCents || amountCents <= 0)
|
|
302
|
+
continue;
|
|
303
|
+
const includedInPrice = row.includedInPrice === true || row.scope === "included";
|
|
304
|
+
lines.push({
|
|
305
|
+
code: typeof row.code === "string" ? row.code : null,
|
|
306
|
+
name,
|
|
307
|
+
scope: includedInPrice ? "included" : "excluded",
|
|
308
|
+
currency,
|
|
309
|
+
amountCents,
|
|
310
|
+
rateBasisPoints: rate == null ? null : Math.round(rate * 10_000),
|
|
311
|
+
includedInPrice,
|
|
312
|
+
sortOrder: index,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return lines.length ? lines : undefined;
|
|
316
|
+
}
|
|
317
|
+
export function resolveSellAmountCentsOverride(pricing) {
|
|
318
|
+
if (!pricing)
|
|
319
|
+
return null;
|
|
320
|
+
const breakdown = pricing.breakdown;
|
|
321
|
+
if (hasInclusiveTaxLine(breakdown)) {
|
|
322
|
+
const total = readBreakdownTotal(breakdown);
|
|
323
|
+
if (total != null)
|
|
324
|
+
return total;
|
|
325
|
+
}
|
|
326
|
+
return pricing.base_amount != null ? Math.round(pricing.base_amount) : null;
|
|
327
|
+
}
|
|
328
|
+
export function hasInclusiveTaxLine(breakdown) {
|
|
329
|
+
if (!breakdown || typeof breakdown !== "object" || Array.isArray(breakdown))
|
|
330
|
+
return false;
|
|
331
|
+
const taxes = breakdown.taxes;
|
|
332
|
+
if (!Array.isArray(taxes))
|
|
333
|
+
return false;
|
|
334
|
+
return taxes.some((tax) => {
|
|
335
|
+
if (!tax || typeof tax !== "object" || Array.isArray(tax))
|
|
336
|
+
return false;
|
|
337
|
+
const row = tax;
|
|
338
|
+
return row.includedInPrice === true || row.scope === "included";
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
export function readBreakdownTotal(breakdown) {
|
|
342
|
+
if (!breakdown || typeof breakdown !== "object" || Array.isArray(breakdown))
|
|
343
|
+
return null;
|
|
344
|
+
const total = breakdown.total;
|
|
345
|
+
return typeof total === "number" && Number.isFinite(total) ? Math.round(total) : null;
|
|
346
|
+
}
|
|
347
|
+
export function asFiniteInteger(value) {
|
|
348
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
349
|
+
return null;
|
|
350
|
+
return Math.round(value);
|
|
351
|
+
}
|
|
352
|
+
export function defaultBookingNumber() {
|
|
353
|
+
const ts = Date.now().toString(36).toUpperCase();
|
|
354
|
+
return `BK-${ts}`;
|
|
355
|
+
}
|