@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.
Files changed (126) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +231 -0
  3. package/dist/booking-intents.d.ts +42 -0
  4. package/dist/booking-intents.d.ts.map +1 -0
  5. package/dist/booking-intents.js +83 -0
  6. package/dist/customer-portal/index.d.ts +16 -0
  7. package/dist/customer-portal/index.d.ts.map +1 -0
  8. package/dist/customer-portal/index.js +23 -0
  9. package/dist/customer-portal/route-runtime.d.ts +16 -0
  10. package/dist/customer-portal/route-runtime.d.ts.map +1 -0
  11. package/dist/customer-portal/route-runtime.js +27 -0
  12. package/dist/customer-portal/routes-public.d.ts +1936 -0
  13. package/dist/customer-portal/routes-public.d.ts.map +1 -0
  14. package/dist/customer-portal/routes-public.js +165 -0
  15. package/dist/customer-portal/routes.d.ts +43 -0
  16. package/dist/customer-portal/routes.d.ts.map +1 -0
  17. package/dist/customer-portal/routes.js +17 -0
  18. package/dist/customer-portal/service-public-impl.d.ts +138 -0
  19. package/dist/customer-portal/service-public-impl.d.ts.map +1 -0
  20. package/dist/customer-portal/service-public-impl.js +1808 -0
  21. package/dist/customer-portal/service-public.d.ts +2 -0
  22. package/dist/customer-portal/service-public.d.ts.map +1 -0
  23. package/dist/customer-portal/service-public.js +1 -0
  24. package/dist/customer-portal/validation-public/bookings.d.ts +551 -0
  25. package/dist/customer-portal/validation-public/bookings.d.ts.map +1 -0
  26. package/dist/customer-portal/validation-public/bookings.js +132 -0
  27. package/dist/customer-portal/validation-public/common.d.ts +162 -0
  28. package/dist/customer-portal/validation-public/common.d.ts.map +1 -0
  29. package/dist/customer-portal/validation-public/common.js +139 -0
  30. package/dist/customer-portal/validation-public/profile.d.ts +749 -0
  31. package/dist/customer-portal/validation-public/profile.d.ts.map +1 -0
  32. package/dist/customer-portal/validation-public/profile.js +308 -0
  33. package/dist/customer-portal/validation-public.d.ts +3 -0
  34. package/dist/customer-portal/validation-public.d.ts.map +1 -0
  35. package/dist/customer-portal/validation-public.js +2 -0
  36. package/dist/guest-booking-guard.d.ts +24 -0
  37. package/dist/guest-booking-guard.d.ts.map +1 -0
  38. package/dist/guest-booking-guard.js +55 -0
  39. package/dist/index.d.ts +23 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +41 -0
  42. package/dist/product-extra-ref.d.ts +238 -0
  43. package/dist/product-extra-ref.d.ts.map +1 -0
  44. package/dist/product-extra-ref.js +22 -0
  45. package/dist/routes-admin.d.ts +220 -0
  46. package/dist/routes-admin.d.ts.map +1 -0
  47. package/dist/routes-admin.js +28 -0
  48. package/dist/routes-public.d.ts +1475 -0
  49. package/dist/routes-public.d.ts.map +1 -0
  50. package/dist/routes-public.js +362 -0
  51. package/dist/service-booking-session-bootstrap.d.ts +227 -0
  52. package/dist/service-booking-session-bootstrap.d.ts.map +1 -0
  53. package/dist/service-booking-session-bootstrap.js +287 -0
  54. package/dist/service-boundary-resource-sql.d.ts +18 -0
  55. package/dist/service-boundary-resource-sql.d.ts.map +1 -0
  56. package/dist/service-boundary-resource-sql.js +73 -0
  57. package/dist/service-boundary-sql.d.ts +103 -0
  58. package/dist/service-boundary-sql.d.ts.map +1 -0
  59. package/dist/service-boundary-sql.js +307 -0
  60. package/dist/service-departures-core.d.ts +41 -0
  61. package/dist/service-departures-core.d.ts.map +1 -0
  62. package/dist/service-departures-core.js +92 -0
  63. package/dist/service-departures-extensions.d.ts +46 -0
  64. package/dist/service-departures-extensions.d.ts.map +1 -0
  65. package/dist/service-departures-extensions.js +81 -0
  66. package/dist/service-departures-offers.d.ts +220 -0
  67. package/dist/service-departures-offers.d.ts.map +1 -0
  68. package/dist/service-departures-offers.js +177 -0
  69. package/dist/service-departures-price-preview.d.ts +306 -0
  70. package/dist/service-departures-price-preview.d.ts.map +1 -0
  71. package/dist/service-departures-price-preview.js +383 -0
  72. package/dist/service-departures-pricing-context.d.ts +115 -0
  73. package/dist/service-departures-pricing-context.d.ts.map +1 -0
  74. package/dist/service-departures-pricing-context.js +237 -0
  75. package/dist/service-departures-pricing.d.ts +5 -0
  76. package/dist/service-departures-pricing.d.ts.map +1 -0
  77. package/dist/service-departures-pricing.js +4 -0
  78. package/dist/service-departures.d.ts +192 -0
  79. package/dist/service-departures.d.ts.map +1 -0
  80. package/dist/service-departures.js +213 -0
  81. package/dist/service-intake.d.ts +130 -0
  82. package/dist/service-intake.d.ts.map +1 -0
  83. package/dist/service-intake.js +274 -0
  84. package/dist/service-transport-eligibility.d.ts +10 -0
  85. package/dist/service-transport-eligibility.d.ts.map +1 -0
  86. package/dist/service-transport-eligibility.js +198 -0
  87. package/dist/service.d.ts +1062 -0
  88. package/dist/service.d.ts.map +1 -0
  89. package/dist/service.js +332 -0
  90. package/dist/transport-eligibility.d.ts +4 -0
  91. package/dist/transport-eligibility.d.ts.map +1 -0
  92. package/dist/transport-eligibility.js +2 -0
  93. package/dist/validation/departures.d.ts +1669 -0
  94. package/dist/validation/departures.d.ts.map +1 -0
  95. package/dist/validation/departures.js +397 -0
  96. package/dist/validation/intake.d.ts +147 -0
  97. package/dist/validation/intake.d.ts.map +1 -0
  98. package/dist/validation/intake.js +69 -0
  99. package/dist/validation/offers.d.ts +340 -0
  100. package/dist/validation/offers.d.ts.map +1 -0
  101. package/dist/validation/offers.js +117 -0
  102. package/dist/validation-settings.d.ts +609 -0
  103. package/dist/validation-settings.d.ts.map +1 -0
  104. package/dist/validation-settings.js +235 -0
  105. package/dist/validation-transport-eligibility.d.ts +314 -0
  106. package/dist/validation-transport-eligibility.d.ts.map +1 -0
  107. package/dist/validation-transport-eligibility.js +97 -0
  108. package/dist/validation.d.ts +6 -0
  109. package/dist/validation.d.ts.map +1 -0
  110. package/dist/validation.js +4 -0
  111. package/dist/verification/index.d.ts +12 -0
  112. package/dist/verification/index.d.ts.map +1 -0
  113. package/dist/verification/index.js +18 -0
  114. package/dist/verification/routes-public.d.ts +121 -0
  115. package/dist/verification/routes-public.d.ts.map +1 -0
  116. package/dist/verification/routes-public.js +125 -0
  117. package/dist/verification/schema.d.ts +273 -0
  118. package/dist/verification/schema.d.ts.map +1 -0
  119. package/dist/verification/schema.js +50 -0
  120. package/dist/verification/service.d.ts +114 -0
  121. package/dist/verification/service.d.ts.map +1 -0
  122. package/dist/verification/service.js +283 -0
  123. package/dist/verification/validation.d.ts +98 -0
  124. package/dist/verification/validation.d.ts.map +1 -0
  125. package/dist/verification/validation.js +54 -0
  126. package/package.json +148 -0
@@ -0,0 +1,115 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { SlotRow } from "./service-departures-core.js";
3
+ export type PricingContext = {
4
+ product: {
5
+ id: string;
6
+ sellCurrency: string;
7
+ sellAmountCents: number | null;
8
+ capacityMode: string;
9
+ } | null;
10
+ catalog: {
11
+ id: string;
12
+ currencyCode: string | null;
13
+ } | null;
14
+ option: {
15
+ id: string;
16
+ name: string;
17
+ description: string | null;
18
+ } | null;
19
+ rule: {
20
+ id: string;
21
+ name: string;
22
+ description: string | null;
23
+ pricingMode: string;
24
+ baseSellAmountCents: number | null;
25
+ } | null;
26
+ units: Array<{
27
+ id: string;
28
+ name: string;
29
+ unitType: string;
30
+ minAge: number | null;
31
+ maxAge: number | null;
32
+ occupancyMin: number | null;
33
+ occupancyMax: number | null;
34
+ isRequired: boolean;
35
+ }>;
36
+ unitRules: Array<{
37
+ id: string;
38
+ unitId: string;
39
+ pricingMode: string;
40
+ sellAmountCents: number | null;
41
+ minQuantity: number | null;
42
+ maxQuantity: number | null;
43
+ sortOrder: number;
44
+ }>;
45
+ tiers: Array<{
46
+ id: string;
47
+ optionUnitPriceRuleId: string;
48
+ minQuantity: number;
49
+ maxQuantity: number | null;
50
+ sellAmountCents: number | null;
51
+ sortOrder: number;
52
+ }>;
53
+ extraRules: Array<{
54
+ id: string;
55
+ productExtraId: string | null;
56
+ pricingMode: string;
57
+ sellAmountCents: number | null;
58
+ sortOrder: number;
59
+ }>;
60
+ unitPriceOverrides: Map<string, {
61
+ sellAmountCents: number;
62
+ costAmountCents: number | null;
63
+ }>;
64
+ };
65
+ export declare function centsToAmount(cents: number | null | undefined): number | null;
66
+ export declare function amountToCents(amount: number): number;
67
+ export declare function convertCents(cents: number | null | undefined, rate: number | null | undefined): number | null;
68
+ export declare function convertedAmount(cents: number | null | undefined, rate: number | null | undefined): number | null;
69
+ export declare function getPreferredCurrency(context: PricingContext): string;
70
+ export declare function selectUnitTier(unitRule: PricingContext["unitRules"][number] | undefined, tiers: PricingContext["tiers"], quantity: number): {
71
+ id: string;
72
+ optionUnitPriceRuleId: string;
73
+ minQuantity: number;
74
+ maxQuantity: number | null;
75
+ sellAmountCents: number | null;
76
+ sortOrder: number;
77
+ } | null;
78
+ export declare function selectTierAmount(unitRule: PricingContext["unitRules"][number] | undefined, tiers: PricingContext["tiers"], quantity: number): number | null;
79
+ export declare function selectUnitAmount(context: PricingContext, unitRule: PricingContext["unitRules"][number] | undefined, quantity: number): number | null;
80
+ export declare function buildTravelerRequestedUnits(args: {
81
+ units: PricingContext["units"];
82
+ adults: number;
83
+ children: number;
84
+ infants: number;
85
+ }): {
86
+ unitId?: string;
87
+ requestRef?: string;
88
+ quantity: number;
89
+ }[];
90
+ export declare function resolvePricingContext(db: PostgresJsDatabase, productId: string, optionId?: string | null, departureId?: string | null): Promise<PricingContext>;
91
+ export declare function buildRatePlans(context: PricingContext): {
92
+ id: string;
93
+ active: boolean;
94
+ name: string;
95
+ pricingModel: string;
96
+ basePrices: {
97
+ amount: number;
98
+ currencyCode: string;
99
+ }[];
100
+ roomPrices: {
101
+ amount: number;
102
+ currencyCode: string;
103
+ roomType: {
104
+ id: string;
105
+ name: string;
106
+ occupancy: {
107
+ adultsMin: number;
108
+ adultsMax: number;
109
+ childrenMax: number;
110
+ };
111
+ };
112
+ }[];
113
+ }[];
114
+ export declare function buildDepartureStatus(slot: SlotRow, context: PricingContext): "on_request" | import("./service-boundary-sql.js").StorefrontSlotStatus;
115
+ //# sourceMappingURL=service-departures-pricing-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-departures-pricing-context.d.ts","sourceRoot":"","sources":["../src/service-departures-pricing-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,237 @@
1
+ import { extraPriceRules, loadDeparturePriceOverrides, optionPriceRules, optionUnitPriceRules, optionUnitTiers, priceCatalogs, } from "@voyant-travel/commerce";
2
+ import { and, asc, desc, eq, inArray } from "drizzle-orm";
3
+ import { listOptionUnitFacts, loadProductOptionFacts, loadProductPricingFacts, } from "./service-boundary-sql.js";
4
+ export function centsToAmount(cents) {
5
+ if (cents == null) {
6
+ return null;
7
+ }
8
+ return Number((cents / 100).toFixed(2));
9
+ }
10
+ export function amountToCents(amount) {
11
+ return Math.round(amount * 100);
12
+ }
13
+ export function convertCents(cents, rate) {
14
+ if (cents == null) {
15
+ return null;
16
+ }
17
+ return rate == null ? cents : Math.round(cents * rate);
18
+ }
19
+ export function convertedAmount(cents, rate) {
20
+ return centsToAmount(convertCents(cents, rate));
21
+ }
22
+ export function getPreferredCurrency(context) {
23
+ return context.catalog?.currencyCode ?? context.product?.sellCurrency ?? "EUR";
24
+ }
25
+ export function selectUnitTier(unitRule, tiers, quantity) {
26
+ if (!unitRule) {
27
+ return null;
28
+ }
29
+ return (tiers
30
+ .filter((row) => row.optionUnitPriceRuleId === unitRule.id &&
31
+ quantity >= row.minQuantity &&
32
+ (row.maxQuantity == null || quantity <= row.maxQuantity))
33
+ .sort((a, b) => a.sortOrder - b.sortOrder)[0] ?? null);
34
+ }
35
+ export function selectTierAmount(unitRule, tiers, quantity) {
36
+ if (!unitRule) {
37
+ return null;
38
+ }
39
+ const tier = selectUnitTier(unitRule, tiers, quantity);
40
+ return tier?.sellAmountCents ?? unitRule.sellAmountCents ?? null;
41
+ }
42
+ export function selectUnitAmount(context, unitRule, quantity) {
43
+ if (!unitRule) {
44
+ return null;
45
+ }
46
+ return (context.unitPriceOverrides.get(unitRule.unitId)?.sellAmountCents ??
47
+ selectTierAmount(unitRule, context.tiers, quantity));
48
+ }
49
+ function findNamedUnit(units, matcher) {
50
+ return units.find(matcher) ?? null;
51
+ }
52
+ export function buildTravelerRequestedUnits(args) {
53
+ const requestedUnits = [];
54
+ const normalized = args.units.filter((unit) => unit.unitType === "person");
55
+ const adultUnit = findNamedUnit(normalized, (unit) => (unit.maxAge == null || unit.maxAge >= 18) &&
56
+ (unit.minAge == null || unit.minAge < 18) &&
57
+ !/child|infant/i.test(unit.name)) ?? normalized[0];
58
+ const childUnit = findNamedUnit(normalized, (unit) => /child/i.test(unit.name) || ((unit.maxAge ?? 99) < 18 && (unit.maxAge ?? 99) > 2)) ?? null;
59
+ const infantUnit = findNamedUnit(normalized, (unit) => /infant/i.test(unit.name) || (unit.maxAge != null && unit.maxAge <= 2)) ?? null;
60
+ if (args.adults > 0) {
61
+ requestedUnits.push(adultUnit
62
+ ? { unitId: adultUnit.id, requestRef: adultUnit.id, quantity: args.adults }
63
+ : { quantity: args.adults });
64
+ }
65
+ if (args.children > 0) {
66
+ requestedUnits.push(childUnit
67
+ ? { unitId: childUnit.id, requestRef: childUnit.id, quantity: args.children }
68
+ : { quantity: args.children });
69
+ }
70
+ if (args.infants > 0) {
71
+ requestedUnits.push(infantUnit
72
+ ? { unitId: infantUnit.id, requestRef: infantUnit.id, quantity: args.infants }
73
+ : { quantity: args.infants });
74
+ }
75
+ return requestedUnits;
76
+ }
77
+ export async function resolvePricingContext(db, productId, optionId, departureId) {
78
+ const product = await loadProductPricingFacts(db, productId);
79
+ const [catalog] = await db
80
+ .select({
81
+ id: priceCatalogs.id,
82
+ currencyCode: priceCatalogs.currencyCode,
83
+ })
84
+ .from(priceCatalogs)
85
+ .where(and(eq(priceCatalogs.catalogType, "public"), eq(priceCatalogs.active, true)))
86
+ .orderBy(desc(priceCatalogs.isDefault), asc(priceCatalogs.name))
87
+ .limit(1);
88
+ const resolvedOption = await loadProductOptionFacts(db, { productId, optionId });
89
+ if (!resolvedOption || !catalog) {
90
+ return {
91
+ product: product ?? null,
92
+ catalog: catalog ?? null,
93
+ option: resolvedOption ?? null,
94
+ rule: null,
95
+ units: [],
96
+ unitRules: [],
97
+ tiers: [],
98
+ extraRules: [],
99
+ unitPriceOverrides: new Map(),
100
+ };
101
+ }
102
+ const [rule] = await db
103
+ .select({
104
+ id: optionPriceRules.id,
105
+ name: optionPriceRules.name,
106
+ description: optionPriceRules.description,
107
+ pricingMode: optionPriceRules.pricingMode,
108
+ baseSellAmountCents: optionPriceRules.baseSellAmountCents,
109
+ })
110
+ .from(optionPriceRules)
111
+ .where(and(eq(optionPriceRules.productId, productId), eq(optionPriceRules.optionId, resolvedOption.id), eq(optionPriceRules.priceCatalogId, catalog.id), eq(optionPriceRules.active, true)))
112
+ .orderBy(desc(optionPriceRules.isDefault), asc(optionPriceRules.name))
113
+ .limit(1);
114
+ const units = await listOptionUnitFacts(db, resolvedOption.id);
115
+ if (!rule) {
116
+ return {
117
+ product: product ?? null,
118
+ catalog,
119
+ option: resolvedOption,
120
+ rule: null,
121
+ units,
122
+ unitRules: [],
123
+ tiers: [],
124
+ extraRules: [],
125
+ unitPriceOverrides: new Map(),
126
+ };
127
+ }
128
+ const unitRules = await db
129
+ .select({
130
+ id: optionUnitPriceRules.id,
131
+ unitId: optionUnitPriceRules.unitId,
132
+ pricingMode: optionUnitPriceRules.pricingMode,
133
+ sellAmountCents: optionUnitPriceRules.sellAmountCents,
134
+ minQuantity: optionUnitPriceRules.minQuantity,
135
+ maxQuantity: optionUnitPriceRules.maxQuantity,
136
+ sortOrder: optionUnitPriceRules.sortOrder,
137
+ })
138
+ .from(optionUnitPriceRules)
139
+ .where(and(eq(optionUnitPriceRules.optionPriceRuleId, rule.id), eq(optionUnitPriceRules.active, true)))
140
+ .orderBy(asc(optionUnitPriceRules.sortOrder), asc(optionUnitPriceRules.createdAt));
141
+ const tiers = unitRules.length > 0
142
+ ? await db
143
+ .select({
144
+ id: optionUnitTiers.id,
145
+ optionUnitPriceRuleId: optionUnitTiers.optionUnitPriceRuleId,
146
+ minQuantity: optionUnitTiers.minQuantity,
147
+ maxQuantity: optionUnitTiers.maxQuantity,
148
+ sellAmountCents: optionUnitTiers.sellAmountCents,
149
+ sortOrder: optionUnitTiers.sortOrder,
150
+ })
151
+ .from(optionUnitTiers)
152
+ .where(and(inArray(optionUnitTiers.optionUnitPriceRuleId, unitRules.map((unitRule) => unitRule.id)), eq(optionUnitTiers.active, true)))
153
+ .orderBy(asc(optionUnitTiers.sortOrder), asc(optionUnitTiers.minQuantity))
154
+ : [];
155
+ const extraRules = await db
156
+ .select({
157
+ id: extraPriceRules.id,
158
+ productExtraId: extraPriceRules.productExtraId,
159
+ pricingMode: extraPriceRules.pricingMode,
160
+ sellAmountCents: extraPriceRules.sellAmountCents,
161
+ sortOrder: extraPriceRules.sortOrder,
162
+ })
163
+ .from(extraPriceRules)
164
+ .where(and(eq(extraPriceRules.optionPriceRuleId, rule.id), eq(extraPriceRules.active, true)))
165
+ .orderBy(asc(extraPriceRules.sortOrder), asc(extraPriceRules.createdAt));
166
+ const unitPriceOverrides = departureId
167
+ ? await loadDeparturePriceOverrides(db, {
168
+ departureId,
169
+ catalogId: catalog.id,
170
+ })
171
+ : new Map();
172
+ return {
173
+ product: product ?? null,
174
+ catalog,
175
+ option: resolvedOption,
176
+ rule,
177
+ units,
178
+ unitRules,
179
+ tiers,
180
+ extraRules,
181
+ unitPriceOverrides,
182
+ };
183
+ }
184
+ export function buildRatePlans(context) {
185
+ if (!context.rule) {
186
+ return [];
187
+ }
188
+ const currencyCode = getPreferredCurrency(context);
189
+ const roomPrices = context.units
190
+ .filter((unit) => unit.unitType === "room")
191
+ .map((unit) => {
192
+ const unitRule = context.unitRules.find((row) => row.unitId === unit.id);
193
+ const quantityHint = Math.max(1, unit.occupancyMax ?? unit.occupancyMin ?? 1);
194
+ const amount = centsToAmount(selectUnitAmount(context, unitRule, quantityHint));
195
+ if (amount == null) {
196
+ return null;
197
+ }
198
+ return {
199
+ amount,
200
+ currencyCode,
201
+ roomType: {
202
+ id: unit.id,
203
+ name: unit.name,
204
+ occupancy: {
205
+ adultsMin: unit.occupancyMin ?? 1,
206
+ adultsMax: unit.occupancyMax ?? Math.max(2, unit.occupancyMin ?? 1),
207
+ childrenMax: Math.max(0, (unit.occupancyMax ?? Math.max(2, unit.occupancyMin ?? 1)) - (unit.occupancyMin ?? 1)),
208
+ },
209
+ },
210
+ };
211
+ })
212
+ .filter((value) => value !== null);
213
+ const baseAmount = centsToAmount(context.rule.baseSellAmountCents);
214
+ return [
215
+ {
216
+ id: context.rule.id,
217
+ active: true,
218
+ name: context.rule.name,
219
+ pricingModel: roomPrices.length > 0 ? "per_room_person" : context.rule.pricingMode,
220
+ basePrices: baseAmount == null
221
+ ? []
222
+ : [
223
+ {
224
+ amount: baseAmount,
225
+ currencyCode,
226
+ },
227
+ ],
228
+ roomPrices,
229
+ },
230
+ ];
231
+ }
232
+ export function buildDepartureStatus(slot, context) {
233
+ if (slot.status === "open" && context.product?.capacityMode === "on_request") {
234
+ return "on_request";
235
+ }
236
+ return slot.status;
237
+ }
@@ -0,0 +1,5 @@
1
+ export { getStorefrontProductExtensions } from "./service-departures-extensions.js";
2
+ export { buildOfferPreview, type StorefrontDeparturePricePreviewOfferResolvers, } from "./service-departures-offers.js";
3
+ export { previewStorefrontDeparturePrice } from "./service-departures-price-preview.js";
4
+ export { buildDepartureStatus, buildRatePlans, buildTravelerRequestedUnits, getPreferredCurrency, type PricingContext, resolvePricingContext, } from "./service-departures-pricing-context.js";
5
+ //# sourceMappingURL=service-departures-pricing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-departures-pricing.d.ts","sourceRoot":"","sources":["../src/service-departures-pricing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAA;AACnF,OAAO,EACL,iBAAiB,EACjB,KAAK,6CAA6C,GACnD,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAAE,+BAA+B,EAAE,MAAM,uCAAuC,CAAA;AACvF,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,2BAA2B,EAC3B,oBAAoB,EACpB,KAAK,cAAc,EACnB,qBAAqB,GACtB,MAAM,yCAAyC,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { getStorefrontProductExtensions } from "./service-departures-extensions.js";
2
+ export { buildOfferPreview, } from "./service-departures-offers.js";
3
+ export { previewStorefrontDeparturePrice } from "./service-departures-price-preview.js";
4
+ export { buildDepartureStatus, buildRatePlans, buildTravelerRequestedUnits, getPreferredCurrency, resolvePricingContext, } from "./service-departures-pricing-context.js";
@@ -0,0 +1,192 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { StorefrontDepartureListQuery, StorefrontProductAvailabilitySummaryQuery } from "./validation.js";
3
+ export { buildTravelerRequestedUnits, getStorefrontProductExtensions, previewStorefrontDeparturePrice, type StorefrontDeparturePricePreviewOfferResolvers, } from "./service-departures-pricing.js";
4
+ export declare function getStorefrontDeparture(db: PostgresJsDatabase, departureId: string): Promise<{
5
+ id: string;
6
+ productId: string;
7
+ itineraryId: string;
8
+ optionId: string | null;
9
+ dateLocal: string | null;
10
+ startAt: string | null;
11
+ endAt: string | null;
12
+ timezone: string;
13
+ startTime: {
14
+ id: string;
15
+ label: string | null;
16
+ startTimeLocal: string;
17
+ durationMinutes: number | null;
18
+ } | null;
19
+ meetingPoint: string | null;
20
+ capacity: number | null;
21
+ remaining: number | null;
22
+ departureStatus: "on_request" | import("./service-boundary-sql.js").StorefrontSlotStatus;
23
+ nights: number | null;
24
+ days: number | null;
25
+ ratePlans: {
26
+ id: string;
27
+ active: boolean;
28
+ name: string;
29
+ pricingModel: string;
30
+ basePrices: {
31
+ amount: number;
32
+ currencyCode: string;
33
+ }[];
34
+ roomPrices: {
35
+ amount: number;
36
+ currencyCode: string;
37
+ roomType: {
38
+ id: string;
39
+ name: string;
40
+ occupancy: {
41
+ adultsMin: number;
42
+ adultsMax: number;
43
+ childrenMax: number;
44
+ };
45
+ };
46
+ }[];
47
+ }[];
48
+ resourceManifest: {
49
+ kinds: {
50
+ capacity: number;
51
+ assigned: number;
52
+ available: number;
53
+ kind: string;
54
+ }[];
55
+ resources: {
56
+ id: string;
57
+ kind: string;
58
+ label: string | null;
59
+ refType: string | null;
60
+ refId: string | null;
61
+ capacity: number;
62
+ assigned: number;
63
+ available: number;
64
+ parentId: string | null;
65
+ flags: Record<string, unknown>;
66
+ }[];
67
+ } | null;
68
+ } | null>;
69
+ export declare function listStorefrontProductDepartures(db: PostgresJsDatabase, productId: string, query: StorefrontDepartureListQuery): Promise<{
70
+ data: {
71
+ id: string;
72
+ productId: string;
73
+ itineraryId: string;
74
+ optionId: string | null;
75
+ dateLocal: string | null;
76
+ startAt: string | null;
77
+ endAt: string | null;
78
+ timezone: string;
79
+ startTime: {
80
+ id: string;
81
+ label: string | null;
82
+ startTimeLocal: string;
83
+ durationMinutes: number | null;
84
+ } | null;
85
+ meetingPoint: string | null;
86
+ capacity: number | null;
87
+ remaining: number | null;
88
+ departureStatus: "on_request" | import("./service-boundary-sql.js").StorefrontSlotStatus;
89
+ nights: number | null;
90
+ days: number | null;
91
+ ratePlans: {
92
+ id: string;
93
+ active: boolean;
94
+ name: string;
95
+ pricingModel: string;
96
+ basePrices: {
97
+ amount: number;
98
+ currencyCode: string;
99
+ }[];
100
+ roomPrices: {
101
+ amount: number;
102
+ currencyCode: string;
103
+ roomType: {
104
+ id: string;
105
+ name: string;
106
+ occupancy: {
107
+ adultsMin: number;
108
+ adultsMax: number;
109
+ childrenMax: number;
110
+ };
111
+ };
112
+ }[];
113
+ }[];
114
+ resourceManifest: {
115
+ kinds: {
116
+ capacity: number;
117
+ assigned: number;
118
+ available: number;
119
+ kind: string;
120
+ }[];
121
+ resources: {
122
+ id: string;
123
+ kind: string;
124
+ label: string | null;
125
+ refType: string | null;
126
+ refId: string | null;
127
+ capacity: number;
128
+ assigned: number;
129
+ available: number;
130
+ parentId: string | null;
131
+ flags: Record<string, unknown>;
132
+ }[];
133
+ } | null;
134
+ }[];
135
+ total: number;
136
+ limit: number;
137
+ offset: number;
138
+ }>;
139
+ export declare function getStorefrontProductAvailabilitySummary(db: PostgresJsDatabase, productId: string, query: StorefrontProductAvailabilitySummaryQuery): Promise<{
140
+ productId: string;
141
+ availabilityState: import("./service-departures-core.js").StorefrontProductAvailabilityState;
142
+ counts: {
143
+ total: number;
144
+ open: number;
145
+ closed: number;
146
+ soldOut: number;
147
+ cancelled: number;
148
+ onRequest: number;
149
+ pastCutoff: number;
150
+ tooEarly: number;
151
+ available: number;
152
+ };
153
+ departures: {
154
+ id: string;
155
+ productId: string;
156
+ optionId: string | null;
157
+ dateLocal: string | null;
158
+ startAt: string | null;
159
+ endAt: string | null;
160
+ timezone: string;
161
+ status: "on_request" | import("./service-boundary-sql.js").StorefrontSlotStatus;
162
+ availabilityState: import("./service-departures-core.js").StorefrontProductAvailabilityState;
163
+ capacity: number | null;
164
+ remaining: number | null;
165
+ pastCutoff: boolean;
166
+ tooEarly: boolean;
167
+ }[];
168
+ total: number;
169
+ limit: number;
170
+ offset: number;
171
+ }>;
172
+ export declare function getStorefrontDepartureItinerary(db: PostgresJsDatabase, input: {
173
+ departureId: string;
174
+ productId: string;
175
+ }): Promise<{
176
+ id: string;
177
+ itineraryId: string;
178
+ days: {
179
+ id: string;
180
+ title: string;
181
+ description: string | null;
182
+ thumbnail: {
183
+ url: string;
184
+ } | null;
185
+ segments: {
186
+ id: string;
187
+ title: string;
188
+ description: string | null;
189
+ }[];
190
+ }[];
191
+ } | null>;
192
+ //# sourceMappingURL=service-departures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-departures.d.ts","sourceRoot":"","sources":["../src/service-departures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AA2BjE,OAAO,KAAK,EACV,4BAA4B,EAC5B,yCAAyC,EAC1C,MAAM,iBAAiB,CAAA;AAExB,OAAO,EACL,2BAA2B,EAC3B,8BAA8B,EAC9B,+BAA+B,EAC/B,KAAK,6CAA6C,GACnD,MAAM,iCAAiC,CAAA;AA0CxC,wBAAsB,sBAAsB,CAAC,EAAE,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAoBvF;AAED,wBAAsB,+BAA+B,CACnD,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CpC;AAED,wBAAsB,uCAAuC,CAC3D,EAAE,EAAE,kBAAkB,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,yCAAyC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+FjD;AAED,wBAAsB,+BAA+B,CACnD,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;UAyDlD"}