@voyantjs/octo 0.2.0 → 0.3.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/dist/routes.d.ts +106 -106
- package/dist/service-bookings.d.ts +808 -0
- package/dist/service-bookings.d.ts.map +1 -0
- package/dist/service-bookings.js +219 -0
- package/dist/service-products.d.ts +28 -0
- package/dist/service-products.d.ts.map +1 -0
- package/dist/service-products.js +189 -0
- package/dist/service-shared.d.ts +64 -0
- package/dist/service-shared.d.ts.map +1 -0
- package/dist/service-shared.js +215 -0
- package/dist/service.d.ts +18 -121
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +18 -638
- package/dist/validation.d.ts +1 -1
- package/package.json +8 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-bookings.d.ts","sourceRoot":"","sources":["../src/service-bookings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAapD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AACjE,OAAO,EAGL,KAAK,oBAAoB,EAG1B,MAAM,qBAAqB,CAAA;AAG5B,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAuK/E;AAED,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAY9F;AAmBD,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,IAAI,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAC1D,MAAM,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGhB;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAC1D,MAAM,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAMhB;AAED,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,EAC7D,MAAM,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAMhB;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EACzD,MAAM,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGhB;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,EACzD,MAAM,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGhB;AAED,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM;;;;;;;;;KAYvF;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAC5D,MAAM,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAqBhB"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { bookingsService } from "@voyantjs/bookings";
|
|
2
|
+
import { bookingAllocations, bookingFulfillments, bookingItemParticipants, bookingItems, bookingParticipants, bookingRedemptionEvents, bookingSupplierStatuses, bookings, } from "@voyantjs/bookings/schema";
|
|
3
|
+
import { offers, orders } from "@voyantjs/transactions/schema";
|
|
4
|
+
import { asc, eq, inArray } from "drizzle-orm";
|
|
5
|
+
import { mapBookingArtifact, mapBookingStatus, pickBookingContact, toIsoString, } from "./service-shared.js";
|
|
6
|
+
import { bookingTransactionDetailsRef } from "./transactions-ref.js";
|
|
7
|
+
export async function getProjectedBookingById(db, id) {
|
|
8
|
+
const [booking] = await db.select().from(bookings).where(eq(bookings.id, id)).limit(1);
|
|
9
|
+
if (!booking)
|
|
10
|
+
return null;
|
|
11
|
+
const [participants, items, allocations, fulfillments, redemptions, supplierStatuses, transactionLink,] = await Promise.all([
|
|
12
|
+
db
|
|
13
|
+
.select()
|
|
14
|
+
.from(bookingParticipants)
|
|
15
|
+
.where(eq(bookingParticipants.bookingId, booking.id))
|
|
16
|
+
.orderBy(asc(bookingParticipants.createdAt)),
|
|
17
|
+
db
|
|
18
|
+
.select()
|
|
19
|
+
.from(bookingItems)
|
|
20
|
+
.where(eq(bookingItems.bookingId, booking.id))
|
|
21
|
+
.orderBy(asc(bookingItems.createdAt)),
|
|
22
|
+
db
|
|
23
|
+
.select()
|
|
24
|
+
.from(bookingAllocations)
|
|
25
|
+
.where(eq(bookingAllocations.bookingId, booking.id))
|
|
26
|
+
.orderBy(asc(bookingAllocations.createdAt)),
|
|
27
|
+
db
|
|
28
|
+
.select()
|
|
29
|
+
.from(bookingFulfillments)
|
|
30
|
+
.where(eq(bookingFulfillments.bookingId, booking.id))
|
|
31
|
+
.orderBy(asc(bookingFulfillments.createdAt)),
|
|
32
|
+
db
|
|
33
|
+
.select()
|
|
34
|
+
.from(bookingRedemptionEvents)
|
|
35
|
+
.where(eq(bookingRedemptionEvents.bookingId, booking.id))
|
|
36
|
+
.orderBy(asc(bookingRedemptionEvents.redeemedAt), asc(bookingRedemptionEvents.createdAt)),
|
|
37
|
+
db
|
|
38
|
+
.select()
|
|
39
|
+
.from(bookingSupplierStatuses)
|
|
40
|
+
.where(eq(bookingSupplierStatuses.bookingId, booking.id))
|
|
41
|
+
.orderBy(asc(bookingSupplierStatuses.createdAt)),
|
|
42
|
+
db
|
|
43
|
+
.select()
|
|
44
|
+
.from(bookingTransactionDetailsRef)
|
|
45
|
+
.where(eq(bookingTransactionDetailsRef.bookingId, booking.id))
|
|
46
|
+
.limit(1)
|
|
47
|
+
.then((rows) => rows[0] ?? null),
|
|
48
|
+
]);
|
|
49
|
+
const itemParticipants = items.length > 0
|
|
50
|
+
? await db
|
|
51
|
+
.select()
|
|
52
|
+
.from(bookingItemParticipants)
|
|
53
|
+
.where(inArray(bookingItemParticipants.bookingItemId, items.map((item) => item.id)))
|
|
54
|
+
.orderBy(asc(bookingItemParticipants.createdAt))
|
|
55
|
+
: [];
|
|
56
|
+
const activeAllocation = allocations.find((allocation) => allocation.status === "confirmed") ??
|
|
57
|
+
allocations.find((allocation) => allocation.status === "held") ??
|
|
58
|
+
allocations[0];
|
|
59
|
+
const [offer, order] = await Promise.all([
|
|
60
|
+
transactionLink?.offerId
|
|
61
|
+
? db
|
|
62
|
+
.select({ id: offers.id, offerNumber: offers.offerNumber })
|
|
63
|
+
.from(offers)
|
|
64
|
+
.where(eq(offers.id, transactionLink.offerId))
|
|
65
|
+
.limit(1)
|
|
66
|
+
.then((rows) => rows[0] ?? null)
|
|
67
|
+
: Promise.resolve(null),
|
|
68
|
+
transactionLink?.orderId
|
|
69
|
+
? db
|
|
70
|
+
.select({ id: orders.id, orderNumber: orders.orderNumber })
|
|
71
|
+
.from(orders)
|
|
72
|
+
.where(eq(orders.id, transactionLink.orderId))
|
|
73
|
+
.limit(1)
|
|
74
|
+
.then((rows) => rows[0] ?? null)
|
|
75
|
+
: Promise.resolve(null),
|
|
76
|
+
]);
|
|
77
|
+
return {
|
|
78
|
+
id: booking.id,
|
|
79
|
+
bookingNumber: booking.bookingNumber,
|
|
80
|
+
status: mapBookingStatus(booking.status),
|
|
81
|
+
availabilityId: activeAllocation?.availabilitySlotId ?? null,
|
|
82
|
+
contact: pickBookingContact(participants),
|
|
83
|
+
unitItems: items.map((item) => {
|
|
84
|
+
const itemAllocation = allocations.find((allocation) => allocation.bookingItemId === item.id) ?? null;
|
|
85
|
+
return {
|
|
86
|
+
bookingItemId: item.id,
|
|
87
|
+
title: item.title,
|
|
88
|
+
itemType: item.itemType,
|
|
89
|
+
status: item.status,
|
|
90
|
+
quantity: item.quantity,
|
|
91
|
+
productId: item.productId,
|
|
92
|
+
optionId: item.optionId,
|
|
93
|
+
unitId: item.optionUnitId,
|
|
94
|
+
pricingCategoryId: item.pricingCategoryId,
|
|
95
|
+
availabilityId: itemAllocation?.availabilitySlotId ?? null,
|
|
96
|
+
participantIds: itemParticipants
|
|
97
|
+
.filter((link) => link.bookingItemId === item.id)
|
|
98
|
+
.map((link) => link.participantId),
|
|
99
|
+
};
|
|
100
|
+
}),
|
|
101
|
+
fulfillments: fulfillments.map((fulfillment) => ({
|
|
102
|
+
id: fulfillment.id,
|
|
103
|
+
bookingItemId: fulfillment.bookingItemId,
|
|
104
|
+
participantId: fulfillment.participantId,
|
|
105
|
+
type: fulfillment.fulfillmentType,
|
|
106
|
+
deliveryChannel: fulfillment.deliveryChannel,
|
|
107
|
+
status: fulfillment.status,
|
|
108
|
+
artifactUrl: fulfillment.artifactUrl,
|
|
109
|
+
payload: fulfillment.payload ?? null,
|
|
110
|
+
issuedAt: toIsoString(fulfillment.issuedAt),
|
|
111
|
+
revokedAt: toIsoString(fulfillment.revokedAt),
|
|
112
|
+
})),
|
|
113
|
+
artifacts: fulfillments.map(mapBookingArtifact),
|
|
114
|
+
redemptions: redemptions.map((event) => ({
|
|
115
|
+
id: event.id,
|
|
116
|
+
bookingItemId: event.bookingItemId,
|
|
117
|
+
participantId: event.participantId,
|
|
118
|
+
redeemedAt: event.redeemedAt.toISOString(),
|
|
119
|
+
redeemedBy: event.redeemedBy,
|
|
120
|
+
location: event.location,
|
|
121
|
+
method: event.method,
|
|
122
|
+
metadata: event.metadata ?? null,
|
|
123
|
+
})),
|
|
124
|
+
references: {
|
|
125
|
+
resellerReference: booking.externalBookingRef,
|
|
126
|
+
offerId: offer?.id ?? transactionLink?.offerId ?? null,
|
|
127
|
+
offerNumber: offer?.offerNumber ?? null,
|
|
128
|
+
orderId: order?.id ?? transactionLink?.orderId ?? null,
|
|
129
|
+
orderNumber: order?.orderNumber ?? null,
|
|
130
|
+
supplierReferences: supplierStatuses.map((status) => ({
|
|
131
|
+
id: status.id,
|
|
132
|
+
supplierServiceId: status.supplierServiceId,
|
|
133
|
+
serviceName: status.serviceName,
|
|
134
|
+
status: status.status,
|
|
135
|
+
supplierReference: status.supplierReference,
|
|
136
|
+
confirmedAt: toIsoString(status.confirmedAt),
|
|
137
|
+
})),
|
|
138
|
+
},
|
|
139
|
+
holdExpiresAt: toIsoString(booking.holdExpiresAt),
|
|
140
|
+
confirmedAt: toIsoString(booking.confirmedAt),
|
|
141
|
+
cancelledAt: toIsoString(booking.cancelledAt),
|
|
142
|
+
expiredAt: toIsoString(booking.expiredAt),
|
|
143
|
+
utcRedeemedAt: toIsoString(booking.redeemedAt),
|
|
144
|
+
extensions: {
|
|
145
|
+
sourceType: booking.sourceType,
|
|
146
|
+
externalBookingRef: booking.externalBookingRef,
|
|
147
|
+
communicationLanguage: booking.communicationLanguage,
|
|
148
|
+
personId: booking.personId,
|
|
149
|
+
organizationId: booking.organizationId,
|
|
150
|
+
sellCurrency: booking.sellCurrency,
|
|
151
|
+
baseCurrency: booking.baseCurrency,
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export async function listProjectedBookings(db, query) {
|
|
156
|
+
const result = await bookingsService.listBookings(db, query);
|
|
157
|
+
const data = await Promise.all(result.data.map(async (booking) => getProjectedBookingById(db, booking.id)));
|
|
158
|
+
return {
|
|
159
|
+
data: data.filter((row) => Boolean(row)),
|
|
160
|
+
total: result.total,
|
|
161
|
+
limit: result.limit,
|
|
162
|
+
offset: result.offset,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async function projectBookingMutationResult(db, result) {
|
|
166
|
+
if (!("booking" in result) || !result.booking) {
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
const projected = await getProjectedBookingById(db, result.booking.id);
|
|
170
|
+
return { status: "ok", booking: projected };
|
|
171
|
+
}
|
|
172
|
+
export async function reserveProjectedBooking(db, data, userId) {
|
|
173
|
+
return projectBookingMutationResult(db, await bookingsService.reserveBooking(db, data, userId));
|
|
174
|
+
}
|
|
175
|
+
export async function confirmProjectedBooking(db, id, data, userId) {
|
|
176
|
+
return projectBookingMutationResult(db, await bookingsService.confirmBooking(db, id, data, userId));
|
|
177
|
+
}
|
|
178
|
+
export async function extendProjectedBookingHold(db, id, data, userId) {
|
|
179
|
+
return projectBookingMutationResult(db, await bookingsService.extendBookingHold(db, id, data, userId));
|
|
180
|
+
}
|
|
181
|
+
export async function expireProjectedBooking(db, id, data, userId) {
|
|
182
|
+
return projectBookingMutationResult(db, await bookingsService.expireBooking(db, id, data, userId));
|
|
183
|
+
}
|
|
184
|
+
export async function cancelProjectedBooking(db, id, data, userId) {
|
|
185
|
+
return projectBookingMutationResult(db, await bookingsService.cancelBooking(db, id, data, userId));
|
|
186
|
+
}
|
|
187
|
+
export async function listProjectedRedemptions(db, bookingId) {
|
|
188
|
+
const events = await bookingsService.listRedemptionEvents(db, bookingId);
|
|
189
|
+
return events.map((event) => ({
|
|
190
|
+
id: event.id,
|
|
191
|
+
bookingItemId: event.bookingItemId,
|
|
192
|
+
participantId: event.participantId,
|
|
193
|
+
redeemedAt: event.redeemedAt.toISOString(),
|
|
194
|
+
redeemedBy: event.redeemedBy,
|
|
195
|
+
location: event.location,
|
|
196
|
+
method: event.method,
|
|
197
|
+
metadata: event.metadata ?? null,
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
export async function recordProjectedRedemption(db, bookingId, data, userId) {
|
|
201
|
+
const event = await bookingsService.recordRedemption(db, bookingId, data, userId);
|
|
202
|
+
if (!event) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const booking = await getProjectedBookingById(db, bookingId);
|
|
206
|
+
return {
|
|
207
|
+
event: {
|
|
208
|
+
id: event.id,
|
|
209
|
+
bookingItemId: event.bookingItemId,
|
|
210
|
+
participantId: event.participantId,
|
|
211
|
+
redeemedAt: event.redeemedAt.toISOString(),
|
|
212
|
+
redeemedBy: event.redeemedBy,
|
|
213
|
+
location: event.location,
|
|
214
|
+
method: event.method,
|
|
215
|
+
metadata: event.metadata ?? null,
|
|
216
|
+
},
|
|
217
|
+
booking,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
2
|
+
import { type OctoAvailabilityCalendarQuery, type OctoAvailabilityListQuery, type OctoProductListQuery } from "./service-shared.js";
|
|
3
|
+
import type { OctoAvailabilityStatus, OctoProjectedProduct } from "./types.js";
|
|
4
|
+
export declare function getProjectedProductById(db: PostgresJsDatabase, id: string): Promise<OctoProjectedProduct | null>;
|
|
5
|
+
export declare function getProjectedAvailabilityById(db: PostgresJsDatabase, id: string): Promise<import("./types.js").OctoProjectedAvailability | null>;
|
|
6
|
+
export declare function listProjectedAvailability(db: PostgresJsDatabase, query: OctoAvailabilityListQuery): Promise<{
|
|
7
|
+
data: import("./types.js").OctoProjectedAvailability[];
|
|
8
|
+
total: number;
|
|
9
|
+
limit: number;
|
|
10
|
+
offset: number;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function getProjectedAvailabilityCalendar(db: PostgresJsDatabase, productId: string, query: OctoAvailabilityCalendarQuery): Promise<{
|
|
13
|
+
data: {
|
|
14
|
+
localDate: string;
|
|
15
|
+
status: OctoAvailabilityStatus;
|
|
16
|
+
vacancies: number | null;
|
|
17
|
+
capacity: number | null;
|
|
18
|
+
availabilityIds: string[];
|
|
19
|
+
}[];
|
|
20
|
+
total: number;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function listProjectedProducts(db: PostgresJsDatabase, query: OctoProductListQuery): Promise<{
|
|
23
|
+
data: OctoProjectedProduct[];
|
|
24
|
+
total: number;
|
|
25
|
+
limit: number;
|
|
26
|
+
offset: number;
|
|
27
|
+
}>;
|
|
28
|
+
//# sourceMappingURL=service-products.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-products.d.ts","sourceRoot":"","sources":["../src/service-products.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,EAKL,KAAK,6BAA6B,EAClC,KAAK,yBAAyB,EAC9B,KAAK,oBAAoB,EAE1B,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAE9E,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAoFtC;AAED,wBAAsB,4BAA4B,CAAC,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,kEAepF;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,yBAAyB;;;;;GA0CjC;AAED,wBAAsB,gCAAgC,CACpD,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,6BAA6B;;mBAcrB,MAAM;gBACT,sBAAsB;mBACnB,MAAM,GAAG,IAAI;kBACd,MAAM,GAAG,IAAI;yBACN,MAAM,EAAE;;;GAuC9B;AAED,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,oBAAoB;;;;;GAY9F"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { availabilitySlots, availabilityStartTimes } from "@voyantjs/availability/schema";
|
|
2
|
+
import { productsService } from "@voyantjs/products";
|
|
3
|
+
import { optionUnits, productCapabilities, productDeliveryFormats, productFaqs, productFeatures, productLocations, productOptions, products, } from "@voyantjs/products/schema";
|
|
4
|
+
import { and, asc, eq, gte, inArray, lte, sql } from "drizzle-orm";
|
|
5
|
+
import { buildProductContent, buildProjectedAvailability, inferOctoAvailabilityType, mapUnit, pickOptionStartTimes, } from "./service-shared.js";
|
|
6
|
+
export async function getProjectedProductById(db, id) {
|
|
7
|
+
const [product] = await db.select().from(products).where(eq(products.id, id)).limit(1);
|
|
8
|
+
if (!product)
|
|
9
|
+
return null;
|
|
10
|
+
const [options, startTimes, capabilities, deliveryFormats, features, faqs, locations] = await Promise.all([
|
|
11
|
+
db
|
|
12
|
+
.select()
|
|
13
|
+
.from(productOptions)
|
|
14
|
+
.where(eq(productOptions.productId, product.id))
|
|
15
|
+
.orderBy(asc(productOptions.sortOrder), asc(productOptions.createdAt)),
|
|
16
|
+
db
|
|
17
|
+
.select()
|
|
18
|
+
.from(availabilityStartTimes)
|
|
19
|
+
.where(eq(availabilityStartTimes.productId, product.id))
|
|
20
|
+
.orderBy(asc(availabilityStartTimes.sortOrder), asc(availabilityStartTimes.createdAt)),
|
|
21
|
+
db
|
|
22
|
+
.select()
|
|
23
|
+
.from(productCapabilities)
|
|
24
|
+
.where(eq(productCapabilities.productId, product.id))
|
|
25
|
+
.orderBy(asc(productCapabilities.createdAt)),
|
|
26
|
+
db
|
|
27
|
+
.select()
|
|
28
|
+
.from(productDeliveryFormats)
|
|
29
|
+
.where(eq(productDeliveryFormats.productId, product.id))
|
|
30
|
+
.orderBy(asc(productDeliveryFormats.createdAt)),
|
|
31
|
+
db
|
|
32
|
+
.select()
|
|
33
|
+
.from(productFeatures)
|
|
34
|
+
.where(eq(productFeatures.productId, product.id))
|
|
35
|
+
.orderBy(asc(productFeatures.sortOrder), asc(productFeatures.createdAt)),
|
|
36
|
+
db
|
|
37
|
+
.select()
|
|
38
|
+
.from(productFaqs)
|
|
39
|
+
.where(eq(productFaqs.productId, product.id))
|
|
40
|
+
.orderBy(asc(productFaqs.sortOrder), asc(productFaqs.createdAt)),
|
|
41
|
+
db
|
|
42
|
+
.select()
|
|
43
|
+
.from(productLocations)
|
|
44
|
+
.where(eq(productLocations.productId, product.id))
|
|
45
|
+
.orderBy(asc(productLocations.sortOrder), asc(productLocations.createdAt)),
|
|
46
|
+
]);
|
|
47
|
+
const optionIds = options.map((option) => option.id);
|
|
48
|
+
const units = optionIds.length > 0
|
|
49
|
+
? await db
|
|
50
|
+
.select()
|
|
51
|
+
.from(optionUnits)
|
|
52
|
+
.where(inArray(optionUnits.optionId, optionIds))
|
|
53
|
+
.orderBy(asc(optionUnits.sortOrder), asc(optionUnits.createdAt))
|
|
54
|
+
: [];
|
|
55
|
+
return {
|
|
56
|
+
id: product.id,
|
|
57
|
+
name: product.name,
|
|
58
|
+
description: product.description,
|
|
59
|
+
timeZone: product.timezone,
|
|
60
|
+
availabilityType: inferOctoAvailabilityType(product.bookingMode),
|
|
61
|
+
allowFreesale: product.capacityMode === "free_sale",
|
|
62
|
+
instantConfirmation: capabilities.some((capability) => capability.capability === "instant_confirmation" && capability.enabled),
|
|
63
|
+
options: options.map((option) => ({
|
|
64
|
+
id: option.id,
|
|
65
|
+
name: option.name,
|
|
66
|
+
code: option.code,
|
|
67
|
+
default: option.isDefault,
|
|
68
|
+
availabilityLocalStartTimes: pickOptionStartTimes(option, startTimes),
|
|
69
|
+
units: units.filter((unit) => unit.optionId === option.id).map(mapUnit),
|
|
70
|
+
})),
|
|
71
|
+
content: buildProductContent({ features, faqs, locations }),
|
|
72
|
+
extensions: {
|
|
73
|
+
status: product.status,
|
|
74
|
+
visibility: product.visibility,
|
|
75
|
+
activated: product.activated,
|
|
76
|
+
facilityId: product.facilityId ?? null,
|
|
77
|
+
bookingMode: product.bookingMode,
|
|
78
|
+
capabilityCodes: capabilities
|
|
79
|
+
.filter((capability) => capability.enabled)
|
|
80
|
+
.map((capability) => capability.capability),
|
|
81
|
+
deliveryFormats: deliveryFormats.map((format) => format.format),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export async function getProjectedAvailabilityById(db, id) {
|
|
86
|
+
const [row] = await db
|
|
87
|
+
.select()
|
|
88
|
+
.from(availabilitySlots)
|
|
89
|
+
.where(eq(availabilitySlots.id, id))
|
|
90
|
+
.limit(1);
|
|
91
|
+
if (!row)
|
|
92
|
+
return null;
|
|
93
|
+
const [product] = await db
|
|
94
|
+
.select({ capacityMode: products.capacityMode, timezone: products.timezone })
|
|
95
|
+
.from(products)
|
|
96
|
+
.where(eq(products.id, row.productId))
|
|
97
|
+
.limit(1);
|
|
98
|
+
return buildProjectedAvailability(row, product);
|
|
99
|
+
}
|
|
100
|
+
export async function listProjectedAvailability(db, query) {
|
|
101
|
+
const conditions = [];
|
|
102
|
+
if (query.productId)
|
|
103
|
+
conditions.push(eq(availabilitySlots.productId, query.productId));
|
|
104
|
+
if (query.optionId)
|
|
105
|
+
conditions.push(eq(availabilitySlots.optionId, query.optionId));
|
|
106
|
+
if (query.localDateStart)
|
|
107
|
+
conditions.push(gte(availabilitySlots.dateLocal, query.localDateStart));
|
|
108
|
+
if (query.localDateEnd)
|
|
109
|
+
conditions.push(lte(availabilitySlots.dateLocal, query.localDateEnd));
|
|
110
|
+
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
111
|
+
const [rows, countResult] = await Promise.all([
|
|
112
|
+
db
|
|
113
|
+
.select()
|
|
114
|
+
.from(availabilitySlots)
|
|
115
|
+
.where(where)
|
|
116
|
+
.limit(query.limit)
|
|
117
|
+
.offset(query.offset)
|
|
118
|
+
.orderBy(asc(availabilitySlots.startsAt)),
|
|
119
|
+
db.select({ count: sql `count(*)::int` }).from(availabilitySlots).where(where),
|
|
120
|
+
]);
|
|
121
|
+
const productIds = [...new Set(rows.map((row) => row.productId))];
|
|
122
|
+
const productRows = productIds.length > 0
|
|
123
|
+
? await db
|
|
124
|
+
.select({
|
|
125
|
+
id: products.id,
|
|
126
|
+
capacityMode: products.capacityMode,
|
|
127
|
+
timezone: products.timezone,
|
|
128
|
+
})
|
|
129
|
+
.from(products)
|
|
130
|
+
.where(inArray(products.id, productIds))
|
|
131
|
+
: [];
|
|
132
|
+
const productsById = new Map(productRows.map((product) => [product.id, product]));
|
|
133
|
+
return {
|
|
134
|
+
data: rows.map((row) => buildProjectedAvailability(row, productsById.get(row.productId))),
|
|
135
|
+
total: countResult[0]?.count ?? 0,
|
|
136
|
+
limit: query.limit,
|
|
137
|
+
offset: query.offset,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
export async function getProjectedAvailabilityCalendar(db, productId, query) {
|
|
141
|
+
const result = await listProjectedAvailability(db, {
|
|
142
|
+
productId,
|
|
143
|
+
optionId: query.optionId,
|
|
144
|
+
localDateStart: query.localDateStart,
|
|
145
|
+
localDateEnd: query.localDateEnd,
|
|
146
|
+
limit: 200,
|
|
147
|
+
offset: 0,
|
|
148
|
+
});
|
|
149
|
+
const days = new Map();
|
|
150
|
+
const rank = {
|
|
151
|
+
FREESALE: 5,
|
|
152
|
+
AVAILABLE: 4,
|
|
153
|
+
LIMITED: 3,
|
|
154
|
+
SOLD_OUT: 2,
|
|
155
|
+
CLOSED: 1,
|
|
156
|
+
};
|
|
157
|
+
for (const availability of result.data) {
|
|
158
|
+
const localDate = availability.localDateTimeStart.slice(0, 10);
|
|
159
|
+
const existing = days.get(localDate);
|
|
160
|
+
const nextStatus = !existing || rank[availability.status] > rank[existing.status]
|
|
161
|
+
? availability.status
|
|
162
|
+
: existing.status;
|
|
163
|
+
days.set(localDate, {
|
|
164
|
+
localDate,
|
|
165
|
+
status: nextStatus,
|
|
166
|
+
vacancies: existing?.vacancies === null || availability.vacancies === null
|
|
167
|
+
? null
|
|
168
|
+
: Math.max(existing?.vacancies ?? 0, availability.vacancies),
|
|
169
|
+
capacity: existing?.capacity === null || availability.capacity === null
|
|
170
|
+
? null
|
|
171
|
+
: Math.max(existing?.capacity ?? 0, availability.capacity),
|
|
172
|
+
availabilityIds: [...(existing?.availabilityIds ?? []), availability.id],
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
data: [...days.values()].sort((left, right) => left.localDate.localeCompare(right.localDate)),
|
|
177
|
+
total: days.size,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export async function listProjectedProducts(db, query) {
|
|
181
|
+
const result = await productsService.listProducts(db, query);
|
|
182
|
+
const data = await Promise.all(result.data.map(async (product) => getProjectedProductById(db, product.id)));
|
|
183
|
+
return {
|
|
184
|
+
data: data.filter((row) => Boolean(row)),
|
|
185
|
+
total: result.total,
|
|
186
|
+
limit: result.limit,
|
|
187
|
+
offset: result.offset,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { availabilitySlots, availabilityStartTimes } from "@voyantjs/availability/schema";
|
|
2
|
+
import type { bookingFulfillments, bookings } from "@voyantjs/bookings/schema";
|
|
3
|
+
import type { optionUnits, productFaqs, productFeatures, productLocations, productOptions, products } from "@voyantjs/products/schema";
|
|
4
|
+
import type { z } from "zod";
|
|
5
|
+
import type { OctoAvailabilityStatus, OctoAvailabilityType, OctoBookingStatus, OctoProjectedAvailability, OctoProjectedProductContent, OctoProjectedUnit, OctoUnitType } from "./types.js";
|
|
6
|
+
import type { octoAvailabilityCalendarQuerySchema, octoAvailabilityListQuerySchema, octoBookingListQuerySchema, octoProductListQuerySchema } from "./validation.js";
|
|
7
|
+
export type ProductRow = typeof products.$inferSelect;
|
|
8
|
+
export type OptionRow = typeof productOptions.$inferSelect;
|
|
9
|
+
export type UnitRow = typeof optionUnits.$inferSelect;
|
|
10
|
+
export type SlotRow = typeof availabilitySlots.$inferSelect;
|
|
11
|
+
export type BookingRow = typeof bookings.$inferSelect;
|
|
12
|
+
export type OctoAvailabilityListQuery = z.infer<typeof octoAvailabilityListQuerySchema>;
|
|
13
|
+
export type OctoAvailabilityCalendarQuery = z.infer<typeof octoAvailabilityCalendarQuerySchema>;
|
|
14
|
+
export type OctoProductListQuery = z.infer<typeof octoProductListQuerySchema>;
|
|
15
|
+
export type OctoBookingListQuery = z.infer<typeof octoBookingListQuerySchema>;
|
|
16
|
+
export declare function toIsoString(value: Date | null | undefined): string | null;
|
|
17
|
+
export declare function formatLocalDateTime(value: Date, timeZone: string): string;
|
|
18
|
+
export declare function inferOctoAvailabilityType(bookingMode: ProductRow["bookingMode"]): OctoAvailabilityType;
|
|
19
|
+
export declare function inferOctoUnitType(unit: Pick<UnitRow, "name" | "code" | "unitType">): OctoUnitType;
|
|
20
|
+
export declare function deriveOctoAvailabilityStatus(slot: Pick<SlotRow, "status" | "unlimited" | "initialPax" | "remainingPax">, capacityMode: ProductRow["capacityMode"] | null | undefined): OctoAvailabilityStatus;
|
|
21
|
+
export declare function mapBookingStatus(status: BookingRow["status"]): OctoBookingStatus;
|
|
22
|
+
export declare function mapUnit(unit: UnitRow): OctoProjectedUnit;
|
|
23
|
+
export declare function buildProductContent({ features, faqs, locations, }: {
|
|
24
|
+
features: Array<typeof productFeatures.$inferSelect>;
|
|
25
|
+
faqs: Array<typeof productFaqs.$inferSelect>;
|
|
26
|
+
locations: Array<typeof productLocations.$inferSelect>;
|
|
27
|
+
}): OctoProjectedProductContent;
|
|
28
|
+
export declare function pickOptionStartTimes(option: OptionRow, startTimes: Array<typeof availabilityStartTimes.$inferSelect>): string[];
|
|
29
|
+
export declare function pickBookingContact(participants: Array<{
|
|
30
|
+
participantType: string | null;
|
|
31
|
+
isPrimary: boolean | null;
|
|
32
|
+
id: string;
|
|
33
|
+
firstName: string | null;
|
|
34
|
+
lastName: string | null;
|
|
35
|
+
email: string | null;
|
|
36
|
+
phone: string | null;
|
|
37
|
+
preferredLanguage: string | null;
|
|
38
|
+
}>): {
|
|
39
|
+
participantId: string;
|
|
40
|
+
firstName: string | null;
|
|
41
|
+
lastName: string | null;
|
|
42
|
+
email: string | null;
|
|
43
|
+
phone: string | null;
|
|
44
|
+
language: string | null;
|
|
45
|
+
} | null;
|
|
46
|
+
export declare function pickPayloadString(payload: Record<string, unknown> | null | undefined, keys: string[]): string | null;
|
|
47
|
+
export declare function mapBookingArtifact(fulfillment: typeof bookingFulfillments.$inferSelect): {
|
|
48
|
+
fulfillmentId: string;
|
|
49
|
+
bookingItemId: string | null;
|
|
50
|
+
participantId: string | null;
|
|
51
|
+
type: "other" | "voucher" | "ticket" | "pdf" | "qr_code" | "barcode" | "mobile";
|
|
52
|
+
deliveryChannel: "other" | "download" | "email" | "api" | "wallet";
|
|
53
|
+
status: "pending" | "issued" | "reissued" | "revoked" | "failed";
|
|
54
|
+
artifactUrl: string | null;
|
|
55
|
+
downloadUrl: string | null;
|
|
56
|
+
pdfUrl: string | null;
|
|
57
|
+
qrCode: string | null;
|
|
58
|
+
barcode: string | null;
|
|
59
|
+
voucherCode: string | null;
|
|
60
|
+
issuedAt: string | null;
|
|
61
|
+
revokedAt: string | null;
|
|
62
|
+
};
|
|
63
|
+
export declare function buildProjectedAvailability(slot: SlotRow, product: Pick<ProductRow, "capacityMode" | "timezone"> | null | undefined): OctoProjectedAvailability;
|
|
64
|
+
//# sourceMappingURL=service-shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../src/service-shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAA;AAC9F,OAAO,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAC9E,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,QAAQ,EACT,MAAM,2BAA2B,CAAA;AAClC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,KAAK,EACV,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,yBAAyB,EACzB,2BAA2B,EAC3B,iBAAiB,EACjB,YAAY,EACb,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EACV,mCAAmC,EACnC,+BAA+B,EAC/B,0BAA0B,EAC1B,0BAA0B,EAC3B,MAAM,iBAAiB,CAAA;AAExB,MAAM,MAAM,UAAU,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAA;AACrD,MAAM,MAAM,SAAS,GAAG,OAAO,cAAc,CAAC,YAAY,CAAA;AAC1D,MAAM,MAAM,OAAO,GAAG,OAAO,WAAW,CAAC,YAAY,CAAA;AACrD,MAAM,MAAM,OAAO,GAAG,OAAO,iBAAiB,CAAC,YAAY,CAAA;AAC3D,MAAM,MAAM,UAAU,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAA;AACrD,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAA;AACvF,MAAM,MAAM,6BAA6B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA;AAC/F,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAA;AAC7E,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAA;AAE7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,iBAEzD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,UAchE;AAED,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,UAAU,CAAC,aAAa,CAAC,GACrC,oBAAoB,CAEtB;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC,GAAG,YAAY,CAajG;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,GAAG,WAAW,GAAG,YAAY,GAAG,cAAc,CAAC,EAC3E,YAAY,EAAE,UAAU,CAAC,cAAc,CAAC,GAAG,IAAI,GAAG,SAAS,GAC1D,sBAAsB,CAgBxB;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,iBAAiB,CAWhF;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,iBAAiB,CAexD;AAED,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,IAAI,EACJ,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,OAAO,eAAe,CAAC,YAAY,CAAC,CAAA;IACpD,IAAI,EAAE,KAAK,CAAC,OAAO,WAAW,CAAC,YAAY,CAAC,CAAA;IAC5C,SAAS,EAAE,KAAK,CAAC,OAAO,gBAAgB,CAAC,YAAY,CAAC,CAAA;CACvD,GAAG,2BAA2B,CAiD9B;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,SAAS,EACjB,UAAU,EAAE,KAAK,CAAC,OAAO,sBAAsB,CAAC,YAAY,CAAC,YAM9D;AAED,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,KAAK,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,SAAS,EAAE,OAAO,GAAG,IAAI,CAAA;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC,CAAC;;;;;;;SAkBH;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EACnD,IAAI,EAAE,MAAM,EAAE,iBAYf;AAED,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,OAAO,mBAAmB,CAAC,YAAY;;;;;;;;;;;;;;;EAoCtF;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,cAAc,GAAG,UAAU,CAAC,GAAG,IAAI,GAAG,SAAS,GACxE,yBAAyB,CAc3B"}
|