@voyant-travel/storefront 0.120.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.
- package/LICENSE +201 -0
- package/README.md +231 -0
- package/dist/booking-intents.d.ts +42 -0
- package/dist/booking-intents.d.ts.map +1 -0
- package/dist/booking-intents.js +83 -0
- package/dist/customer-portal/index.d.ts +16 -0
- package/dist/customer-portal/index.d.ts.map +1 -0
- package/dist/customer-portal/index.js +23 -0
- package/dist/customer-portal/route-runtime.d.ts +16 -0
- package/dist/customer-portal/route-runtime.d.ts.map +1 -0
- package/dist/customer-portal/route-runtime.js +27 -0
- package/dist/customer-portal/routes-public.d.ts +1936 -0
- package/dist/customer-portal/routes-public.d.ts.map +1 -0
- package/dist/customer-portal/routes-public.js +165 -0
- package/dist/customer-portal/routes.d.ts +43 -0
- package/dist/customer-portal/routes.d.ts.map +1 -0
- package/dist/customer-portal/routes.js +17 -0
- package/dist/customer-portal/service-public-impl.d.ts +138 -0
- package/dist/customer-portal/service-public-impl.d.ts.map +1 -0
- package/dist/customer-portal/service-public-impl.js +1808 -0
- package/dist/customer-portal/service-public.d.ts +2 -0
- package/dist/customer-portal/service-public.d.ts.map +1 -0
- package/dist/customer-portal/service-public.js +1 -0
- package/dist/customer-portal/validation-public/bookings.d.ts +551 -0
- package/dist/customer-portal/validation-public/bookings.d.ts.map +1 -0
- package/dist/customer-portal/validation-public/bookings.js +132 -0
- package/dist/customer-portal/validation-public/common.d.ts +162 -0
- package/dist/customer-portal/validation-public/common.d.ts.map +1 -0
- package/dist/customer-portal/validation-public/common.js +139 -0
- package/dist/customer-portal/validation-public/profile.d.ts +749 -0
- package/dist/customer-portal/validation-public/profile.d.ts.map +1 -0
- package/dist/customer-portal/validation-public/profile.js +308 -0
- package/dist/customer-portal/validation-public.d.ts +3 -0
- package/dist/customer-portal/validation-public.d.ts.map +1 -0
- package/dist/customer-portal/validation-public.js +2 -0
- package/dist/guest-booking-guard.d.ts +24 -0
- package/dist/guest-booking-guard.d.ts.map +1 -0
- package/dist/guest-booking-guard.js +55 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/product-extra-ref.d.ts +238 -0
- package/dist/product-extra-ref.d.ts.map +1 -0
- package/dist/product-extra-ref.js +22 -0
- package/dist/routes-admin.d.ts +220 -0
- package/dist/routes-admin.d.ts.map +1 -0
- package/dist/routes-admin.js +28 -0
- package/dist/routes-public.d.ts +1475 -0
- package/dist/routes-public.d.ts.map +1 -0
- package/dist/routes-public.js +362 -0
- package/dist/service-booking-session-bootstrap.d.ts +227 -0
- package/dist/service-booking-session-bootstrap.d.ts.map +1 -0
- package/dist/service-booking-session-bootstrap.js +287 -0
- package/dist/service-boundary-resource-sql.d.ts +18 -0
- package/dist/service-boundary-resource-sql.d.ts.map +1 -0
- package/dist/service-boundary-resource-sql.js +73 -0
- package/dist/service-boundary-sql.d.ts +103 -0
- package/dist/service-boundary-sql.d.ts.map +1 -0
- package/dist/service-boundary-sql.js +307 -0
- package/dist/service-departures-core.d.ts +41 -0
- package/dist/service-departures-core.d.ts.map +1 -0
- package/dist/service-departures-core.js +92 -0
- package/dist/service-departures-extensions.d.ts +46 -0
- package/dist/service-departures-extensions.d.ts.map +1 -0
- package/dist/service-departures-extensions.js +81 -0
- package/dist/service-departures-offers.d.ts +220 -0
- package/dist/service-departures-offers.d.ts.map +1 -0
- package/dist/service-departures-offers.js +177 -0
- package/dist/service-departures-price-preview.d.ts +306 -0
- package/dist/service-departures-price-preview.d.ts.map +1 -0
- package/dist/service-departures-price-preview.js +383 -0
- package/dist/service-departures-pricing-context.d.ts +115 -0
- package/dist/service-departures-pricing-context.d.ts.map +1 -0
- package/dist/service-departures-pricing-context.js +237 -0
- package/dist/service-departures-pricing.d.ts +5 -0
- package/dist/service-departures-pricing.d.ts.map +1 -0
- package/dist/service-departures-pricing.js +4 -0
- package/dist/service-departures.d.ts +192 -0
- package/dist/service-departures.d.ts.map +1 -0
- package/dist/service-departures.js +213 -0
- package/dist/service-intake.d.ts +130 -0
- package/dist/service-intake.d.ts.map +1 -0
- package/dist/service-intake.js +274 -0
- package/dist/service-transport-eligibility.d.ts +10 -0
- package/dist/service-transport-eligibility.d.ts.map +1 -0
- package/dist/service-transport-eligibility.js +198 -0
- package/dist/service.d.ts +1062 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +332 -0
- package/dist/transport-eligibility.d.ts +4 -0
- package/dist/transport-eligibility.d.ts.map +1 -0
- package/dist/transport-eligibility.js +2 -0
- package/dist/validation/departures.d.ts +1669 -0
- package/dist/validation/departures.d.ts.map +1 -0
- package/dist/validation/departures.js +397 -0
- package/dist/validation/intake.d.ts +147 -0
- package/dist/validation/intake.d.ts.map +1 -0
- package/dist/validation/intake.js +69 -0
- package/dist/validation/offers.d.ts +340 -0
- package/dist/validation/offers.d.ts.map +1 -0
- package/dist/validation/offers.js +117 -0
- package/dist/validation-settings.d.ts +609 -0
- package/dist/validation-settings.d.ts.map +1 -0
- package/dist/validation-settings.js +235 -0
- package/dist/validation-transport-eligibility.d.ts +314 -0
- package/dist/validation-transport-eligibility.d.ts.map +1 -0
- package/dist/validation-transport-eligibility.js +97 -0
- package/dist/validation.d.ts +6 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +4 -0
- package/dist/verification/index.d.ts +12 -0
- package/dist/verification/index.d.ts.map +1 -0
- package/dist/verification/index.js +18 -0
- package/dist/verification/routes-public.d.ts +121 -0
- package/dist/verification/routes-public.d.ts.map +1 -0
- package/dist/verification/routes-public.js +125 -0
- package/dist/verification/schema.d.ts +273 -0
- package/dist/verification/schema.d.ts.map +1 -0
- package/dist/verification/schema.js +50 -0
- package/dist/verification/service.d.ts +114 -0
- package/dist/verification/service.d.ts.map +1 -0
- package/dist/verification/service.js +283 -0
- package/dist/verification/validation.d.ts +98 -0
- package/dist/verification/validation.d.ts.map +1 -0
- package/dist/verification/validation.js +54 -0
- package/package.json +148 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { publicBookingsService, resolveSessionPricingSnapshot } from "@voyant-travel/bookings";
|
|
2
|
+
import { computePaymentSchedule, financeService, noDepositPolicy, } from "@voyant-travel/finance";
|
|
3
|
+
import { loadStorefrontAvailabilitySlot } from "./service-boundary-sql.js";
|
|
4
|
+
function normalizeDate(value) {
|
|
5
|
+
if (!value) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return value instanceof Date ? value.toISOString().slice(0, 10) : value;
|
|
9
|
+
}
|
|
10
|
+
function normalizeDateTime(value) {
|
|
11
|
+
if (!value) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
15
|
+
}
|
|
16
|
+
function isExpired(value, now) {
|
|
17
|
+
if (!value) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const expiresAt = new Date(value);
|
|
21
|
+
return Number.isNaN(expiresAt.getTime()) || expiresAt.getTime() <= now.getTime();
|
|
22
|
+
}
|
|
23
|
+
function resolveTierAmount(tiers, quantity, fallbackAmount) {
|
|
24
|
+
const tier = tiers.find((candidate) => quantity >= candidate.minQuantity &&
|
|
25
|
+
(candidate.maxQuantity === null || quantity <= candidate.maxQuantity));
|
|
26
|
+
return tier?.sellAmountCents ?? fallbackAmount;
|
|
27
|
+
}
|
|
28
|
+
function computeLineTotal(pricingMode, unitSellAmountCents, quantity, fallbackAmount) {
|
|
29
|
+
switch (pricingMode) {
|
|
30
|
+
case "free":
|
|
31
|
+
case "included":
|
|
32
|
+
return 0;
|
|
33
|
+
case "on_request":
|
|
34
|
+
return null;
|
|
35
|
+
case "per_unit":
|
|
36
|
+
case "per_person":
|
|
37
|
+
return unitSellAmountCents === null ? null : unitSellAmountCents * quantity;
|
|
38
|
+
default:
|
|
39
|
+
return unitSellAmountCents ?? fallbackAmount;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function serializePaymentPlan(policy, requiresFullPayment) {
|
|
43
|
+
return {
|
|
44
|
+
source: "storefront_default",
|
|
45
|
+
depositKind: policy.deposit.kind,
|
|
46
|
+
depositPercent: policy.deposit.kind === "percent" ? (policy.deposit.percent ?? 0) : null,
|
|
47
|
+
depositAmountCents: policy.deposit.kind === "fixed_cents" ? (policy.deposit.amountCents ?? 0) : null,
|
|
48
|
+
requiresFullPayment,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async function resolveAvailabilitySlot(db, slotId) {
|
|
52
|
+
return loadStorefrontAvailabilitySlot(db, slotId);
|
|
53
|
+
}
|
|
54
|
+
async function previewBootstrapPricing(db, input, slot) {
|
|
55
|
+
const pricedItems = [];
|
|
56
|
+
let resolvedCatalogId = input.catalogId ?? null;
|
|
57
|
+
let resolvedCurrency = input.session.sellCurrency;
|
|
58
|
+
for (const [index, item] of input.session.items.entries()) {
|
|
59
|
+
const productId = item.productId ?? slot.productId;
|
|
60
|
+
const optionId = item.optionId ?? slot.optionId ?? undefined;
|
|
61
|
+
if (!productId) {
|
|
62
|
+
return { status: "pricing_unavailable" };
|
|
63
|
+
}
|
|
64
|
+
const snapshot = await resolveSessionPricingSnapshot(db, productId, {
|
|
65
|
+
catalogId: input.catalogId,
|
|
66
|
+
departureId: input.slotId,
|
|
67
|
+
optionId,
|
|
68
|
+
});
|
|
69
|
+
if (!snapshot) {
|
|
70
|
+
return { status: "pricing_unavailable" };
|
|
71
|
+
}
|
|
72
|
+
resolvedCatalogId = snapshot.catalog.id;
|
|
73
|
+
resolvedCurrency = snapshot.catalog.currencyCode ?? input.session.sellCurrency;
|
|
74
|
+
const option = snapshot.options.find((candidate) => candidate.id === optionId) ?? snapshot.options[0] ?? null;
|
|
75
|
+
if (!option) {
|
|
76
|
+
return { status: "pricing_unavailable" };
|
|
77
|
+
}
|
|
78
|
+
const rule = snapshot.rules.find((candidate) => candidate.optionId === option.id && candidate.isDefault) ??
|
|
79
|
+
snapshot.rules.find((candidate) => candidate.optionId === option.id) ??
|
|
80
|
+
null;
|
|
81
|
+
if (!rule) {
|
|
82
|
+
return { status: "pricing_unavailable" };
|
|
83
|
+
}
|
|
84
|
+
const selectedUnitId = item.optionUnitId ?? null;
|
|
85
|
+
const pricingCategoryId = item.pricingCategoryId ?? null;
|
|
86
|
+
const ruleUnitPrices = snapshot.unitPrices.filter((candidate) => candidate.optionPriceRuleId === rule.id);
|
|
87
|
+
const unitPriceCandidates = ruleUnitPrices.filter((candidate) => {
|
|
88
|
+
if (selectedUnitId && candidate.unitId !== selectedUnitId) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (pricingCategoryId && candidate.pricingCategoryId !== pricingCategoryId) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (candidate.minQuantity !== null && item.quantity < candidate.minQuantity) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (candidate.maxQuantity !== null && item.quantity > candidate.maxQuantity) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
});
|
|
102
|
+
const fallbackUnitPrice = !pricingCategoryId && !selectedUnitId
|
|
103
|
+
? (ruleUnitPrices.find((candidate) => candidate.pricingCategoryId === null &&
|
|
104
|
+
(candidate.minQuantity === null || item.quantity >= candidate.minQuantity) &&
|
|
105
|
+
(candidate.maxQuantity === null || item.quantity <= candidate.maxQuantity)) ?? null)
|
|
106
|
+
: null;
|
|
107
|
+
const unitPrice = unitPriceCandidates[0] ?? fallbackUnitPrice;
|
|
108
|
+
if ((selectedUnitId || ruleUnitPrices.length > 0) &&
|
|
109
|
+
!unitPrice &&
|
|
110
|
+
rule.pricingMode !== "per_booking") {
|
|
111
|
+
return { status: "pricing_unavailable" };
|
|
112
|
+
}
|
|
113
|
+
const unitSellAmountCents = unitPrice
|
|
114
|
+
? resolveTierAmount(unitPrice.tiers, item.quantity, unitPrice.sellAmountCents)
|
|
115
|
+
: rule.baseSellAmountCents;
|
|
116
|
+
const pricingMode = unitPrice?.pricingMode ?? rule.pricingMode;
|
|
117
|
+
const totalSellAmountCents = computeLineTotal(pricingMode, unitSellAmountCents, item.quantity, rule.baseSellAmountCents);
|
|
118
|
+
if (totalSellAmountCents === null) {
|
|
119
|
+
return { status: "pricing_unavailable" };
|
|
120
|
+
}
|
|
121
|
+
pricedItems.push({
|
|
122
|
+
inputIndex: index,
|
|
123
|
+
itemId: `input:${index}`,
|
|
124
|
+
title: item.title,
|
|
125
|
+
productId,
|
|
126
|
+
optionId: option.id,
|
|
127
|
+
optionUnitId: selectedUnitId,
|
|
128
|
+
optionUnitName: unitPrice?.unitName ?? null,
|
|
129
|
+
optionUnitType: unitPrice?.unitType ?? null,
|
|
130
|
+
pricingCategoryId,
|
|
131
|
+
quantity: item.quantity,
|
|
132
|
+
pricingMode,
|
|
133
|
+
unitSellAmountCents,
|
|
134
|
+
totalSellAmountCents,
|
|
135
|
+
warnings: [],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
status: "ok",
|
|
140
|
+
pricing: {
|
|
141
|
+
sessionId: "pending",
|
|
142
|
+
catalogId: resolvedCatalogId,
|
|
143
|
+
currencyCode: resolvedCurrency,
|
|
144
|
+
totalSellAmountCents: pricedItems.reduce((total, item) => total + (item.totalSellAmountCents ?? 0), 0),
|
|
145
|
+
items: pricedItems,
|
|
146
|
+
warnings: [],
|
|
147
|
+
appliedToSession: true,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async function resolveBootstrapPaymentPolicy(input, context, options) {
|
|
152
|
+
return ((await options?.resolvePaymentPolicy?.({
|
|
153
|
+
...input,
|
|
154
|
+
...context,
|
|
155
|
+
})) ??
|
|
156
|
+
options?.paymentPolicy ??
|
|
157
|
+
noDepositPolicy);
|
|
158
|
+
}
|
|
159
|
+
export async function bootstrapStorefrontBookingSession(context, input, options, userId) {
|
|
160
|
+
const now = options?.today ?? new Date();
|
|
161
|
+
if (isExpired(input.quote.expiresAt, now)) {
|
|
162
|
+
return { status: "stale_quote" };
|
|
163
|
+
}
|
|
164
|
+
const [departure, slot] = await Promise.all([
|
|
165
|
+
resolveAvailabilitySlot(context.db, input.departureId),
|
|
166
|
+
resolveAvailabilitySlot(context.db, input.slotId),
|
|
167
|
+
]);
|
|
168
|
+
if (!departure) {
|
|
169
|
+
return { status: "departure_not_found" };
|
|
170
|
+
}
|
|
171
|
+
if (!slot) {
|
|
172
|
+
return { status: "slot_not_found" };
|
|
173
|
+
}
|
|
174
|
+
if (departure.id !== slot.id) {
|
|
175
|
+
return { status: "invalid_slot" };
|
|
176
|
+
}
|
|
177
|
+
const mismatchedItem = input.session.items.find((item) => item.availabilitySlotId !== input.slotId ||
|
|
178
|
+
(item.productId !== undefined &&
|
|
179
|
+
item.productId !== null &&
|
|
180
|
+
item.productId !== slot.productId) ||
|
|
181
|
+
(slot.optionId !== null &&
|
|
182
|
+
item.optionId !== undefined &&
|
|
183
|
+
item.optionId !== null &&
|
|
184
|
+
item.optionId !== slot.optionId));
|
|
185
|
+
if (mismatchedItem) {
|
|
186
|
+
return { status: "invalid_slot" };
|
|
187
|
+
}
|
|
188
|
+
const preview = await previewBootstrapPricing(context.db, input, {
|
|
189
|
+
productId: slot.productId,
|
|
190
|
+
optionId: slot.optionId ?? null,
|
|
191
|
+
});
|
|
192
|
+
if (preview.status !== "ok") {
|
|
193
|
+
return preview;
|
|
194
|
+
}
|
|
195
|
+
if (preview.pricing.currencyCode !== input.quote.currencyCode ||
|
|
196
|
+
preview.pricing.totalSellAmountCents !== input.quote.totalSellAmountCents) {
|
|
197
|
+
return {
|
|
198
|
+
status: "stale_quote",
|
|
199
|
+
repricing: {
|
|
200
|
+
originalQuote: input.quote,
|
|
201
|
+
current: preview.pricing,
|
|
202
|
+
deltaAmountCents: preview.pricing.totalSellAmountCents - input.quote.totalSellAmountCents,
|
|
203
|
+
staleQuote: true,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const createResult = await publicBookingsService.createSession(context.db, {
|
|
208
|
+
...input.session,
|
|
209
|
+
sellCurrency: preview.pricing.currencyCode,
|
|
210
|
+
sellAmountCents: preview.pricing.totalSellAmountCents,
|
|
211
|
+
items: input.session.items.map((item, index) => {
|
|
212
|
+
const pricedItem = preview.pricing.items[index];
|
|
213
|
+
return {
|
|
214
|
+
...item,
|
|
215
|
+
sellCurrency: preview.pricing.currencyCode,
|
|
216
|
+
productId: pricedItem?.productId ?? item.productId,
|
|
217
|
+
optionId: pricedItem?.optionId ?? item.optionId,
|
|
218
|
+
optionUnitId: pricedItem?.optionUnitId ?? item.optionUnitId,
|
|
219
|
+
pricingCategoryId: pricedItem?.pricingCategoryId ?? item.pricingCategoryId,
|
|
220
|
+
unitSellAmountCents: pricedItem?.unitSellAmountCents ?? item.unitSellAmountCents,
|
|
221
|
+
totalSellAmountCents: pricedItem?.totalSellAmountCents ?? item.totalSellAmountCents,
|
|
222
|
+
};
|
|
223
|
+
}),
|
|
224
|
+
}, userId);
|
|
225
|
+
if (createResult.status !== "ok") {
|
|
226
|
+
return createResult;
|
|
227
|
+
}
|
|
228
|
+
if (!("session" in createResult)) {
|
|
229
|
+
return { status: "not_found" };
|
|
230
|
+
}
|
|
231
|
+
const createdSession = createResult.session;
|
|
232
|
+
const policy = await resolveBootstrapPaymentPolicy(input, context, options);
|
|
233
|
+
const computedSchedule = computePaymentSchedule({
|
|
234
|
+
totalCents: createdSession.sellAmountCents ?? preview.pricing.totalSellAmountCents,
|
|
235
|
+
currency: createdSession.sellCurrency,
|
|
236
|
+
departureDate: normalizeDate(slot.dateLocal) ?? normalizeDate(slot.startsAt),
|
|
237
|
+
today: now,
|
|
238
|
+
}, policy);
|
|
239
|
+
const persistedSchedule = (await financeService.applyComputedPaymentSchedule(context.db, createdSession.sessionId, computedSchedule)) ?? [];
|
|
240
|
+
const availability = (await resolveAvailabilitySlot(context.db, input.slotId)) ?? slot;
|
|
241
|
+
const currentItems = preview.pricing.items.map(({ inputIndex: _inputIndex, ...item }, index) => ({
|
|
242
|
+
...item,
|
|
243
|
+
itemId: createdSession.items[index]?.id ?? item.itemId,
|
|
244
|
+
}));
|
|
245
|
+
const current = {
|
|
246
|
+
...preview.pricing,
|
|
247
|
+
sessionId: createdSession.sessionId,
|
|
248
|
+
items: currentItems,
|
|
249
|
+
};
|
|
250
|
+
return {
|
|
251
|
+
status: "ok",
|
|
252
|
+
bootstrap: {
|
|
253
|
+
session: createdSession,
|
|
254
|
+
paymentPlan: serializePaymentPlan(policy, computedSchedule.length === 1 && computedSchedule[0]?.scheduleType === "full"),
|
|
255
|
+
paymentSchedule: persistedSchedule.map((schedule) => ({
|
|
256
|
+
id: schedule.id,
|
|
257
|
+
scheduleType: schedule.scheduleType,
|
|
258
|
+
status: schedule.status,
|
|
259
|
+
dueDate: normalizeDate(schedule.dueDate),
|
|
260
|
+
currency: schedule.currency,
|
|
261
|
+
amountCents: schedule.amountCents,
|
|
262
|
+
notes: schedule.notes ?? null,
|
|
263
|
+
})),
|
|
264
|
+
repricing: {
|
|
265
|
+
originalQuote: input.quote,
|
|
266
|
+
current,
|
|
267
|
+
deltaAmountCents: 0,
|
|
268
|
+
staleQuote: false,
|
|
269
|
+
},
|
|
270
|
+
availability: {
|
|
271
|
+
departureId: input.departureId,
|
|
272
|
+
slotId: input.slotId,
|
|
273
|
+
productId: availability.productId,
|
|
274
|
+
optionId: availability.optionId ?? null,
|
|
275
|
+
dateLocal: availability.dateLocal ?? null,
|
|
276
|
+
startsAt: normalizeDateTime(availability.startsAt),
|
|
277
|
+
endsAt: normalizeDateTime(availability.endsAt),
|
|
278
|
+
timezone: availability.timezone,
|
|
279
|
+
status: availability.status,
|
|
280
|
+
capacity: availability.initialPax ?? null,
|
|
281
|
+
remaining: availability.unlimited ? null : (availability.remainingPax ?? null),
|
|
282
|
+
},
|
|
283
|
+
allocation: createdSession.allocations,
|
|
284
|
+
currency: createdSession.sellCurrency,
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
export interface StorefrontSlotResourceAvailability {
|
|
3
|
+
id: string;
|
|
4
|
+
slotId: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
label: string | null;
|
|
7
|
+
refType: string | null;
|
|
8
|
+
refId: string | null;
|
|
9
|
+
parentId: string | null;
|
|
10
|
+
capacity: number;
|
|
11
|
+
assigned: number;
|
|
12
|
+
available: number;
|
|
13
|
+
flags: Record<string, unknown>;
|
|
14
|
+
sortOrder: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function getStorefrontSlotResourceAvailability(db: PostgresJsDatabase, slotId: string): Promise<StorefrontSlotResourceAvailability[]>;
|
|
17
|
+
export declare function getStorefrontSlotsResourceAvailability(db: PostgresJsDatabase, slotIds: string[]): Promise<Map<string, StorefrontSlotResourceAvailability[]>>;
|
|
18
|
+
//# sourceMappingURL=service-boundary-resource-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-boundary-resource-sql.d.ts","sourceRoot":"","sources":["../src/service-boundary-resource-sql.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,MAAM,WAAW,kCAAkC;IACjD,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAA;CAClB;AAsCD,wBAAsB,qCAAqC,CACzD,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,kCAAkC,EAAE,CAAC,CAG/C;AAED,wBAAsB,sCAAsC,CAC1D,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,kCAAkC,EAAE,CAAC,CAAC,CA2D5D"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { sql } from "drizzle-orm";
|
|
2
|
+
function isRowsResult(value) {
|
|
3
|
+
return (typeof value === "object" && value !== null && Array.isArray(value.rows));
|
|
4
|
+
}
|
|
5
|
+
async function executeBoundaryRows(db, query) {
|
|
6
|
+
const result = await db.execute(query);
|
|
7
|
+
return (Array.isArray(result) ? result : isRowsResult(result) ? result.rows : []);
|
|
8
|
+
}
|
|
9
|
+
function sqlList(values) {
|
|
10
|
+
// agent-quality: raw-sql reviewed -- owner: storefront; callers pass only parameter-bound scalar ids into this SQL fragment.
|
|
11
|
+
return sql.join(values.map((value) => sql `${value}`), sql `, `);
|
|
12
|
+
}
|
|
13
|
+
export async function getStorefrontSlotResourceAvailability(db, slotId) {
|
|
14
|
+
const map = await getStorefrontSlotsResourceAvailability(db, [slotId]);
|
|
15
|
+
return map.get(slotId) ?? [];
|
|
16
|
+
}
|
|
17
|
+
export async function getStorefrontSlotsResourceAvailability(db, slotIds) {
|
|
18
|
+
const uniqueIds = [...new Set(slotIds)].filter(Boolean);
|
|
19
|
+
if (uniqueIds.length === 0)
|
|
20
|
+
return new Map();
|
|
21
|
+
const rows = await executeBoundaryRows(db,
|
|
22
|
+
// agent-quality: raw-sql reviewed -- owner: storefront; slot resource manifests are read-only and slot ids are parameter-bound.
|
|
23
|
+
sql `
|
|
24
|
+
SELECT
|
|
25
|
+
ar.id,
|
|
26
|
+
ar.slot_id,
|
|
27
|
+
ar.kind,
|
|
28
|
+
ar.label,
|
|
29
|
+
ar.ref_type,
|
|
30
|
+
ar.ref_id,
|
|
31
|
+
ar.parent_id,
|
|
32
|
+
ar.capacity,
|
|
33
|
+
COALESCE(usage.assigned, 0)::int AS assigned,
|
|
34
|
+
ar.flags,
|
|
35
|
+
ar.sort_order
|
|
36
|
+
FROM allocation_resources ar
|
|
37
|
+
LEFT JOIN LATERAL (
|
|
38
|
+
SELECT COUNT(DISTINCT btd.traveler_id)::int AS assigned
|
|
39
|
+
FROM booking_traveler_travel_details btd
|
|
40
|
+
JOIN booking_travelers bt ON bt.id = btd.traveler_id
|
|
41
|
+
JOIN booking_allocations ba ON ba.booking_id = bt.booking_id
|
|
42
|
+
JOIN bookings b ON b.id = bt.booking_id
|
|
43
|
+
WHERE btd.allocations ->> ar.kind = ar.id
|
|
44
|
+
AND ba.availability_slot_id = ar.slot_id
|
|
45
|
+
AND b.status IN ('draft', 'pending', 'confirmed', 'checked_in')
|
|
46
|
+
AND ba.status IN ('held', 'confirmed', 'fulfilled')
|
|
47
|
+
) usage ON true
|
|
48
|
+
WHERE ar.slot_id IN (${sqlList(uniqueIds)})
|
|
49
|
+
ORDER BY ar.slot_id, ar.kind, ar.sort_order, ar.created_at
|
|
50
|
+
`);
|
|
51
|
+
const out = new Map();
|
|
52
|
+
for (const row of rows) {
|
|
53
|
+
const capacity = Number(row.capacity);
|
|
54
|
+
const assigned = Number(row.assigned ?? 0);
|
|
55
|
+
const list = out.get(row.slot_id) ?? [];
|
|
56
|
+
list.push({
|
|
57
|
+
id: row.id,
|
|
58
|
+
slotId: row.slot_id,
|
|
59
|
+
kind: row.kind,
|
|
60
|
+
label: row.label,
|
|
61
|
+
refType: row.ref_type,
|
|
62
|
+
refId: row.ref_id,
|
|
63
|
+
parentId: row.parent_id,
|
|
64
|
+
capacity,
|
|
65
|
+
assigned,
|
|
66
|
+
available: Math.max(0, capacity - assigned),
|
|
67
|
+
flags: row.flags ?? {},
|
|
68
|
+
sortOrder: Number(row.sort_order),
|
|
69
|
+
});
|
|
70
|
+
out.set(row.slot_id, list);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
export { getStorefrontSlotResourceAvailability, getStorefrontSlotsResourceAvailability, type StorefrontSlotResourceAvailability, } from "./service-boundary-resource-sql.js";
|
|
3
|
+
export type StorefrontSlotStatus = "open" | "closed" | "sold_out" | "cancelled";
|
|
4
|
+
export interface StorefrontSlotRow {
|
|
5
|
+
id: string;
|
|
6
|
+
productId: string;
|
|
7
|
+
itineraryId: string | null;
|
|
8
|
+
optionId: string | null;
|
|
9
|
+
startTimeId: string | null;
|
|
10
|
+
dateLocal: Date | string;
|
|
11
|
+
startsAt: Date | string;
|
|
12
|
+
endsAt: Date | string | null;
|
|
13
|
+
timezone: string;
|
|
14
|
+
status: StorefrontSlotStatus;
|
|
15
|
+
unlimited: boolean;
|
|
16
|
+
initialPax: number | null;
|
|
17
|
+
remainingPax: number | null;
|
|
18
|
+
remainingResources: number | null;
|
|
19
|
+
pastCutoff: boolean;
|
|
20
|
+
tooEarly: boolean;
|
|
21
|
+
nights: number | null;
|
|
22
|
+
days: number | null;
|
|
23
|
+
startTimeLabel: string | null;
|
|
24
|
+
startTimeLocal: string | null;
|
|
25
|
+
durationMinutes: number | null;
|
|
26
|
+
}
|
|
27
|
+
export interface StorefrontProductPricingFacts {
|
|
28
|
+
id: string;
|
|
29
|
+
sellCurrency: string;
|
|
30
|
+
sellAmountCents: number | null;
|
|
31
|
+
capacityMode: string;
|
|
32
|
+
}
|
|
33
|
+
export interface StorefrontProductOptionFacts {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
description: string | null;
|
|
37
|
+
}
|
|
38
|
+
export interface StorefrontOptionUnitFacts {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
unitType: string;
|
|
42
|
+
minAge: number | null;
|
|
43
|
+
maxAge: number | null;
|
|
44
|
+
occupancyMin: number | null;
|
|
45
|
+
occupancyMax: number | null;
|
|
46
|
+
isRequired: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface StorefrontItineraryDay {
|
|
49
|
+
id: string;
|
|
50
|
+
dayNumber: number;
|
|
51
|
+
title: string | null;
|
|
52
|
+
description: string | null;
|
|
53
|
+
}
|
|
54
|
+
export interface StorefrontItineraryDayService {
|
|
55
|
+
id: string;
|
|
56
|
+
dayId: string;
|
|
57
|
+
name: string;
|
|
58
|
+
description: string | null;
|
|
59
|
+
sortOrder: number | null;
|
|
60
|
+
}
|
|
61
|
+
export interface StorefrontItineraryDayMedia {
|
|
62
|
+
id: string;
|
|
63
|
+
dayId: string | null;
|
|
64
|
+
url: string;
|
|
65
|
+
isCover: boolean;
|
|
66
|
+
sortOrder: number;
|
|
67
|
+
}
|
|
68
|
+
export declare function listStorefrontSlots(db: PostgresJsDatabase, filters?: {
|
|
69
|
+
productId?: string;
|
|
70
|
+
slotId?: string;
|
|
71
|
+
optionId?: string;
|
|
72
|
+
status?: StorefrontSlotStatus;
|
|
73
|
+
dateFrom?: string;
|
|
74
|
+
dateTo?: string;
|
|
75
|
+
limit?: number;
|
|
76
|
+
offset?: number;
|
|
77
|
+
includeCancelled?: boolean;
|
|
78
|
+
}): Promise<StorefrontSlotRow[]>;
|
|
79
|
+
export declare function countStorefrontSlots(db: PostgresJsDatabase, filters?: {
|
|
80
|
+
productId?: string;
|
|
81
|
+
slotId?: string;
|
|
82
|
+
optionId?: string;
|
|
83
|
+
status?: StorefrontSlotStatus;
|
|
84
|
+
dateFrom?: string;
|
|
85
|
+
dateTo?: string;
|
|
86
|
+
includeCancelled?: boolean;
|
|
87
|
+
}): Promise<number>;
|
|
88
|
+
export declare function loadStorefrontAvailabilitySlot(db: PostgresJsDatabase, slotId: string): Promise<StorefrontSlotRow | null>;
|
|
89
|
+
export declare function listMeetingPointsByProductIds(db: PostgresJsDatabase, productIds: string[]): Promise<Map<string, string>>;
|
|
90
|
+
export declare function listDefaultItineraryIdsByProductIds(db: PostgresJsDatabase, productIds: string[]): Promise<Map<string, string>>;
|
|
91
|
+
export declare function loadProductPricingFacts(db: PostgresJsDatabase, productId: string): Promise<StorefrontProductPricingFacts | null>;
|
|
92
|
+
export declare function loadProductOptionFacts(db: PostgresJsDatabase, input: {
|
|
93
|
+
productId: string;
|
|
94
|
+
optionId?: string | null;
|
|
95
|
+
}): Promise<StorefrontProductOptionFacts | null>;
|
|
96
|
+
export declare function listOptionUnitFacts(db: PostgresJsDatabase, optionId: string): Promise<StorefrontOptionUnitFacts[]>;
|
|
97
|
+
export declare function listItineraryDays(db: PostgresJsDatabase, itineraryId: string): Promise<StorefrontItineraryDay[]>;
|
|
98
|
+
export declare function listItineraryDayServices(db: PostgresJsDatabase, dayIds: string[]): Promise<StorefrontItineraryDayService[]>;
|
|
99
|
+
export declare function listItineraryDayMedia(db: PostgresJsDatabase, input: {
|
|
100
|
+
productId: string;
|
|
101
|
+
dayIds: string[];
|
|
102
|
+
}): Promise<StorefrontItineraryDayMedia[]>;
|
|
103
|
+
//# sourceMappingURL=service-boundary-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-boundary-sql.d.ts","sourceRoot":"","sources":["../src/service-boundary-sql.ts"],"names":[],"mappings":"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"}
|