@voyantjs/octo 0.2.0 → 0.3.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/dist/service.js CHANGED
@@ -1,640 +1,20 @@
1
- import { availabilitySlots, availabilityStartTimes } from "@voyantjs/availability/schema";
2
- import { bookingsService } from "@voyantjs/bookings";
3
- import { bookingAllocations, bookingFulfillments, bookingItemParticipants, bookingItems, bookingParticipants, bookingRedemptionEvents, bookingSupplierStatuses, bookings, } from "@voyantjs/bookings/schema";
4
- import { productsService } from "@voyantjs/products";
5
- import { optionUnits, productCapabilities, productDeliveryFormats, productFaqs, productFeatures, productLocations, productOptions, products, } from "@voyantjs/products/schema";
6
- import { offers, orders } from "@voyantjs/transactions/schema";
7
- import { and, asc, eq, gte, inArray, lte, sql } from "drizzle-orm";
8
- import { bookingTransactionDetailsRef } from "./transactions-ref.js";
9
- function toIsoString(value) {
10
- return value ? value.toISOString() : null;
11
- }
12
- function formatLocalDateTime(value, timeZone) {
13
- const parts = new Intl.DateTimeFormat("en-GB", {
14
- timeZone,
15
- hour12: false,
16
- year: "numeric",
17
- month: "2-digit",
18
- day: "2-digit",
19
- hour: "2-digit",
20
- minute: "2-digit",
21
- second: "2-digit",
22
- }).formatToParts(value);
23
- const lookup = Object.fromEntries(parts.map((part) => [part.type, part.value]));
24
- return `${lookup.year}-${lookup.month}-${lookup.day}T${lookup.hour}:${lookup.minute}:${lookup.second}`;
25
- }
26
- function buildProjectedAvailability(slot, product) {
27
- const timeZone = slot.timezone || product?.timezone || "UTC";
28
- return {
29
- id: slot.id,
30
- productId: slot.productId,
31
- optionId: slot.optionId,
32
- localDateTimeStart: formatLocalDateTime(slot.startsAt, timeZone),
33
- localDateTimeEnd: slot.endsAt ? formatLocalDateTime(slot.endsAt, timeZone) : null,
34
- timeZone,
35
- status: deriveOctoAvailabilityStatus(slot, product?.capacityMode),
36
- vacancies: slot.unlimited ? null : slot.remainingPax,
37
- capacity: slot.unlimited ? null : slot.initialPax,
38
- };
39
- }
40
- export function inferOctoAvailabilityType(bookingMode) {
41
- return bookingMode === "open" ? "OPENING_HOURS" : "START_TIME";
42
- }
43
- export function inferOctoUnitType(unit) {
44
- const haystack = `${unit.code ?? ""} ${unit.name}`.toLowerCase();
45
- if (haystack.includes("adult"))
46
- return "ADULT";
47
- if (haystack.includes("child"))
48
- return "CHILD";
49
- if (haystack.includes("youth") || haystack.includes("teen"))
50
- return "YOUTH";
51
- if (haystack.includes("infant") || haystack.includes("baby"))
52
- return "INFANT";
53
- if (haystack.includes("family"))
54
- return "FAMILY";
55
- if (haystack.includes("senior"))
56
- return "SENIOR";
57
- if (haystack.includes("student"))
58
- return "STUDENT";
59
- if (haystack.includes("military"))
60
- return "MILITARY";
61
- return unit.unitType === "person" ? "ADULT" : "OTHER";
62
- }
63
- export function deriveOctoAvailabilityStatus(slot, capacityMode) {
64
- if (slot.status === "sold_out")
65
- return "SOLD_OUT";
66
- if (slot.status === "closed" || slot.status === "cancelled")
67
- return "CLOSED";
68
- if (capacityMode === "free_sale" || slot.unlimited)
69
- return "FREESALE";
70
- if (slot.initialPax !== null &&
71
- slot.initialPax !== undefined &&
72
- slot.remainingPax !== null &&
73
- slot.remainingPax !== undefined) {
74
- if (slot.remainingPax <= 0)
75
- return "SOLD_OUT";
76
- if (slot.initialPax > 0 && slot.remainingPax / slot.initialPax < 0.5)
77
- return "LIMITED";
78
- }
79
- return "AVAILABLE";
80
- }
81
- export function mapBookingStatus(status) {
82
- switch (status) {
83
- case "on_hold":
84
- return "ON_HOLD";
85
- case "expired":
86
- return "EXPIRED";
87
- case "cancelled":
88
- return "CANCELLED";
89
- default:
90
- return "CONFIRMED";
91
- }
92
- }
93
- function mapUnit(unit) {
94
- return {
95
- id: unit.id,
96
- name: unit.name,
97
- code: unit.code,
98
- type: inferOctoUnitType(unit),
99
- restrictions: {
100
- minAge: unit.minAge ?? undefined,
101
- maxAge: unit.maxAge ?? undefined,
102
- minQuantity: unit.minQuantity ?? undefined,
103
- maxQuantity: unit.maxQuantity ?? undefined,
104
- occupancyMin: unit.occupancyMin ?? undefined,
105
- occupancyMax: unit.occupancyMax ?? undefined,
106
- },
107
- };
108
- }
109
- function buildProductContent({ features, faqs, locations, }) {
110
- return {
111
- highlights: features
112
- .filter((feature) => feature.featureType === "highlight" || feature.featureType === "other")
113
- .map((feature) => ({
114
- id: feature.id,
115
- title: feature.title,
116
- description: feature.description,
117
- })),
118
- inclusions: features
119
- .filter((feature) => feature.featureType === "inclusion")
120
- .map((feature) => ({
121
- id: feature.id,
122
- title: feature.title,
123
- description: feature.description,
124
- })),
125
- exclusions: features
126
- .filter((feature) => feature.featureType === "exclusion")
127
- .map((feature) => ({
128
- id: feature.id,
129
- title: feature.title,
130
- description: feature.description,
131
- })),
132
- importantInformation: features
133
- .filter((feature) => feature.featureType === "important_information")
134
- .map((feature) => ({
135
- id: feature.id,
136
- title: feature.title,
137
- description: feature.description,
138
- })),
139
- faqs: faqs.map((faq) => ({
140
- id: faq.id,
141
- question: faq.question,
142
- answer: faq.answer,
143
- })),
144
- locations: locations.map((location) => ({
145
- id: location.id,
146
- type: location.locationType,
147
- title: location.title,
148
- address: location.address,
149
- city: location.city,
150
- countryCode: location.countryCode,
151
- latitude: location.latitude,
152
- longitude: location.longitude,
153
- googlePlaceId: location.googlePlaceId,
154
- applePlaceId: location.applePlaceId,
155
- tripadvisorLocationId: location.tripadvisorLocationId,
156
- })),
157
- };
158
- }
159
- function pickOptionStartTimes(option, startTimes) {
160
- const optionTimes = startTimes.filter((startTime) => startTime.optionId === option.id);
161
- const sharedTimes = startTimes.filter((startTime) => startTime.optionId === null);
162
- const source = optionTimes.length > 0 ? optionTimes : sharedTimes;
163
- return source.map((startTime) => startTime.startTimeLocal);
164
- }
165
- function pickBookingContact(participants) {
166
- const preferred = participants.find((participant) => participant.participantType === "booker") ??
167
- participants.find((participant) => participant.participantType === "contact") ??
168
- participants.find((participant) => participant.isPrimary) ??
169
- participants[0];
170
- if (!preferred)
171
- return null;
172
- return {
173
- participantId: preferred.id,
174
- firstName: preferred.firstName,
175
- lastName: preferred.lastName,
176
- email: preferred.email,
177
- phone: preferred.phone,
178
- language: preferred.preferredLanguage,
179
- };
180
- }
181
- function pickPayloadString(payload, keys) {
182
- if (!payload)
183
- return null;
184
- for (const key of keys) {
185
- const value = payload[key];
186
- if (typeof value === "string" && value.length > 0) {
187
- return value;
188
- }
189
- }
190
- return null;
191
- }
192
- export function mapBookingArtifact(fulfillment) {
193
- const payload = fulfillment.payload ?? null;
194
- const artifactUrl = fulfillment.artifactUrl;
195
- const downloadUrl = pickPayloadString(payload, ["downloadUrl", "download_url", "url"]) ?? artifactUrl ?? null;
196
- const pdfUrl = pickPayloadString(payload, ["pdfUrl", "pdf_url"]) ??
197
- (fulfillment.fulfillmentType === "pdf" ? artifactUrl : null);
198
- const qrCode = pickPayloadString(payload, ["qrCode", "qr_code"]) ??
199
- (fulfillment.fulfillmentType === "qr_code"
200
- ? pickPayloadString(payload, ["code", "voucherCode", "voucher_code"])
201
- : null);
202
- const barcode = pickPayloadString(payload, ["barcode", "barcodeValue", "barcode_value"]) ??
203
- (fulfillment.fulfillmentType === "barcode"
204
- ? pickPayloadString(payload, ["code", "voucherCode", "voucher_code"])
205
- : null);
206
- const voucherCode = pickPayloadString(payload, ["voucherCode", "voucher_code", "code"]);
207
- return {
208
- fulfillmentId: fulfillment.id,
209
- bookingItemId: fulfillment.bookingItemId,
210
- participantId: fulfillment.participantId,
211
- type: fulfillment.fulfillmentType,
212
- deliveryChannel: fulfillment.deliveryChannel,
213
- status: fulfillment.status,
214
- artifactUrl,
215
- downloadUrl,
216
- pdfUrl,
217
- qrCode,
218
- barcode,
219
- voucherCode,
220
- issuedAt: toIsoString(fulfillment.issuedAt),
221
- revokedAt: toIsoString(fulfillment.revokedAt),
222
- };
223
- }
1
+ import { cancelProjectedBooking, confirmProjectedBooking, expireProjectedBooking, extendProjectedBookingHold, getProjectedBookingById, listProjectedBookings, listProjectedRedemptions, recordProjectedRedemption, reserveProjectedBooking, } from "./service-bookings.js";
2
+ import { getProjectedAvailabilityById, getProjectedAvailabilityCalendar, getProjectedProductById, listProjectedAvailability, listProjectedProducts, } from "./service-products.js";
3
+ import { deriveOctoAvailabilityStatus, inferOctoAvailabilityType, inferOctoUnitType, mapBookingArtifact, mapBookingStatus, } from "./service-shared.js";
4
+ export { deriveOctoAvailabilityStatus, inferOctoAvailabilityType, inferOctoUnitType, mapBookingArtifact, mapBookingStatus, };
224
5
  export const octoService = {
225
- async getProjectedProductById(db, id) {
226
- const [product] = await db.select().from(products).where(eq(products.id, id)).limit(1);
227
- if (!product)
228
- return null;
229
- const [options, startTimes, capabilities, deliveryFormats, features, faqs, locations] = await Promise.all([
230
- db
231
- .select()
232
- .from(productOptions)
233
- .where(eq(productOptions.productId, product.id))
234
- .orderBy(asc(productOptions.sortOrder), asc(productOptions.createdAt)),
235
- db
236
- .select()
237
- .from(availabilityStartTimes)
238
- .where(eq(availabilityStartTimes.productId, product.id))
239
- .orderBy(asc(availabilityStartTimes.sortOrder), asc(availabilityStartTimes.createdAt)),
240
- db
241
- .select()
242
- .from(productCapabilities)
243
- .where(eq(productCapabilities.productId, product.id))
244
- .orderBy(asc(productCapabilities.createdAt)),
245
- db
246
- .select()
247
- .from(productDeliveryFormats)
248
- .where(eq(productDeliveryFormats.productId, product.id))
249
- .orderBy(asc(productDeliveryFormats.createdAt)),
250
- db
251
- .select()
252
- .from(productFeatures)
253
- .where(eq(productFeatures.productId, product.id))
254
- .orderBy(asc(productFeatures.sortOrder), asc(productFeatures.createdAt)),
255
- db
256
- .select()
257
- .from(productFaqs)
258
- .where(eq(productFaqs.productId, product.id))
259
- .orderBy(asc(productFaqs.sortOrder), asc(productFaqs.createdAt)),
260
- db
261
- .select()
262
- .from(productLocations)
263
- .where(eq(productLocations.productId, product.id))
264
- .orderBy(asc(productLocations.sortOrder), asc(productLocations.createdAt)),
265
- ]);
266
- const optionIds = options.map((option) => option.id);
267
- const units = optionIds.length > 0
268
- ? await db
269
- .select()
270
- .from(optionUnits)
271
- .where(inArray(optionUnits.optionId, optionIds))
272
- .orderBy(asc(optionUnits.sortOrder), asc(optionUnits.createdAt))
273
- : [];
274
- return {
275
- id: product.id,
276
- name: product.name,
277
- description: product.description,
278
- timeZone: product.timezone,
279
- availabilityType: inferOctoAvailabilityType(product.bookingMode),
280
- allowFreesale: product.capacityMode === "free_sale",
281
- instantConfirmation: capabilities.some((capability) => capability.capability === "instant_confirmation" && capability.enabled),
282
- options: options.map((option) => ({
283
- id: option.id,
284
- name: option.name,
285
- code: option.code,
286
- default: option.isDefault,
287
- availabilityLocalStartTimes: pickOptionStartTimes(option, startTimes),
288
- units: units.filter((unit) => unit.optionId === option.id).map(mapUnit),
289
- })),
290
- content: buildProductContent({ features, faqs, locations }),
291
- extensions: {
292
- status: product.status,
293
- visibility: product.visibility,
294
- activated: product.activated,
295
- facilityId: product.facilityId ?? null,
296
- bookingMode: product.bookingMode,
297
- capabilityCodes: capabilities
298
- .filter((capability) => capability.enabled)
299
- .map((capability) => capability.capability),
300
- deliveryFormats: deliveryFormats.map((format) => format.format),
301
- },
302
- };
303
- },
304
- async getProjectedAvailabilityById(db, id) {
305
- const [row] = await db
306
- .select()
307
- .from(availabilitySlots)
308
- .where(eq(availabilitySlots.id, id))
309
- .limit(1);
310
- if (!row)
311
- return null;
312
- const [product] = await db
313
- .select({ capacityMode: products.capacityMode, timezone: products.timezone })
314
- .from(products)
315
- .where(eq(products.id, row.productId))
316
- .limit(1);
317
- return buildProjectedAvailability(row, product);
318
- },
319
- async listProjectedAvailability(db, query) {
320
- const conditions = [];
321
- if (query.productId)
322
- conditions.push(eq(availabilitySlots.productId, query.productId));
323
- if (query.optionId)
324
- conditions.push(eq(availabilitySlots.optionId, query.optionId));
325
- if (query.localDateStart)
326
- conditions.push(gte(availabilitySlots.dateLocal, query.localDateStart));
327
- if (query.localDateEnd)
328
- conditions.push(lte(availabilitySlots.dateLocal, query.localDateEnd));
329
- const where = conditions.length > 0 ? and(...conditions) : undefined;
330
- const [rows, countResult] = await Promise.all([
331
- db
332
- .select()
333
- .from(availabilitySlots)
334
- .where(where)
335
- .limit(query.limit)
336
- .offset(query.offset)
337
- .orderBy(asc(availabilitySlots.startsAt)),
338
- db.select({ count: sql `count(*)::int` }).from(availabilitySlots).where(where),
339
- ]);
340
- const productIds = [...new Set(rows.map((row) => row.productId))];
341
- const productRows = productIds.length > 0
342
- ? await db
343
- .select({
344
- id: products.id,
345
- capacityMode: products.capacityMode,
346
- timezone: products.timezone,
347
- })
348
- .from(products)
349
- .where(inArray(products.id, productIds))
350
- : [];
351
- const productsById = new Map(productRows.map((product) => [product.id, product]));
352
- return {
353
- data: rows.map((row) => buildProjectedAvailability(row, productsById.get(row.productId))),
354
- total: countResult[0]?.count ?? 0,
355
- limit: query.limit,
356
- offset: query.offset,
357
- };
358
- },
359
- async getProjectedAvailabilityCalendar(db, productId, query) {
360
- const result = await this.listProjectedAvailability(db, {
361
- productId,
362
- optionId: query.optionId,
363
- localDateStart: query.localDateStart,
364
- localDateEnd: query.localDateEnd,
365
- limit: 200,
366
- offset: 0,
367
- });
368
- const days = new Map();
369
- const rank = {
370
- FREESALE: 5,
371
- AVAILABLE: 4,
372
- LIMITED: 3,
373
- SOLD_OUT: 2,
374
- CLOSED: 1,
375
- };
376
- for (const availability of result.data) {
377
- const localDate = availability.localDateTimeStart.slice(0, 10);
378
- const existing = days.get(localDate);
379
- const nextStatus = !existing || rank[availability.status] > rank[existing.status]
380
- ? availability.status
381
- : existing.status;
382
- days.set(localDate, {
383
- localDate,
384
- status: nextStatus,
385
- vacancies: existing?.vacancies === null || availability.vacancies === null
386
- ? null
387
- : Math.max(existing?.vacancies ?? 0, availability.vacancies),
388
- capacity: existing?.capacity === null || availability.capacity === null
389
- ? null
390
- : Math.max(existing?.capacity ?? 0, availability.capacity),
391
- availabilityIds: [...(existing?.availabilityIds ?? []), availability.id],
392
- });
393
- }
394
- return {
395
- data: [...days.values()].sort((left, right) => left.localDate.localeCompare(right.localDate)),
396
- total: days.size,
397
- };
398
- },
399
- async listProjectedProducts(db, query) {
400
- const result = await productsService.listProducts(db, query);
401
- const data = await Promise.all(result.data.map(async (product) => this.getProjectedProductById(db, product.id)));
402
- return {
403
- data: data.filter((row) => Boolean(row)),
404
- total: result.total,
405
- limit: result.limit,
406
- offset: result.offset,
407
- };
408
- },
409
- async getProjectedBookingById(db, id) {
410
- const [booking] = await db.select().from(bookings).where(eq(bookings.id, id)).limit(1);
411
- if (!booking)
412
- return null;
413
- const [participants, items, allocations, fulfillments, redemptions, supplierStatuses, transactionLink,] = await Promise.all([
414
- db
415
- .select()
416
- .from(bookingParticipants)
417
- .where(eq(bookingParticipants.bookingId, booking.id))
418
- .orderBy(asc(bookingParticipants.createdAt)),
419
- db
420
- .select()
421
- .from(bookingItems)
422
- .where(eq(bookingItems.bookingId, booking.id))
423
- .orderBy(asc(bookingItems.createdAt)),
424
- db
425
- .select()
426
- .from(bookingAllocations)
427
- .where(eq(bookingAllocations.bookingId, booking.id))
428
- .orderBy(asc(bookingAllocations.createdAt)),
429
- db
430
- .select()
431
- .from(bookingFulfillments)
432
- .where(eq(bookingFulfillments.bookingId, booking.id))
433
- .orderBy(asc(bookingFulfillments.createdAt)),
434
- db
435
- .select()
436
- .from(bookingRedemptionEvents)
437
- .where(eq(bookingRedemptionEvents.bookingId, booking.id))
438
- .orderBy(asc(bookingRedemptionEvents.redeemedAt), asc(bookingRedemptionEvents.createdAt)),
439
- db
440
- .select()
441
- .from(bookingSupplierStatuses)
442
- .where(eq(bookingSupplierStatuses.bookingId, booking.id))
443
- .orderBy(asc(bookingSupplierStatuses.createdAt)),
444
- db
445
- .select()
446
- .from(bookingTransactionDetailsRef)
447
- .where(eq(bookingTransactionDetailsRef.bookingId, booking.id))
448
- .limit(1)
449
- .then((rows) => rows[0] ?? null),
450
- ]);
451
- const itemParticipants = items.length > 0
452
- ? await db
453
- .select()
454
- .from(bookingItemParticipants)
455
- .where(inArray(bookingItemParticipants.bookingItemId, items.map((item) => item.id)))
456
- .orderBy(asc(bookingItemParticipants.createdAt))
457
- : [];
458
- const activeAllocation = allocations.find((allocation) => allocation.status === "confirmed") ??
459
- allocations.find((allocation) => allocation.status === "held") ??
460
- allocations[0];
461
- const [offer, order] = await Promise.all([
462
- transactionLink?.offerId
463
- ? db
464
- .select({ id: offers.id, offerNumber: offers.offerNumber })
465
- .from(offers)
466
- .where(eq(offers.id, transactionLink.offerId))
467
- .limit(1)
468
- .then((rows) => rows[0] ?? null)
469
- : Promise.resolve(null),
470
- transactionLink?.orderId
471
- ? db
472
- .select({ id: orders.id, orderNumber: orders.orderNumber })
473
- .from(orders)
474
- .where(eq(orders.id, transactionLink.orderId))
475
- .limit(1)
476
- .then((rows) => rows[0] ?? null)
477
- : Promise.resolve(null),
478
- ]);
479
- return {
480
- id: booking.id,
481
- bookingNumber: booking.bookingNumber,
482
- status: mapBookingStatus(booking.status),
483
- availabilityId: activeAllocation?.availabilitySlotId ?? null,
484
- contact: pickBookingContact(participants),
485
- unitItems: items.map((item) => {
486
- const itemAllocation = allocations.find((allocation) => allocation.bookingItemId === item.id) ?? null;
487
- return {
488
- bookingItemId: item.id,
489
- title: item.title,
490
- itemType: item.itemType,
491
- status: item.status,
492
- quantity: item.quantity,
493
- productId: item.productId,
494
- optionId: item.optionId,
495
- unitId: item.optionUnitId,
496
- pricingCategoryId: item.pricingCategoryId,
497
- availabilityId: itemAllocation?.availabilitySlotId ?? null,
498
- participantIds: itemParticipants
499
- .filter((link) => link.bookingItemId === item.id)
500
- .map((link) => link.participantId),
501
- };
502
- }),
503
- fulfillments: fulfillments.map((fulfillment) => ({
504
- id: fulfillment.id,
505
- bookingItemId: fulfillment.bookingItemId,
506
- participantId: fulfillment.participantId,
507
- type: fulfillment.fulfillmentType,
508
- deliveryChannel: fulfillment.deliveryChannel,
509
- status: fulfillment.status,
510
- artifactUrl: fulfillment.artifactUrl,
511
- payload: fulfillment.payload ?? null,
512
- issuedAt: toIsoString(fulfillment.issuedAt),
513
- revokedAt: toIsoString(fulfillment.revokedAt),
514
- })),
515
- artifacts: fulfillments.map(mapBookingArtifact),
516
- redemptions: redemptions.map((event) => ({
517
- id: event.id,
518
- bookingItemId: event.bookingItemId,
519
- participantId: event.participantId,
520
- redeemedAt: event.redeemedAt.toISOString(),
521
- redeemedBy: event.redeemedBy,
522
- location: event.location,
523
- method: event.method,
524
- metadata: event.metadata ?? null,
525
- })),
526
- references: {
527
- resellerReference: booking.externalBookingRef,
528
- offerId: offer?.id ?? transactionLink?.offerId ?? null,
529
- offerNumber: offer?.offerNumber ?? null,
530
- orderId: order?.id ?? transactionLink?.orderId ?? null,
531
- orderNumber: order?.orderNumber ?? null,
532
- supplierReferences: supplierStatuses.map((status) => ({
533
- id: status.id,
534
- supplierServiceId: status.supplierServiceId,
535
- serviceName: status.serviceName,
536
- status: status.status,
537
- supplierReference: status.supplierReference,
538
- confirmedAt: toIsoString(status.confirmedAt),
539
- })),
540
- },
541
- holdExpiresAt: toIsoString(booking.holdExpiresAt),
542
- confirmedAt: toIsoString(booking.confirmedAt),
543
- cancelledAt: toIsoString(booking.cancelledAt),
544
- expiredAt: toIsoString(booking.expiredAt),
545
- utcRedeemedAt: toIsoString(booking.redeemedAt),
546
- extensions: {
547
- sourceType: booking.sourceType,
548
- externalBookingRef: booking.externalBookingRef,
549
- communicationLanguage: booking.communicationLanguage,
550
- personId: booking.personId,
551
- organizationId: booking.organizationId,
552
- sellCurrency: booking.sellCurrency,
553
- baseCurrency: booking.baseCurrency,
554
- },
555
- };
556
- },
557
- async listProjectedBookings(db, query) {
558
- const result = await bookingsService.listBookings(db, query);
559
- const data = await Promise.all(result.data.map(async (booking) => this.getProjectedBookingById(db, booking.id)));
560
- return {
561
- data: data.filter((row) => Boolean(row)),
562
- total: result.total,
563
- limit: result.limit,
564
- offset: result.offset,
565
- };
566
- },
567
- async reserveProjectedBooking(db, data, userId) {
568
- const result = await bookingsService.reserveBooking(db, data, userId);
569
- if (!("booking" in result) || !result.booking) {
570
- return result;
571
- }
572
- const projected = await this.getProjectedBookingById(db, result.booking.id);
573
- return { status: "ok", booking: projected };
574
- },
575
- async confirmProjectedBooking(db, id, data, userId) {
576
- const result = await bookingsService.confirmBooking(db, id, data, userId);
577
- if (!("booking" in result) || !result.booking) {
578
- return result;
579
- }
580
- const projected = await this.getProjectedBookingById(db, result.booking.id);
581
- return { status: "ok", booking: projected };
582
- },
583
- async extendProjectedBookingHold(db, id, data, userId) {
584
- const result = await bookingsService.extendBookingHold(db, id, data, userId);
585
- if (!("booking" in result) || !result.booking) {
586
- return result;
587
- }
588
- const projected = await this.getProjectedBookingById(db, result.booking.id);
589
- return { status: "ok", booking: projected };
590
- },
591
- async expireProjectedBooking(db, id, data, userId) {
592
- const result = await bookingsService.expireBooking(db, id, data, userId);
593
- if (!("booking" in result) || !result.booking) {
594
- return result;
595
- }
596
- const projected = await this.getProjectedBookingById(db, result.booking.id);
597
- return { status: "ok", booking: projected };
598
- },
599
- async cancelProjectedBooking(db, id, data, userId) {
600
- const result = await bookingsService.cancelBooking(db, id, data, userId);
601
- if (!("booking" in result) || !result.booking) {
602
- return result;
603
- }
604
- const projected = await this.getProjectedBookingById(db, result.booking.id);
605
- return { status: "ok", booking: projected };
606
- },
607
- async listProjectedRedemptions(db, bookingId) {
608
- const events = await bookingsService.listRedemptionEvents(db, bookingId);
609
- return events.map((event) => ({
610
- id: event.id,
611
- bookingItemId: event.bookingItemId,
612
- participantId: event.participantId,
613
- redeemedAt: event.redeemedAt.toISOString(),
614
- redeemedBy: event.redeemedBy,
615
- location: event.location,
616
- method: event.method,
617
- metadata: event.metadata ?? null,
618
- }));
619
- },
620
- async recordProjectedRedemption(db, bookingId, data, userId) {
621
- const event = await bookingsService.recordRedemption(db, bookingId, data, userId);
622
- if (!event) {
623
- return null;
624
- }
625
- const booking = await this.getProjectedBookingById(db, bookingId);
626
- return {
627
- event: {
628
- id: event.id,
629
- bookingItemId: event.bookingItemId,
630
- participantId: event.participantId,
631
- redeemedAt: event.redeemedAt.toISOString(),
632
- redeemedBy: event.redeemedBy,
633
- location: event.location,
634
- method: event.method,
635
- metadata: event.metadata ?? null,
636
- },
637
- booking,
638
- };
639
- },
6
+ getProjectedProductById,
7
+ getProjectedAvailabilityById,
8
+ listProjectedAvailability,
9
+ getProjectedAvailabilityCalendar,
10
+ listProjectedProducts,
11
+ getProjectedBookingById,
12
+ listProjectedBookings,
13
+ reserveProjectedBooking,
14
+ confirmProjectedBooking,
15
+ extendProjectedBookingHold,
16
+ expireProjectedBooking,
17
+ cancelProjectedBooking,
18
+ listProjectedRedemptions,
19
+ recordProjectedRedemption,
640
20
  };
@@ -29,13 +29,13 @@ export declare const octoBookingListQuerySchema: z.ZodObject<{
29
29
  limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
30
30
  offset: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
31
31
  status: z.ZodOptional<z.ZodEnum<{
32
- cancelled: "cancelled";
33
32
  draft: "draft";
34
33
  on_hold: "on_hold";
35
34
  confirmed: "confirmed";
36
35
  in_progress: "in_progress";
37
36
  completed: "completed";
38
37
  expired: "expired";
38
+ cancelled: "cancelled";
39
39
  }>>;
40
40
  search: z.ZodOptional<z.ZodString>;
41
41
  }, z.core.$strip>;