@voyant-travel/commerce 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +145 -0
  3. package/dist/accepted-quote-version-reservation-golden-flow.test.d.ts +2 -0
  4. package/dist/accepted-quote-version-reservation-golden-flow.test.d.ts.map +1 -0
  5. package/dist/accepted-quote-version-reservation-golden-flow.test.js +398 -0
  6. package/dist/index.d.ts +15 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +14 -0
  9. package/dist/interface.d.ts +18 -0
  10. package/dist/interface.d.ts.map +1 -0
  11. package/dist/interface.js +246 -0
  12. package/dist/interface.test.d.ts +2 -0
  13. package/dist/interface.test.d.ts.map +1 -0
  14. package/dist/interface.test.js +357 -0
  15. package/dist/markets/index.d.ts +11 -0
  16. package/dist/markets/index.d.ts.map +1 -0
  17. package/dist/markets/index.js +12 -0
  18. package/dist/markets/routes.d.ts +1182 -0
  19. package/dist/markets/routes.d.ts.map +1 -0
  20. package/dist/markets/routes.js +209 -0
  21. package/dist/markets/schema.d.ts +1527 -0
  22. package/dist/markets/schema.d.ts.map +1 -0
  23. package/dist/markets/schema.js +240 -0
  24. package/dist/markets/service-core.d.ts +253 -0
  25. package/dist/markets/service-core.d.ts.map +1 -0
  26. package/dist/markets/service-core.js +242 -0
  27. package/dist/markets/service-rules.d.ts +191 -0
  28. package/dist/markets/service-rules.d.ts.map +1 -0
  29. package/dist/markets/service-rules.js +155 -0
  30. package/dist/markets/service-shared.d.ts +36 -0
  31. package/dist/markets/service-shared.d.ts.map +1 -0
  32. package/dist/markets/service-shared.js +7 -0
  33. package/dist/markets/service.d.ts +43 -0
  34. package/dist/markets/service.d.ts.map +1 -0
  35. package/dist/markets/service.js +42 -0
  36. package/dist/markets/validation.d.ts +451 -0
  37. package/dist/markets/validation.d.ts.map +1 -0
  38. package/dist/markets/validation.js +160 -0
  39. package/dist/pricing/events.d.ts +53 -0
  40. package/dist/pricing/events.d.ts.map +1 -0
  41. package/dist/pricing/events.js +28 -0
  42. package/dist/pricing/index.d.ts +15 -0
  43. package/dist/pricing/index.d.ts.map +1 -0
  44. package/dist/pricing/index.js +18 -0
  45. package/dist/pricing/routes-core.d.ts +981 -0
  46. package/dist/pricing/routes-core.d.ts.map +1 -0
  47. package/dist/pricing/routes-core.js +102 -0
  48. package/dist/pricing/routes-public.d.ts +136 -0
  49. package/dist/pricing/routes-public.d.ts.map +1 -0
  50. package/dist/pricing/routes-public.js +14 -0
  51. package/dist/pricing/routes-rules.d.ts +1339 -0
  52. package/dist/pricing/routes-rules.d.ts.map +1 -0
  53. package/dist/pricing/routes-rules.js +138 -0
  54. package/dist/pricing/routes-shared.d.ts +14 -0
  55. package/dist/pricing/routes-shared.d.ts.map +1 -0
  56. package/dist/pricing/routes-shared.js +3 -0
  57. package/dist/pricing/routes.d.ts +7 -0
  58. package/dist/pricing/routes.d.ts.map +1 -0
  59. package/dist/pricing/routes.js +6 -0
  60. package/dist/pricing/schema-catalogs.d.ts +467 -0
  61. package/dist/pricing/schema-catalogs.d.ts.map +1 -0
  62. package/dist/pricing/schema-catalogs.js +47 -0
  63. package/dist/pricing/schema-categories.d.ts +497 -0
  64. package/dist/pricing/schema-categories.d.ts.map +1 -0
  65. package/dist/pricing/schema-categories.js +54 -0
  66. package/dist/pricing/schema-departure-overrides.d.ts +228 -0
  67. package/dist/pricing/schema-departure-overrides.d.ts.map +1 -0
  68. package/dist/pricing/schema-departure-overrides.js +36 -0
  69. package/dist/pricing/schema-option-rules.d.ts +1770 -0
  70. package/dist/pricing/schema-option-rules.d.ts.map +1 -0
  71. package/dist/pricing/schema-option-rules.js +181 -0
  72. package/dist/pricing/schema-policies.d.ts +395 -0
  73. package/dist/pricing/schema-policies.d.ts.map +1 -0
  74. package/dist/pricing/schema-policies.js +41 -0
  75. package/dist/pricing/schema-relations.d.ts +59 -0
  76. package/dist/pricing/schema-relations.d.ts.map +1 -0
  77. package/dist/pricing/schema-relations.js +111 -0
  78. package/dist/pricing/schema-shared.d.ts +11 -0
  79. package/dist/pricing/schema-shared.d.ts.map +1 -0
  80. package/dist/pricing/schema-shared.js +67 -0
  81. package/dist/pricing/schema.d.ts +8 -0
  82. package/dist/pricing/schema.d.ts.map +1 -0
  83. package/dist/pricing/schema.js +7 -0
  84. package/dist/pricing/service-catalog-plane-pricing.d.ts +95 -0
  85. package/dist/pricing/service-catalog-plane-pricing.d.ts.map +1 -0
  86. package/dist/pricing/service-catalog-plane-pricing.js +382 -0
  87. package/dist/pricing/service-catalogs.d.ts +139 -0
  88. package/dist/pricing/service-catalogs.d.ts.map +1 -0
  89. package/dist/pricing/service-catalogs.js +89 -0
  90. package/dist/pricing/service-categories.d.ts +147 -0
  91. package/dist/pricing/service-categories.d.ts.map +1 -0
  92. package/dist/pricing/service-categories.js +105 -0
  93. package/dist/pricing/service-departure-overrides.d.ts +67 -0
  94. package/dist/pricing/service-departure-overrides.d.ts.map +1 -0
  95. package/dist/pricing/service-departure-overrides.js +54 -0
  96. package/dist/pricing/service-option-rules.d.ts +321 -0
  97. package/dist/pricing/service-option-rules.d.ts.map +1 -0
  98. package/dist/pricing/service-option-rules.js +340 -0
  99. package/dist/pricing/service-policies.d.ts +123 -0
  100. package/dist/pricing/service-policies.d.ts.map +1 -0
  101. package/dist/pricing/service-policies.js +95 -0
  102. package/dist/pricing/service-public.d.ts +89 -0
  103. package/dist/pricing/service-public.d.ts.map +1 -0
  104. package/dist/pricing/service-public.js +473 -0
  105. package/dist/pricing/service-rule-resolver.d.ts +67 -0
  106. package/dist/pricing/service-rule-resolver.d.ts.map +1 -0
  107. package/dist/pricing/service-rule-resolver.js +204 -0
  108. package/dist/pricing/service-shared.d.ts +53 -0
  109. package/dist/pricing/service-shared.d.ts.map +1 -0
  110. package/dist/pricing/service-shared.js +4 -0
  111. package/dist/pricing/service-transfer-rules.d.ts +211 -0
  112. package/dist/pricing/service-transfer-rules.d.ts.map +1 -0
  113. package/dist/pricing/service-transfer-rules.js +139 -0
  114. package/dist/pricing/service.d.ts +79 -0
  115. package/dist/pricing/service.d.ts.map +1 -0
  116. package/dist/pricing/service.js +78 -0
  117. package/dist/pricing/validation-public.d.ts +412 -0
  118. package/dist/pricing/validation-public.d.ts.map +1 -0
  119. package/dist/pricing/validation-public.js +111 -0
  120. package/dist/pricing/validation-shared.d.ts +71 -0
  121. package/dist/pricing/validation-shared.d.ts.map +1 -0
  122. package/dist/pricing/validation-shared.js +63 -0
  123. package/dist/pricing/validation.d.ts +987 -0
  124. package/dist/pricing/validation.d.ts.map +1 -0
  125. package/dist/pricing/validation.js +307 -0
  126. package/dist/promotions/events.d.ts +38 -0
  127. package/dist/promotions/events.d.ts.map +1 -0
  128. package/dist/promotions/events.js +25 -0
  129. package/dist/promotions/index.d.ts +12 -0
  130. package/dist/promotions/index.d.ts.map +1 -0
  131. package/dist/promotions/index.js +17 -0
  132. package/dist/promotions/routes-shared.d.ts +14 -0
  133. package/dist/promotions/routes-shared.d.ts.map +1 -0
  134. package/dist/promotions/routes-shared.js +3 -0
  135. package/dist/promotions/routes.d.ts +395 -0
  136. package/dist/promotions/routes.d.ts.map +1 -0
  137. package/dist/promotions/routes.js +55 -0
  138. package/dist/promotions/schema.d.ts +675 -0
  139. package/dist/promotions/schema.d.ts.map +1 -0
  140. package/dist/promotions/schema.js +126 -0
  141. package/dist/promotions/service-booking-confirmed.d.ts +77 -0
  142. package/dist/promotions/service-booking-confirmed.d.ts.map +1 -0
  143. package/dist/promotions/service-booking-confirmed.js +134 -0
  144. package/dist/promotions/service-boundary-scheduler.d.ts +85 -0
  145. package/dist/promotions/service-boundary-scheduler.d.ts.map +1 -0
  146. package/dist/promotions/service-boundary-scheduler.js +141 -0
  147. package/dist/promotions/service-catalog-evaluator.d.ts +22 -0
  148. package/dist/promotions/service-catalog-evaluator.d.ts.map +1 -0
  149. package/dist/promotions/service-catalog-evaluator.js +33 -0
  150. package/dist/promotions/service-catalog-plane-promotions.d.ts +73 -0
  151. package/dist/promotions/service-catalog-plane-promotions.d.ts.map +1 -0
  152. package/dist/promotions/service-catalog-plane-promotions.js +118 -0
  153. package/dist/promotions/service-evaluator.d.ts +134 -0
  154. package/dist/promotions/service-evaluator.d.ts.map +1 -0
  155. package/dist/promotions/service-evaluator.js +302 -0
  156. package/dist/promotions/service-storefront.d.ts +147 -0
  157. package/dist/promotions/service-storefront.d.ts.map +1 -0
  158. package/dist/promotions/service-storefront.js +326 -0
  159. package/dist/promotions/service.d.ts +143 -0
  160. package/dist/promotions/service.d.ts.map +1 -0
  161. package/dist/promotions/service.js +359 -0
  162. package/dist/promotions/validation.d.ts +195 -0
  163. package/dist/promotions/validation.d.ts.map +1 -0
  164. package/dist/promotions/validation.js +167 -0
  165. package/dist/promotions/workflow-bulk-reindex.d.ts +36 -0
  166. package/dist/promotions/workflow-bulk-reindex.d.ts.map +1 -0
  167. package/dist/promotions/workflow-bulk-reindex.js +53 -0
  168. package/dist/promotions/workflow-runtime.d.ts +17 -0
  169. package/dist/promotions/workflow-runtime.d.ts.map +1 -0
  170. package/dist/promotions/workflow-runtime.js +9 -0
  171. package/dist/runtime.d.ts +18 -0
  172. package/dist/runtime.d.ts.map +1 -0
  173. package/dist/runtime.js +27 -0
  174. package/dist/runtime.test.d.ts +2 -0
  175. package/dist/runtime.test.d.ts.map +1 -0
  176. package/dist/runtime.test.js +25 -0
  177. package/dist/schema.d.ts +5 -0
  178. package/dist/schema.d.ts.map +1 -0
  179. package/dist/schema.js +4 -0
  180. package/dist/sellability/index.d.ts +13 -0
  181. package/dist/sellability/index.d.ts.map +1 -0
  182. package/dist/sellability/index.js +17 -0
  183. package/dist/sellability/routes.d.ts +2332 -0
  184. package/dist/sellability/routes.d.ts.map +1 -0
  185. package/dist/sellability/routes.js +166 -0
  186. package/dist/sellability/schema.d.ts +1716 -0
  187. package/dist/sellability/schema.d.ts.map +1 -0
  188. package/dist/sellability/schema.js +278 -0
  189. package/dist/sellability/service-records.d.ts +316 -0
  190. package/dist/sellability/service-records.d.ts.map +1 -0
  191. package/dist/sellability/service-records.js +253 -0
  192. package/dist/sellability/service-resolve.d.ts +72 -0
  193. package/dist/sellability/service-resolve.d.ts.map +1 -0
  194. package/dist/sellability/service-resolve.js +580 -0
  195. package/dist/sellability/service-shared.d.ts +124 -0
  196. package/dist/sellability/service-shared.d.ts.map +1 -0
  197. package/dist/sellability/service-shared.js +96 -0
  198. package/dist/sellability/service-snapshots.d.ts +191 -0
  199. package/dist/sellability/service-snapshots.d.ts.map +1 -0
  200. package/dist/sellability/service-snapshots.js +153 -0
  201. package/dist/sellability/service.d.ts +1038 -0
  202. package/dist/sellability/service.d.ts.map +1 -0
  203. package/dist/sellability/service.js +17 -0
  204. package/dist/sellability/validation.d.ts +477 -0
  205. package/dist/sellability/validation.d.ts.map +1 -0
  206. package/dist/sellability/validation.js +192 -0
  207. package/dist/types.d.ts +239 -0
  208. package/dist/types.d.ts.map +1 -0
  209. package/dist/types.js +1 -0
  210. package/package.json +62 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Storefront resolvers — populate the previously-empty
3
+ * `/v1/public/products/:productId/offers` and `/v1/public/offers/:slug`
4
+ * endpoints in `@voyant-travel/storefront` with real data.
5
+ *
6
+ * Wire via:
7
+ *
8
+ * const storefrontModule = createStorefrontHonoModule({
9
+ * offers: createPromotionsStorefrontResolvers(),
10
+ * })
11
+ *
12
+ * Per docs/architecture/promotions-architecture.md §8.
13
+ *
14
+ * V1 limitations:
15
+ * - Storefront calls don't carry an audience / market in the request
16
+ * context, so the resolver only filters by `products` + `global`
17
+ * scopes (the catalog plane handles audience-scoped projection).
18
+ * `markets` and `audiences` scoped offers don't appear in this
19
+ * listing endpoint.
20
+ * - Locale-aware offer names: the schema doesn't store translations
21
+ * yet (single-locale offer names per §12.1); `locale` is accepted
22
+ * but ignored. A `promotional_offer_translations` table mirrors
23
+ * `destinations_translations` if/when needed.
24
+ * - `applicableDepartureIds`: always empty per §12.7 — departure-
25
+ * scoped offers aren't modelled in v1.
26
+ * - Only auto-applied offers (no code) appear in `listApplicableOffers`;
27
+ * code-gated offers are still queryable via `getOfferBySlug`.
28
+ */
29
+ import type { AnyDrizzleDb } from "@voyant-travel/db";
30
+ import { type PromotionalOffer } from "./schema.js";
31
+ import { type CodeStatus } from "./service-evaluator.js";
32
+ import type { PromotionalOfferScope } from "./validation.js";
33
+ export interface StorefrontRequestContext {
34
+ db?: AnyDrizzleDb;
35
+ eventBus?: unknown;
36
+ env?: unknown;
37
+ context?: unknown;
38
+ }
39
+ export interface StorefrontPromotionalOffer {
40
+ id: string;
41
+ name: string;
42
+ slug: string | null;
43
+ description: string | null;
44
+ discountType: "percentage" | "fixed_amount";
45
+ discountValue: string;
46
+ currency: string | null;
47
+ applicableProductIds: string[];
48
+ applicableDepartureIds: string[];
49
+ validFrom: string | null;
50
+ validTo: string | null;
51
+ minTravelers: number | null;
52
+ imageMobileUrl: string | null;
53
+ imageDesktopUrl: string | null;
54
+ stackable: boolean;
55
+ createdAt: string;
56
+ updatedAt: string;
57
+ }
58
+ export interface StorefrontOfferApplyInput {
59
+ productId: string;
60
+ departureId?: string | null;
61
+ bookingId?: string | null;
62
+ sessionId?: string | null;
63
+ locale?: string;
64
+ pax: number;
65
+ audience: "staff" | "customer" | "partner" | "supplier";
66
+ market: string;
67
+ basePriceCents: number;
68
+ currency: string;
69
+ }
70
+ export interface StorefrontOfferRedeemInput extends StorefrontOfferApplyInput {
71
+ code: string;
72
+ }
73
+ export interface StorefrontAppliedOffer {
74
+ offerId: string;
75
+ offerName: string;
76
+ discountAppliedCents: number;
77
+ discountedPriceCents: number;
78
+ currency: string;
79
+ discountKind: "percentage" | "fixed_amount";
80
+ discountPercent: number | null;
81
+ discountAmountCents: number | null;
82
+ appliedCode: string | null;
83
+ stackable: boolean;
84
+ }
85
+ export interface StorefrontOfferMutationResult {
86
+ status: "applied" | "not_applicable" | "invalid" | "conflict";
87
+ reason: "offer_not_found" | "offer_expired" | "offer_not_yet_valid" | "code_not_found" | "code_required" | "code_expired" | "code_not_yet_valid" | "scope" | "min_pax" | "eligibility" | "currency" | "no_discount" | "booking_mismatch" | "session_mismatch" | "conflict" | null;
88
+ offer: StorefrontPromotionalOffer | null;
89
+ target: {
90
+ bookingId: string | null;
91
+ sessionId: string | null;
92
+ productId: string;
93
+ departureId: string | null;
94
+ };
95
+ pricing: {
96
+ basePriceCents: number;
97
+ currency: string;
98
+ discountAppliedCents: number;
99
+ discountedPriceCents: number;
100
+ };
101
+ appliedOffers: StorefrontAppliedOffer[];
102
+ conflict: {
103
+ policy: "best_discount_wins" | "stackable_compose";
104
+ autoAppliedOfferIds: string[];
105
+ manualOfferId: string | null;
106
+ selectedOfferIds: string[];
107
+ message: string;
108
+ } | null;
109
+ }
110
+ export interface StorefrontOfferResolvers {
111
+ listApplicableOffers?: (input: {
112
+ productId: string;
113
+ departureId?: string;
114
+ locale?: string;
115
+ } & StorefrontRequestContext) => Promise<StorefrontPromotionalOffer[]> | StorefrontPromotionalOffer[];
116
+ getOfferBySlug?: (input: {
117
+ slug: string;
118
+ locale?: string;
119
+ } & StorefrontRequestContext) => Promise<StorefrontPromotionalOffer | null> | StorefrontPromotionalOffer | null;
120
+ applyOffer?: (input: {
121
+ slug: string;
122
+ body: StorefrontOfferApplyInput;
123
+ } & StorefrontRequestContext) => Promise<StorefrontOfferMutationResult> | StorefrontOfferMutationResult;
124
+ redeemOffer?: (input: {
125
+ body: StorefrontOfferRedeemInput;
126
+ } & StorefrontRequestContext) => Promise<StorefrontOfferMutationResult> | StorefrontOfferMutationResult;
127
+ }
128
+ export declare function createPromotionsStorefrontResolvers(): StorefrontOfferResolvers;
129
+ declare function matchesProduct(scope: PromotionalOfferScope, inLinkTable: boolean): boolean;
130
+ declare function toStorefrontDto(offer: PromotionalOffer, applicableProductIds: string[]): StorefrontPromotionalOffer;
131
+ declare function currentValidityStatus(offer: PromotionalOffer, now: Date): "offer_expired" | "offer_not_yet_valid" | null;
132
+ declare function codeStatusToReason(status: CodeStatus): NonNullable<StorefrontOfferMutationResult["reason"]> | null;
133
+ declare function buildConflict(input: {
134
+ autoAppliedOfferIds: string[];
135
+ manualOfferId: string | null;
136
+ selectedOfferIds: string[];
137
+ manualSelected: boolean;
138
+ }): StorefrontOfferMutationResult["conflict"];
139
+ export declare const __test__: {
140
+ buildConflict: typeof buildConflict;
141
+ codeStatusToReason: typeof codeStatusToReason;
142
+ currentValidityStatus: typeof currentValidityStatus;
143
+ matchesProduct: typeof matchesProduct;
144
+ toStorefrontDto: typeof toStorefrontDto;
145
+ };
146
+ export {};
147
+ //# sourceMappingURL=service-storefront.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-storefront.d.ts","sourceRoot":"","sources":["../../src/promotions/service-storefront.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Storefront resolvers — populate the previously-empty
3
+ * `/v1/public/products/:productId/offers` and `/v1/public/offers/:slug`
4
+ * endpoints in `@voyant-travel/storefront` with real data.
5
+ *
6
+ * Wire via:
7
+ *
8
+ * const storefrontModule = createStorefrontHonoModule({
9
+ * offers: createPromotionsStorefrontResolvers(),
10
+ * })
11
+ *
12
+ * Per docs/architecture/promotions-architecture.md §8.
13
+ *
14
+ * V1 limitations:
15
+ * - Storefront calls don't carry an audience / market in the request
16
+ * context, so the resolver only filters by `products` + `global`
17
+ * scopes (the catalog plane handles audience-scoped projection).
18
+ * `markets` and `audiences` scoped offers don't appear in this
19
+ * listing endpoint.
20
+ * - Locale-aware offer names: the schema doesn't store translations
21
+ * yet (single-locale offer names per §12.1); `locale` is accepted
22
+ * but ignored. A `promotional_offer_translations` table mirrors
23
+ * `destinations_translations` if/when needed.
24
+ * - `applicableDepartureIds`: always empty per §12.7 — departure-
25
+ * scoped offers aren't modelled in v1.
26
+ * - Only auto-applied offers (no code) appear in `listApplicableOffers`;
27
+ * code-gated offers are still queryable via `getOfferBySlug`.
28
+ */
29
+ import { and, eq, gte, inArray, isNull, lte, or } from "drizzle-orm";
30
+ import { promotionalOfferProducts, promotionalOffers } from "./schema.js";
31
+ import { createDrizzleOfferDataSource, evaluateOffersForProduct, } from "./service-evaluator.js";
32
+ export function createPromotionsStorefrontResolvers() {
33
+ return {
34
+ async listApplicableOffers(input) {
35
+ const db = resolveDb(input);
36
+ if (!db)
37
+ return [];
38
+ const now = new Date();
39
+ // Active + currently-valid + auto-applied (no code) offers.
40
+ const baseRows = await db
41
+ .select()
42
+ .from(promotionalOffers)
43
+ .where(and(eq(promotionalOffers.active, true), isNull(promotionalOffers.code), or(isNull(promotionalOffers.validFrom), lte(promotionalOffers.validFrom, now)), or(isNull(promotionalOffers.validUntil), gte(promotionalOffers.validUntil, now))));
44
+ if (baseRows.length === 0)
45
+ return [];
46
+ // Restrict to offers that match the product: either `global` scope
47
+ // (always matches) OR a product-shaped scope whose materialized
48
+ // link table contains this product.
49
+ const productLinkRows = await db
50
+ .select({ offerId: promotionalOfferProducts.offerId })
51
+ .from(promotionalOfferProducts)
52
+ .where(and(eq(promotionalOfferProducts.productId, input.productId), inArray(promotionalOfferProducts.offerId, baseRows.map((r) => r.id))));
53
+ const offersMatchingProduct = new Set(productLinkRows.map((r) => r.offerId));
54
+ const productLinkLookups = await loadApplicableProductIds(db, baseRows.map((r) => r.id));
55
+ const out = [];
56
+ for (const offer of baseRows) {
57
+ if (!matchesProduct(offer.scope, offersMatchingProduct.has(offer.id)))
58
+ continue;
59
+ out.push(toStorefrontDto(offer, productLinkLookups.get(offer.id) ?? []));
60
+ }
61
+ return out;
62
+ },
63
+ async getOfferBySlug(input) {
64
+ const db = resolveDb(input);
65
+ if (!db)
66
+ return null;
67
+ const offer = await findActiveOfferBySlug(db, input.slug);
68
+ if (!offer)
69
+ return null;
70
+ const links = await loadApplicableProductIds(db, [offer.id]);
71
+ return toStorefrontDto(offer, links.get(offer.id) ?? []);
72
+ },
73
+ async applyOffer(input) {
74
+ const db = resolveDb(input);
75
+ if (!db)
76
+ return notConfiguredResult(input.body);
77
+ const offer = await findActiveOfferBySlug(db, input.slug);
78
+ if (!offer) {
79
+ return emptyResult(input.body, "invalid", "offer_not_found", null);
80
+ }
81
+ if (offer.code != null) {
82
+ return emptyResult(input.body, "invalid", "code_required", await dtoForOffer(db, offer));
83
+ }
84
+ const validity = currentValidityStatus(offer, new Date());
85
+ if (validity !== null) {
86
+ return emptyResult(input.body, "invalid", validity, await dtoForOffer(db, offer));
87
+ }
88
+ return evaluateStorefrontMutation(db, input.body, {
89
+ manualOffer: offer,
90
+ code: undefined,
91
+ offer: await dtoForOffer(db, offer),
92
+ });
93
+ },
94
+ async redeemOffer(input) {
95
+ const db = resolveDb(input);
96
+ if (!db)
97
+ return notConfiguredResult(input.body);
98
+ const offer = await findActiveOfferByCode(db, input.body.code);
99
+ return evaluateStorefrontMutation(db, input.body, {
100
+ manualOffer: offer,
101
+ code: input.body.code,
102
+ offer: offer ? await dtoForOffer(db, offer) : null,
103
+ });
104
+ },
105
+ };
106
+ }
107
+ function resolveDb(input) {
108
+ return input.db ?? undefined;
109
+ }
110
+ function matchesProduct(scope, inLinkTable) {
111
+ switch (scope.kind) {
112
+ case "global":
113
+ return true;
114
+ case "products":
115
+ case "categories":
116
+ case "destinations":
117
+ return inLinkTable;
118
+ // markets / audiences scopes don't render via this endpoint in v1
119
+ // (no audience / market in the request context). They're still
120
+ // honored by the catalog projection (PR3) when products are indexed.
121
+ case "markets":
122
+ case "audiences":
123
+ // Fare/cabin-grade scopes need booking-line context and are evaluated
124
+ // by the checkout path rather than this product-listing endpoint.
125
+ case "fare_codes":
126
+ case "cabin_grades":
127
+ return false;
128
+ }
129
+ }
130
+ async function loadApplicableProductIds(db, offerIds) {
131
+ if (offerIds.length === 0)
132
+ return new Map();
133
+ const rows = await db
134
+ .select({
135
+ offerId: promotionalOfferProducts.offerId,
136
+ productId: promotionalOfferProducts.productId,
137
+ })
138
+ .from(promotionalOfferProducts)
139
+ .where(inArray(promotionalOfferProducts.offerId, offerIds));
140
+ const out = new Map();
141
+ for (const row of rows) {
142
+ const list = out.get(row.offerId) ?? [];
143
+ list.push(row.productId);
144
+ out.set(row.offerId, list);
145
+ }
146
+ return out;
147
+ }
148
+ function toStorefrontDto(offer, applicableProductIds) {
149
+ return {
150
+ id: offer.id,
151
+ name: offer.name,
152
+ slug: offer.slug,
153
+ description: offer.description,
154
+ discountType: offer.discountType,
155
+ // The DTO carries `discountValue` as a string for both flavors.
156
+ // Percentage offers store the percent ("20"); fixed_amount offers
157
+ // store the cents amount as a string ("500" for $5.00).
158
+ discountValue: offer.discountType === "percentage"
159
+ ? (offer.discountPercent ?? "0")
160
+ : String(offer.discountAmountCents ?? 0),
161
+ currency: offer.currency,
162
+ applicableProductIds,
163
+ // V1: departure-scoped offers aren't modelled (per §12.7).
164
+ applicableDepartureIds: [],
165
+ validFrom: offer.validFrom?.toISOString() ?? null,
166
+ validTo: offer.validUntil?.toISOString() ?? null,
167
+ minTravelers: typeof offer.conditions === "object" && offer.conditions != null
168
+ ? (offer.conditions.minPax ?? null)
169
+ : null,
170
+ // V1: no merchandising images on offers (per §2 non-goals).
171
+ imageMobileUrl: null,
172
+ imageDesktopUrl: null,
173
+ stackable: offer.stackable,
174
+ createdAt: offer.createdAt.toISOString(),
175
+ updatedAt: offer.updatedAt.toISOString(),
176
+ };
177
+ }
178
+ async function findActiveOfferBySlug(db, slug) {
179
+ const rows = await db
180
+ .select()
181
+ .from(promotionalOffers)
182
+ .where(and(eq(promotionalOffers.slug, slug), eq(promotionalOffers.active, true)))
183
+ .limit(1);
184
+ return rows[0] ?? null;
185
+ }
186
+ async function findActiveOfferByCode(db, code) {
187
+ const rows = await db
188
+ .select()
189
+ .from(promotionalOffers)
190
+ .where(and(eq(promotionalOffers.active, true), eq(promotionalOffers.code, code.toLowerCase())))
191
+ .limit(1);
192
+ return rows[0] ?? null;
193
+ }
194
+ async function dtoForOffer(db, offer) {
195
+ const links = await loadApplicableProductIds(db, [offer.id]);
196
+ return toStorefrontDto(offer, links.get(offer.id) ?? []);
197
+ }
198
+ function currentValidityStatus(offer, now) {
199
+ if (offer.validUntil != null && offer.validUntil < now)
200
+ return "offer_expired";
201
+ if (offer.validFrom != null && offer.validFrom > now)
202
+ return "offer_not_yet_valid";
203
+ return null;
204
+ }
205
+ function emptyResult(input, status, reason, offer) {
206
+ return {
207
+ status,
208
+ reason,
209
+ offer,
210
+ target: targetFromInput(input),
211
+ pricing: {
212
+ basePriceCents: input.basePriceCents,
213
+ currency: input.currency,
214
+ discountAppliedCents: 0,
215
+ discountedPriceCents: input.basePriceCents,
216
+ },
217
+ appliedOffers: [],
218
+ conflict: null,
219
+ };
220
+ }
221
+ function notConfiguredResult(input) {
222
+ return emptyResult(input, "invalid", "offer_not_found", null);
223
+ }
224
+ async function evaluateStorefrontMutation(db, input, options) {
225
+ const evaluation = await evaluateOffersForProduct(createDrizzleOfferDataSource(db), {
226
+ productId: input.productId,
227
+ slice: {
228
+ audience: input.audience,
229
+ market: input.market,
230
+ },
231
+ pax: input.pax,
232
+ code: options.code,
233
+ basePriceCents: input.basePriceCents,
234
+ baseCurrency: input.currency,
235
+ });
236
+ const codeReason = codeStatusToReason(evaluation.codeStatus);
237
+ if (codeReason != null) {
238
+ return emptyResult(input, "invalid", codeReason, options.offer);
239
+ }
240
+ const appliedOffers = evaluation.applied.map(toStorefrontAppliedOffer);
241
+ const manualOfferId = options.manualOffer?.id ?? null;
242
+ const selectedOfferIds = appliedOffers.map((offer) => offer.offerId);
243
+ const manualSelected = manualOfferId ? selectedOfferIds.includes(manualOfferId) : false;
244
+ const autoAppliedOfferIds = appliedOffers
245
+ .filter((offer) => offer.appliedCode == null && offer.offerId !== manualOfferId)
246
+ .map((offer) => offer.offerId);
247
+ const conflict = manualOfferId
248
+ ? buildConflict({
249
+ autoAppliedOfferIds,
250
+ manualOfferId,
251
+ selectedOfferIds,
252
+ manualSelected,
253
+ })
254
+ : null;
255
+ if (evaluation.total.discountAppliedCents <= 0) {
256
+ return {
257
+ ...emptyResult(input, "not_applicable", "no_discount", options.offer),
258
+ conflict,
259
+ };
260
+ }
261
+ const status = manualOfferId && !manualSelected ? "conflict" : "applied";
262
+ return {
263
+ status,
264
+ reason: status === "conflict" ? "conflict" : null,
265
+ offer: options.offer,
266
+ target: targetFromInput(input),
267
+ pricing: {
268
+ basePriceCents: input.basePriceCents,
269
+ currency: input.currency,
270
+ discountAppliedCents: evaluation.total.discountAppliedCents,
271
+ discountedPriceCents: evaluation.total.discountedPriceCents,
272
+ },
273
+ appliedOffers,
274
+ conflict,
275
+ };
276
+ }
277
+ function codeStatusToReason(status) {
278
+ if (status == null || status.kind === "code_valid")
279
+ return null;
280
+ if (status.kind === "code_not_applicable")
281
+ return status.reason;
282
+ return status.kind;
283
+ }
284
+ function toStorefrontAppliedOffer(offer) {
285
+ return {
286
+ offerId: offer.offerId,
287
+ offerName: offer.offerName,
288
+ discountAppliedCents: offer.discountAppliedCents,
289
+ discountedPriceCents: offer.discountedPriceCents,
290
+ currency: offer.currency,
291
+ discountKind: offer.discountKind,
292
+ discountPercent: offer.discountPercent,
293
+ discountAmountCents: offer.discountAmountCents,
294
+ appliedCode: offer.appliedCode,
295
+ stackable: offer.stackable,
296
+ };
297
+ }
298
+ function targetFromInput(input) {
299
+ return {
300
+ bookingId: input.bookingId ?? null,
301
+ sessionId: input.sessionId ?? null,
302
+ productId: input.productId,
303
+ departureId: input.departureId ?? null,
304
+ };
305
+ }
306
+ function buildConflict(input) {
307
+ if (input.autoAppliedOfferIds.length === 0 && input.manualSelected)
308
+ return null;
309
+ const policy = input.manualSelected ? "stackable_compose" : "best_discount_wins";
310
+ return {
311
+ policy,
312
+ autoAppliedOfferIds: input.autoAppliedOfferIds,
313
+ manualOfferId: input.manualOfferId,
314
+ selectedOfferIds: input.selectedOfferIds,
315
+ message: policy === "stackable_compose"
316
+ ? "The manually applied offer composes with selected auto-applied offers because every selected offer is stackable."
317
+ : "The best discount wins when a manually applied or code offer competes with non-stackable auto-applied offers.",
318
+ };
319
+ }
320
+ export const __test__ = {
321
+ buildConflict,
322
+ codeStatusToReason,
323
+ currentValidityStatus,
324
+ matchesProduct,
325
+ toStorefrontDto,
326
+ };
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Promotions service — CRUD over `promotional_offers` plus link-table
3
+ * materialization for product-shaped scopes.
4
+ *
5
+ * Per docs/architecture/promotions-architecture.md §13 (PR1 scope):
6
+ * - listOffers / getOfferById / createOffer / updateOffer / archiveOffer / deleteOffer
7
+ * - recomputeOfferLinks (rebuilds `promotional_offer_products` from current scope)
8
+ * - emit `promotion.changed` (when an `eventBus` is supplied via the runtime arg)
9
+ *
10
+ * The evaluator (PR2) and catalog-plane / booking-engine / scheduler wiring
11
+ * (PR3 + PR4 + PR3.boundary) are NOT in this PR.
12
+ *
13
+ * Cross-module reads:
14
+ * - `categories` scope expands via the Product-owned `product_category_products` table
15
+ * - `destinations` scope expands via the Product-owned `product_destinations` table
16
+ * through a resolver seam or raw SQL fallback. Promotions does not import
17
+ * Product schemas at runtime.
18
+ */
19
+ import type { EventBus } from "@voyant-travel/core";
20
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
21
+ import { type PromotionChangedSource } from "./events.js";
22
+ import { type PromotionalOffer } from "./schema.js";
23
+ import type { InsertPromotionalOffer, PromotionalOfferListQuery, PromotionalOfferScope, UpdatePromotionalOffer } from "./validation.js";
24
+ export interface OfferMutationRuntime {
25
+ /**
26
+ * Optional event bus. When wired, every CRUD path emits
27
+ * `promotion.changed` so the catalog bridge reindexes affected products.
28
+ * Per docs/architecture/promotions-architecture.md §9.1.
29
+ */
30
+ eventBus?: EventBus;
31
+ /**
32
+ * Override the emission `source`. `createOffer` / `deleteOffer` default
33
+ * to `"created"` / `"deleted"`; `updateOffer` / `archiveOffer` default
34
+ * to `"updated"`. The boundary scheduler (PR3) overrides with
35
+ * `"expired"` for `validUntil` crossings.
36
+ */
37
+ source?: PromotionChangedSource;
38
+ /**
39
+ * Optional resolver for Product-owned category/destination scope expansion.
40
+ * Hosts that move Product authoring behind Inventory can inject the
41
+ * appropriate adapter here without making Promotions depend on Product
42
+ * schemas.
43
+ */
44
+ resolveScopeProductIds?: ResolvePromotionalOfferScopeProductIds;
45
+ }
46
+ export type ResolvePromotionalOfferScopeProductIds = (db: PostgresJsDatabase, scope: Extract<PromotionalOfferScope, {
47
+ kind: "categories" | "destinations";
48
+ }>) => Promise<string[]>;
49
+ /**
50
+ * Expand an offer's scope to the product set it covers. Used by
51
+ * `recomputeOfferLinks` and by the event emitter to populate
52
+ * `affected.productIds`.
53
+ *
54
+ * Returns `null` for slice-shaped or checkout-shaped scopes (`global`,
55
+ * `markets`, `audiences`, `fare_codes`, `cabin_grades`)
56
+ * to signal that the link table should be empty AND that the event payload
57
+ * should fall back to `affected: { kind: "all" }` — these scopes can match
58
+ * an unbounded product set and we don't enumerate them at write time
59
+ * (per §9.1's resolution rules).
60
+ */
61
+ export declare function resolveScopeProductIds(db: PostgresJsDatabase, scope: PromotionalOfferScope, resolver?: ResolvePromotionalOfferScopeProductIds): Promise<string[] | null>;
62
+ /**
63
+ * Rebuild the `promotional_offer_products` rows for an offer from its
64
+ * current scope. Idempotent: deletes any prior rows, inserts the
65
+ * freshly-resolved set. Slice-shaped scopes leave the table empty.
66
+ */
67
+ export declare function recomputeOfferLinks(db: PostgresJsDatabase, offerId: string, scope: PromotionalOfferScope, runtime?: OfferMutationRuntime): Promise<{
68
+ productIds: string[] | null;
69
+ }>;
70
+ declare function listOffers(db: PostgresJsDatabase, query: PromotionalOfferListQuery): Promise<{
71
+ data: {
72
+ id: string;
73
+ name: string;
74
+ slug: string;
75
+ description: string | null;
76
+ discountType: "percentage" | "fixed_amount";
77
+ discountPercent: string | null;
78
+ discountAmountCents: number | null;
79
+ currency: string | null;
80
+ scope: {
81
+ kind: "global";
82
+ } | {
83
+ kind: "products";
84
+ productIds: string[];
85
+ } | {
86
+ kind: "categories";
87
+ categoryIds: string[];
88
+ } | {
89
+ kind: "destinations";
90
+ destinationIds: string[];
91
+ } | {
92
+ kind: "markets";
93
+ marketIds: string[];
94
+ } | {
95
+ kind: "audiences";
96
+ audiences: ("staff" | "customer" | "partner" | "supplier")[];
97
+ } | {
98
+ kind: "fare_codes";
99
+ fareCodes: string[];
100
+ } | {
101
+ kind: "cabin_grades";
102
+ cabinGradeCodes: string[];
103
+ };
104
+ conditions: {
105
+ minPax?: number | undefined;
106
+ pastGuestOnly?: boolean | undefined;
107
+ soloTravelerOnly?: boolean | undefined;
108
+ childTravelerOnly?: boolean | undefined;
109
+ familyOnly?: boolean | undefined;
110
+ };
111
+ validFrom: Date | null;
112
+ validUntil: Date | null;
113
+ code: string | null;
114
+ stackable: boolean;
115
+ active: boolean;
116
+ metadata: unknown;
117
+ createdAt: Date;
118
+ updatedAt: Date;
119
+ }[];
120
+ total: number;
121
+ limit: number;
122
+ offset: number;
123
+ }>;
124
+ declare function getOfferById(db: PostgresJsDatabase, id: string): Promise<PromotionalOffer | null>;
125
+ declare function createOffer(db: PostgresJsDatabase, input: InsertPromotionalOffer, runtime?: OfferMutationRuntime): Promise<PromotionalOffer>;
126
+ declare function updateOffer(db: PostgresJsDatabase, id: string, patch: UpdatePromotionalOffer, runtime?: OfferMutationRuntime): Promise<PromotionalOffer | null>;
127
+ declare function archiveOffer(db: PostgresJsDatabase, id: string, runtime?: OfferMutationRuntime): Promise<PromotionalOffer | null>;
128
+ declare function deleteOffer(db: PostgresJsDatabase, id: string, runtime?: OfferMutationRuntime): Promise<{
129
+ id: string;
130
+ } | null>;
131
+ export declare const promotionsService: {
132
+ listOffers: typeof listOffers;
133
+ getOfferById: typeof getOfferById;
134
+ createOffer: typeof createOffer;
135
+ updateOffer: typeof updateOffer;
136
+ archiveOffer: typeof archiveOffer;
137
+ deleteOffer: typeof deleteOffer;
138
+ recomputeOfferLinks: typeof recomputeOfferLinks;
139
+ resolveScopeProductIds: typeof resolveScopeProductIds;
140
+ };
141
+ export type PromotionsService = typeof promotionsService;
142
+ export {};
143
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/promotions/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAEnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,aAAa,CAAA;AACpB,OAAO,EAEL,KAAK,gBAAgB,EAItB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,iBAAiB,CAAA;AAExB,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,sBAAsB,CAAA;IAC/B;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,sCAAsC,CAAA;CAChE;AAED,MAAM,MAAM,sCAAsC,GAAG,CACnD,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,OAAO,CAAC,qBAAqB,EAAE;IAAE,IAAI,EAAE,YAAY,GAAG,cAAc,CAAA;CAAE,CAAC,KAC3E,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;AAUtB;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,qBAAqB,EAC5B,QAAQ,CAAC,EAAE,sCAAsC,GAChD,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAqB1B;AA6CD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,qBAAqB,EAC5B,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;CAAE,CAAC,CAS1C;AA8ED,iBAAe,UAAU,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2EjF;AAUD,iBAAe,YAAY,CAAC,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAOhG;AAED,iBAAe,WAAW,CACxB,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,sBAAsB,EAC7B,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,gBAAgB,CAAC,CAa3B;AAED,iBAAe,WAAW,CACxB,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,sBAAsB,EAC7B,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAyClC;AAED,iBAAe,YAAY,CACzB,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAgBlC;AAED,iBAAe,WAAW,CACxB,EAAE,EAAE,kBAAkB,EACtB,EAAE,EAAE,MAAM,EACV,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAqChC;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;CAS7B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAA"}