@endora-commerce/mod-promotions 0.100.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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/api/promotions-client.d.ts +98 -0
  4. package/dist/admin/api/promotions-client.d.ts.map +1 -0
  5. package/dist/admin/api/promotions-client.js +40 -0
  6. package/dist/admin/api/promotions-client.js.map +1 -0
  7. package/dist/admin/components/CouponGeneratorForm.d.ts +10 -0
  8. package/dist/admin/components/CouponGeneratorForm.d.ts.map +1 -0
  9. package/dist/admin/components/CouponGeneratorForm.js +56 -0
  10. package/dist/admin/components/CouponGeneratorForm.js.map +1 -0
  11. package/dist/admin/index.d.ts +43 -0
  12. package/dist/admin/index.d.ts.map +1 -0
  13. package/dist/admin/index.js +69 -0
  14. package/dist/admin/index.js.map +1 -0
  15. package/dist/admin/pages/PromotionEditPage.d.ts +9 -0
  16. package/dist/admin/pages/PromotionEditPage.d.ts.map +1 -0
  17. package/dist/admin/pages/PromotionEditPage.js +333 -0
  18. package/dist/admin/pages/PromotionEditPage.js.map +1 -0
  19. package/dist/admin/pages/PromotionRulesPage.d.ts +14 -0
  20. package/dist/admin/pages/PromotionRulesPage.d.ts.map +1 -0
  21. package/dist/admin/pages/PromotionRulesPage.js +86 -0
  22. package/dist/admin/pages/PromotionRulesPage.js.map +1 -0
  23. package/dist/admin/pages/PromotionStatsPage.d.ts +13 -0
  24. package/dist/admin/pages/PromotionStatsPage.d.ts.map +1 -0
  25. package/dist/admin/pages/PromotionStatsPage.js +44 -0
  26. package/dist/admin/pages/PromotionStatsPage.js.map +1 -0
  27. package/dist/admin/pages/PromotionsPage.d.ts +9 -0
  28. package/dist/admin/pages/PromotionsPage.d.ts.map +1 -0
  29. package/dist/admin/pages/PromotionsPage.js +79 -0
  30. package/dist/admin/pages/PromotionsPage.js.map +1 -0
  31. package/dist/backend/actions/amount-off-cart.d.ts +4 -0
  32. package/dist/backend/actions/amount-off-cart.d.ts.map +1 -0
  33. package/dist/backend/actions/amount-off-cart.js +16 -0
  34. package/dist/backend/actions/amount-off-cart.js.map +1 -0
  35. package/dist/backend/actions/buy-x-get-y-free.d.ts +8 -0
  36. package/dist/backend/actions/buy-x-get-y-free.d.ts.map +1 -0
  37. package/dist/backend/actions/buy-x-get-y-free.js +20 -0
  38. package/dist/backend/actions/buy-x-get-y-free.js.map +1 -0
  39. package/dist/backend/actions/buy-x-units-amount-off.d.ts +4 -0
  40. package/dist/backend/actions/buy-x-units-amount-off.d.ts.map +1 -0
  41. package/dist/backend/actions/buy-x-units-amount-off.js +19 -0
  42. package/dist/backend/actions/buy-x-units-amount-off.js.map +1 -0
  43. package/dist/backend/actions/buy-x-units-percent-off.d.ts +4 -0
  44. package/dist/backend/actions/buy-x-units-percent-off.d.ts.map +1 -0
  45. package/dist/backend/actions/buy-x-units-percent-off.js +15 -0
  46. package/dist/backend/actions/buy-x-units-percent-off.js.map +1 -0
  47. package/dist/backend/actions/buy-x-units-y-free.d.ts +7 -0
  48. package/dist/backend/actions/buy-x-units-y-free.d.ts.map +1 -0
  49. package/dist/backend/actions/buy-x-units-y-free.js +21 -0
  50. package/dist/backend/actions/buy-x-units-y-free.js.map +1 -0
  51. package/dist/backend/actions/every-nth-product-percent-off.d.ts +7 -0
  52. package/dist/backend/actions/every-nth-product-percent-off.d.ts.map +1 -0
  53. package/dist/backend/actions/every-nth-product-percent-off.js +17 -0
  54. package/dist/backend/actions/every-nth-product-percent-off.js.map +1 -0
  55. package/dist/backend/actions/free-delivery.d.ts +4 -0
  56. package/dist/backend/actions/free-delivery.d.ts.map +1 -0
  57. package/dist/backend/actions/free-delivery.js +11 -0
  58. package/dist/backend/actions/free-delivery.js.map +1 -0
  59. package/dist/backend/actions/percentage-off-cart.d.ts +4 -0
  60. package/dist/backend/actions/percentage-off-cart.d.ts.map +1 -0
  61. package/dist/backend/actions/percentage-off-cart.js +11 -0
  62. package/dist/backend/actions/percentage-off-cart.js.map +1 -0
  63. package/dist/backend/actions/spend-x-amount-off.d.ts +4 -0
  64. package/dist/backend/actions/spend-x-amount-off.d.ts.map +1 -0
  65. package/dist/backend/actions/spend-x-amount-off.js +17 -0
  66. package/dist/backend/actions/spend-x-amount-off.js.map +1 -0
  67. package/dist/backend/actions/spend-x-percent-off.d.ts +4 -0
  68. package/dist/backend/actions/spend-x-percent-off.d.ts.map +1 -0
  69. package/dist/backend/actions/spend-x-percent-off.js +18 -0
  70. package/dist/backend/actions/spend-x-percent-off.js.map +1 -0
  71. package/dist/backend/actions/types.d.ts +37 -0
  72. package/dist/backend/actions/types.d.ts.map +1 -0
  73. package/dist/backend/actions/types.js +2 -0
  74. package/dist/backend/actions/types.js.map +1 -0
  75. package/dist/backend/actions/util.d.ts +13 -0
  76. package/dist/backend/actions/util.d.ts.map +1 -0
  77. package/dist/backend/actions/util.js +18 -0
  78. package/dist/backend/actions/util.js.map +1 -0
  79. package/dist/backend/entities/coupon-batch.entity.d.ts +21 -0
  80. package/dist/backend/entities/coupon-batch.entity.d.ts.map +1 -0
  81. package/dist/backend/entities/coupon-batch.entity.js +78 -0
  82. package/dist/backend/entities/coupon-batch.entity.js.map +1 -0
  83. package/dist/backend/entities/promotion-coupon.entity.d.ts +17 -0
  84. package/dist/backend/entities/promotion-coupon.entity.d.ts.map +1 -0
  85. package/dist/backend/entities/promotion-coupon.entity.js +64 -0
  86. package/dist/backend/entities/promotion-coupon.entity.js.map +1 -0
  87. package/dist/backend/entities/promotion-rule.entity.d.ts +16 -0
  88. package/dist/backend/entities/promotion-rule.entity.d.ts.map +1 -0
  89. package/dist/backend/entities/promotion-rule.entity.js +57 -0
  90. package/dist/backend/entities/promotion-rule.entity.js.map +1 -0
  91. package/dist/backend/entities/promotion-usage-counter.entity.d.ts +15 -0
  92. package/dist/backend/entities/promotion-usage-counter.entity.d.ts.map +1 -0
  93. package/dist/backend/entities/promotion-usage-counter.entity.js +48 -0
  94. package/dist/backend/entities/promotion-usage-counter.entity.js.map +1 -0
  95. package/dist/backend/entities/promotion-usage.entity.d.ts +21 -0
  96. package/dist/backend/entities/promotion-usage.entity.d.ts.map +1 -0
  97. package/dist/backend/entities/promotion-usage.entity.js +89 -0
  98. package/dist/backend/entities/promotion-usage.entity.js.map +1 -0
  99. package/dist/backend/entities/promotion.entity.d.ts +63 -0
  100. package/dist/backend/entities/promotion.entity.d.ts.map +1 -0
  101. package/dist/backend/entities/promotion.entity.js +194 -0
  102. package/dist/backend/entities/promotion.entity.js.map +1 -0
  103. package/dist/backend/index.d.ts +124 -0
  104. package/dist/backend/index.d.ts.map +1 -0
  105. package/dist/backend/index.js +182 -0
  106. package/dist/backend/index.js.map +1 -0
  107. package/dist/backend/routes.d.ts +88 -0
  108. package/dist/backend/routes.d.ts.map +1 -0
  109. package/dist/backend/routes.js +256 -0
  110. package/dist/backend/routes.js.map +1 -0
  111. package/dist/backend/services/coupon-service.d.ts +40 -0
  112. package/dist/backend/services/coupon-service.d.ts.map +1 -0
  113. package/dist/backend/services/coupon-service.js +178 -0
  114. package/dist/backend/services/coupon-service.js.map +1 -0
  115. package/dist/backend/services/promotion-action-registry.d.ts +20 -0
  116. package/dist/backend/services/promotion-action-registry.d.ts.map +1 -0
  117. package/dist/backend/services/promotion-action-registry.js +57 -0
  118. package/dist/backend/services/promotion-action-registry.js.map +1 -0
  119. package/dist/backend/services/promotion-code-port.d.ts +24 -0
  120. package/dist/backend/services/promotion-code-port.d.ts.map +1 -0
  121. package/dist/backend/services/promotion-code-port.js +53 -0
  122. package/dist/backend/services/promotion-code-port.js.map +1 -0
  123. package/dist/backend/services/promotion-currency-reference.d.ts +13 -0
  124. package/dist/backend/services/promotion-currency-reference.d.ts.map +1 -0
  125. package/dist/backend/services/promotion-currency-reference.js +25 -0
  126. package/dist/backend/services/promotion-currency-reference.js.map +1 -0
  127. package/dist/backend/services/promotion-rule-evaluator.d.ts +32 -0
  128. package/dist/backend/services/promotion-rule-evaluator.d.ts.map +1 -0
  129. package/dist/backend/services/promotion-rule-evaluator.js +131 -0
  130. package/dist/backend/services/promotion-rule-evaluator.js.map +1 -0
  131. package/dist/backend/services/promotion-rule-service.d.ts +62 -0
  132. package/dist/backend/services/promotion-rule-service.d.ts.map +1 -0
  133. package/dist/backend/services/promotion-rule-service.js +95 -0
  134. package/dist/backend/services/promotion-rule-service.js.map +1 -0
  135. package/dist/backend/services/promotion-rule-store.d.ts +33 -0
  136. package/dist/backend/services/promotion-rule-store.d.ts.map +1 -0
  137. package/dist/backend/services/promotion-rule-store.js +88 -0
  138. package/dist/backend/services/promotion-rule-store.js.map +1 -0
  139. package/dist/backend/services/promotion-service.d.ts +312 -0
  140. package/dist/backend/services/promotion-service.d.ts.map +1 -0
  141. package/dist/backend/services/promotion-service.js +762 -0
  142. package/dist/backend/services/promotion-service.js.map +1 -0
  143. package/dist/backend/services/promotion-stats-service.d.ts +15 -0
  144. package/dist/backend/services/promotion-stats-service.d.ts.map +1 -0
  145. package/dist/backend/services/promotion-stats-service.js +66 -0
  146. package/dist/backend/services/promotion-stats-service.js.map +1 -0
  147. package/dist/backend/services/promotion-usage-service.d.ts +46 -0
  148. package/dist/backend/services/promotion-usage-service.d.ts.map +1 -0
  149. package/dist/backend/services/promotion-usage-service.js +168 -0
  150. package/dist/backend/services/promotion-usage-service.js.map +1 -0
  151. package/dist/manifest.d.ts +172 -0
  152. package/dist/manifest.d.ts.map +1 -0
  153. package/dist/manifest.js +140 -0
  154. package/dist/manifest.js.map +1 -0
  155. package/dist/migrations/20260505T074605_promotions_criteria.d.ts +19 -0
  156. package/dist/migrations/20260505T074605_promotions_criteria.d.ts.map +1 -0
  157. package/dist/migrations/20260505T074605_promotions_criteria.js +23 -0
  158. package/dist/migrations/20260505T074605_promotions_criteria.js.map +1 -0
  159. package/dist/migrations/20260618T100727_promotions_engine.d.ts +19 -0
  160. package/dist/migrations/20260618T100727_promotions_engine.d.ts.map +1 -0
  161. package/dist/migrations/20260618T100727_promotions_engine.js +148 -0
  162. package/dist/migrations/20260618T100727_promotions_engine.js.map +1 -0
  163. package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.d.ts +42 -0
  164. package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.d.ts.map +1 -0
  165. package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.js +76 -0
  166. package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.js.map +1 -0
  167. package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.d.ts +27 -0
  168. package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.d.ts.map +1 -0
  169. package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.js +44 -0
  170. package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.js.map +1 -0
  171. package/dist/migrations/index.d.ts +39 -0
  172. package/dist/migrations/index.d.ts.map +1 -0
  173. package/dist/migrations/index.js +44 -0
  174. package/dist/migrations/index.js.map +1 -0
  175. package/dist/ports/index.d.ts +106 -0
  176. package/dist/ports/index.d.ts.map +1 -0
  177. package/dist/ports/index.js +2 -0
  178. package/dist/ports/index.js.map +1 -0
  179. package/docs/promotions.md +96 -0
  180. package/i18n/en.json +34 -0
  181. package/i18n/pl.json +34 -0
  182. package/package.json +101 -0
  183. package/tailwind.css +14 -0
@@ -0,0 +1,256 @@
1
+ import { bulkCouponActiveRequestSchema, cartSnapshotSchema, generateCouponsRequestSchema, upsertPromotionRequestSchema, upsertPromotionRuleRequestSchema, } from '@endora-commerce/contracts';
2
+ import { promotionStatsGroupBySchema } from '@endora-commerce/contracts';
3
+ import { PROMOTION_PERMISSIONS } from '../manifest.js';
4
+ export async function registerPromotionRoutes(app, deps) {
5
+ const { promotionService, couponService, ruleStore, statsService, requireAdmin, catalogPromoAttributes, ruleTargets, } = deps;
6
+ const parseStatsQuery = (q) => {
7
+ const out = {};
8
+ if (typeof q['from'] === 'string')
9
+ out.from = q['from'];
10
+ if (typeof q['to'] === 'string')
11
+ out.to = q['to'];
12
+ const gb = promotionStatsGroupBySchema.safeParse(q['groupBy']);
13
+ if (gb.success)
14
+ out.groupBy = gb.data;
15
+ return out;
16
+ };
17
+ const readGate = requireAdmin(PROMOTION_PERMISSIONS.READ);
18
+ const writeGate = requireAdmin(PROMOTION_PERMISSIONS.WRITE);
19
+ const deleteGate = requireAdmin(PROMOTION_PERMISSIONS.DELETE);
20
+ app.get('/api/v1/admin/promotions', { preHandler: readGate }, async () => {
21
+ const rows = await promotionService.list();
22
+ return { data: rows.map(serialize) };
23
+ });
24
+ app.get('/api/v1/admin/promotions/:id', { preHandler: readGate }, async (request) => {
25
+ const row = await promotionService.getById(request.params.id);
26
+ return { data: serialize(row) };
27
+ });
28
+ // Feature 045 — action catalogue introspection for the admin action picker.
29
+ app.get('/api/v1/admin/promotions/action-types', { preHandler: readGate }, async () => {
30
+ return { data: { items: promotionService.listActionTypes() } };
31
+ });
32
+ // Feature 045 (US6) — standalone named rules.
33
+ app.get('/api/v1/admin/promotion-rules', { preHandler: readGate }, async () => {
34
+ const rows = await ruleStore.list();
35
+ return { data: rows.map(serializeRule) };
36
+ });
37
+ app.get('/api/v1/admin/promotion-rules/:id', { preHandler: readGate }, async (request) => {
38
+ const row = await ruleStore.getById(request.params.id);
39
+ const usedBy = await ruleStore.usedBy(request.params.id);
40
+ return { data: serializeRule(row), usedBy };
41
+ });
42
+ app.post('/api/v1/admin/promotion-rules', { preHandler: writeGate, schema: { body: upsertPromotionRuleRequestSchema } }, async (request, reply) => {
43
+ const body = upsertPromotionRuleRequestSchema.parse(request.body);
44
+ const row = await ruleStore.create({
45
+ name: body.name,
46
+ ...(body.description !== undefined ? { description: body.description } : {}),
47
+ definition: body.definition,
48
+ });
49
+ reply.status(201);
50
+ return { data: serializeRule(row) };
51
+ });
52
+ app.put('/api/v1/admin/promotion-rules/:id', { preHandler: writeGate, schema: { body: upsertPromotionRuleRequestSchema } }, async (request) => {
53
+ const body = upsertPromotionRuleRequestSchema.parse(request.body);
54
+ const row = await ruleStore.update(request.params.id, {
55
+ name: body.name,
56
+ ...(body.description !== undefined ? { description: body.description } : {}),
57
+ definition: body.definition,
58
+ });
59
+ return { data: serializeRule(row) };
60
+ });
61
+ app.delete('/api/v1/admin/promotion-rules/:id', { preHandler: deleteGate }, async (request, reply) => {
62
+ await ruleStore.remove(request.params.id);
63
+ return reply.status(204).send();
64
+ });
65
+ app.post('/api/v1/admin/promotions', { preHandler: writeGate, schema: { body: upsertPromotionRequestSchema } }, async (request, reply) => {
66
+ const body = upsertPromotionRequestSchema.parse(request.body);
67
+ const row = await promotionService.upsert(toUpsertInput(body));
68
+ reply.status(201);
69
+ return { data: serialize(row) };
70
+ });
71
+ app.put('/api/v1/admin/promotions/:id', { preHandler: writeGate, schema: { body: upsertPromotionRequestSchema } }, async (request) => {
72
+ const body = upsertPromotionRequestSchema.parse(request.body);
73
+ const row = await promotionService.updateById(request.params.id, toUpsertInput(body));
74
+ return { data: serialize(row) };
75
+ });
76
+ // Feature 012 / US8 — picker payload for the Promotion Rule editor.
77
+ app.get('/api/v1/admin/promotions/rule-targets/attributes', { preHandler: readGate }, async () => {
78
+ const keys = await catalogPromoAttributes.promoRuleAttributeKeys();
79
+ const items = [];
80
+ for (const k of keys) {
81
+ const meta = await catalogPromoAttributes.getAttributeWithOptions(k);
82
+ if (!meta)
83
+ continue;
84
+ const isSelectStyle = meta.valueType === 'select' ||
85
+ meta.valueType === 'enum' ||
86
+ meta.valueType === 'multiselect';
87
+ items.push({
88
+ id: meta.id,
89
+ key: meta.key,
90
+ label: meta.label,
91
+ labelDefault: meta.labelDefault,
92
+ valueType: meta.valueType,
93
+ ...(isSelectStyle ? { options: meta.options } : {}),
94
+ });
95
+ }
96
+ return { data: { items } };
97
+ });
98
+ // Feature 045 (US3) — single coupon management for a promotion.
99
+ app.get('/api/v1/admin/promotions/:id/coupons', { preHandler: readGate }, async (request) => {
100
+ const coupons = await couponService.listForPromotion(request.params.id);
101
+ return { data: coupons.map(serializeCoupon) };
102
+ });
103
+ app.post('/api/v1/admin/promotions/:id/coupons', { preHandler: writeGate }, async (request, reply) => {
104
+ const code = typeof request.body?.code === 'string' ? request.body.code.trim() : '';
105
+ if (!code) {
106
+ reply.status(400);
107
+ return { error: { code: 'validation_failed', message: 'code is required' } };
108
+ }
109
+ const coupon = await couponService.createSingle(request.params.id, code);
110
+ reply.status(201);
111
+ return { data: serializeCoupon(coupon) };
112
+ });
113
+ // Bulk activate/deactivate selected coupons of a promotion.
114
+ app.post('/api/v1/admin/promotions/:id/coupons/bulk-active', { preHandler: writeGate, schema: { body: bulkCouponActiveRequestSchema } }, async (request) => {
115
+ const body = bulkCouponActiveRequestSchema.parse(request.body);
116
+ const updated = await couponService.setActiveBulk(request.params.id, body.couponIds, body.isActive);
117
+ return { data: { updated } };
118
+ });
119
+ // Feature 045 (US4) — bulk coupon generator.
120
+ app.post('/api/v1/admin/promotions/:id/coupon-batches', { preHandler: writeGate, schema: { body: generateCouponsRequestSchema } }, async (request, reply) => {
121
+ const body = generateCouponsRequestSchema.parse(request.body);
122
+ const result = await couponService.generateBatch(request.params.id, body);
123
+ reply.status(201);
124
+ return { data: { batch: { id: result.batch.id }, generated: result.generated } };
125
+ });
126
+ app.get('/api/v1/admin/promotions/:id/coupon-batches/:batchId/export', { preHandler: readGate, config: { streamingResponse: true } }, async (request, reply) => {
127
+ const codes = await couponService.listBatchCodes(request.params.batchId);
128
+ reply.header('content-type', 'text/csv; charset=utf-8');
129
+ reply.header('content-disposition', `attachment; filename="coupons-${request.params.batchId}.csv"`);
130
+ return reply.send(`code\n${codes.join('\n')}\n`);
131
+ });
132
+ // Feature 045 (T033) — rule-target pickers feeding the Rule Builder.
133
+ app.get('/api/v1/admin/promotions/rule-targets/sales-channels', { preHandler: readGate }, async () => ({
134
+ data: { items: (await ruleTargets.salesChannels?.()) ?? [] },
135
+ }));
136
+ app.get('/api/v1/admin/promotions/rule-targets/customer-groups', { preHandler: readGate }, async () => ({
137
+ data: { items: (await ruleTargets.customerGroups?.()) ?? [] },
138
+ }));
139
+ app.get('/api/v1/admin/promotions/rule-targets/organizations', { preHandler: readGate }, async () => ({
140
+ data: { items: (await ruleTargets.organizations?.()) ?? [] },
141
+ }));
142
+ app.get('/api/v1/admin/promotions/rule-targets/categories', { preHandler: readGate }, async () => ({
143
+ data: { items: (await ruleTargets.categories?.()) ?? [] },
144
+ }));
145
+ app.get('/api/v1/admin/promotions/rule-targets/payment-methods', { preHandler: readGate }, async () => ({
146
+ data: { items: (await ruleTargets.paymentMethods?.()) ?? [] },
147
+ }));
148
+ app.get('/api/v1/admin/promotions/rule-targets/delivery-methods', { preHandler: readGate }, async () => ({
149
+ data: { items: (await ruleTargets.deliveryMethods?.()) ?? [] },
150
+ }));
151
+ app.delete('/api/v1/admin/promotions/:id', { preHandler: deleteGate }, async (request, reply) => {
152
+ await promotionService.remove(request.params.id);
153
+ return reply.status(204).send();
154
+ });
155
+ // Feature 045 (US7) — usage statistics.
156
+ app.get('/api/v1/admin/promotions/:id/stats', { preHandler: readGate }, async (request) => {
157
+ const data = await statsService.forPromotion(request.params.id, parseStatsQuery(request.query ?? {}));
158
+ return { data };
159
+ });
160
+ app.get('/api/v1/admin/promotions/coupons/:couponId/stats', { preHandler: readGate }, async (request) => {
161
+ const data = await statsService.forCoupon(request.params.couponId, parseStatsQuery(request.query ?? {}));
162
+ return { data };
163
+ });
164
+ app.post('/api/v1/admin/promotions/preview', { preHandler: readGate, schema: { body: cartSnapshotSchema } }, async (request) => {
165
+ const snapshot = cartSnapshotSchema.parse(request.body);
166
+ const result = await promotionService.applyToCart(snapshot);
167
+ return { data: result };
168
+ });
169
+ }
170
+ /** Pass through validated request fields into the service input. */
171
+ function toUpsertInput(body) {
172
+ return {
173
+ name: body.name,
174
+ ...(body.code !== undefined ? { code: body.code } : {}),
175
+ ...(body.kind !== undefined ? { kind: body.kind } : {}),
176
+ ...(body.value !== undefined ? { value: body.value } : {}),
177
+ ...(body.currency !== undefined ? { currency: body.currency } : {}),
178
+ ...(body.minCartSubtotal !== undefined ? { minCartSubtotal: body.minCartSubtotal } : {}),
179
+ ...(body.validFrom !== undefined ? { validFrom: body.validFrom } : {}),
180
+ ...(body.validUntil !== undefined ? { validUntil: body.validUntil } : {}),
181
+ ...(body.organizationId !== undefined ? { organizationId: body.organizationId } : {}),
182
+ ...(body.customerGroupId !== undefined ? { customerGroupId: body.customerGroupId } : {}),
183
+ ...(body.categoryId !== undefined ? { categoryId: body.categoryId } : {}),
184
+ ...(body.productId !== undefined ? { productId: body.productId } : {}),
185
+ ...(body.criteria !== undefined ? { criteria: body.criteria } : {}),
186
+ ...(body.isActive !== undefined ? { isActive: body.isActive } : {}),
187
+ ...(body.description !== undefined ? { description: body.description } : {}),
188
+ ...(body.priority !== undefined ? { priority: body.priority } : {}),
189
+ ...(body.stopFurther !== undefined ? { stopFurther: body.stopFurther } : {}),
190
+ ...(body.action !== undefined ? { action: body.action } : {}),
191
+ ...(body.ruleId !== undefined ? { ruleId: body.ruleId } : {}),
192
+ ...(body.rule !== undefined ? { rule: body.rule } : {}),
193
+ ...(body.usageLimitGlobal !== undefined ? { usageLimitGlobal: body.usageLimitGlobal } : {}),
194
+ ...(body.usageLimitPerOrganization !== undefined
195
+ ? { usageLimitPerOrganization: body.usageLimitPerOrganization }
196
+ : {}),
197
+ ...(body.usageLimitPerCustomer !== undefined
198
+ ? { usageLimitPerCustomer: body.usageLimitPerCustomer }
199
+ : {}),
200
+ };
201
+ }
202
+ function serializeRule(r) {
203
+ return {
204
+ id: r.id,
205
+ name: r.name,
206
+ description: r.description ?? null,
207
+ definition: r.definition,
208
+ createdAt: r.createdAt.toISOString(),
209
+ updatedAt: r.updatedAt.toISOString(),
210
+ };
211
+ }
212
+ function serializeCoupon(c) {
213
+ return {
214
+ id: c.id,
215
+ promotionId: c.promotionId,
216
+ batchId: c.batchId ?? null,
217
+ code: c.code,
218
+ limitScope: c.limitScope,
219
+ isActive: c.isActive,
220
+ createdAt: c.createdAt.toISOString(),
221
+ };
222
+ }
223
+ function serialize(row) {
224
+ const action = row.actionType
225
+ ? { type: row.actionType, ...row.actionConfig }
226
+ : null;
227
+ return {
228
+ id: row.id,
229
+ code: row.code ?? null,
230
+ name: row.name,
231
+ kind: row.kind ?? null,
232
+ value: row.value != null ? Number(row.value) : null,
233
+ currency: row.currency ?? null,
234
+ minCartSubtotal: row.minCartSubtotal != null ? Number(row.minCartSubtotal) : null,
235
+ validFrom: row.validFrom?.toISOString() ?? null,
236
+ validUntil: row.validUntil?.toISOString() ?? null,
237
+ organizationId: row.organizationId ?? null,
238
+ customerGroupId: row.customerGroupId ?? null,
239
+ categoryId: row.categoryId ?? null,
240
+ productId: row.productId ?? null,
241
+ criteria: row.criteria ?? [],
242
+ isActive: row.isActive,
243
+ description: row.description ?? null,
244
+ priority: row.priority,
245
+ stopFurther: row.stopFurther,
246
+ action,
247
+ ruleId: row.ruleId ?? null,
248
+ rule: row.ruleDefinition ?? null,
249
+ usageLimitGlobal: row.usageLimitGlobal ?? null,
250
+ usageLimitPerOrganization: row.usageLimitPerOrganization ?? null,
251
+ usageLimitPerCustomer: row.usageLimitPerCustomer ?? null,
252
+ createdAt: row.createdAt.toISOString(),
253
+ updatedAt: row.updatedAt.toISOString(),
254
+ };
255
+ }
256
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type GenerateCouponsRequest } from '@endora-commerce/contracts';
3
+ import { PromotionCoupon } from '../entities/promotion-coupon.entity.js';
4
+ import { CouponBatch } from '../entities/coupon-batch.entity.js';
5
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
6
+ /** Hard cap on a single generated batch (avoids a speculative queue, Principle X). */
7
+ export declare const COUPON_BATCH_MAX = 50000;
8
+ export declare function buildCouponCode(req: GenerateCouponsRequest): string;
9
+ /**
10
+ * Feature 045 (US3/US4) — coupon management. Single specified coupons land
11
+ * here with a globally-unique `code`; the generator (US4) bulk-inserts into
12
+ * the same table. The engine and the cart coupon flow resolve a presented
13
+ * code to its promotion through `findByCode`.
14
+ */
15
+ export declare class CouponService {
16
+ #private;
17
+ private readonly emFactory;
18
+ private readonly auditLog?;
19
+ constructor(emFactory: () => EntityManager, auditLog?: AuditPort | undefined);
20
+ listForPromotion(promotionId: string): Promise<PromotionCoupon[]>;
21
+ findByCode(code: string): Promise<PromotionCoupon | null>;
22
+ /**
23
+ * Bulk activate/deactivate coupons by id, scoped to a promotion so an id from
24
+ * another promotion can never be flipped. Returns the number of rows updated.
25
+ */
26
+ setActiveBulk(promotionId: string, couponIds: string[], isActive: boolean): Promise<number>;
27
+ /** Create a single specified coupon for a promotion. Rejects duplicates. */
28
+ createSingle(promotionId: string, code: string): Promise<PromotionCoupon>;
29
+ /**
30
+ * Generate a batch of unique coupon codes (US4). Uniqueness is guaranteed
31
+ * in-batch via a Set and cross-batch via a DB existence check + regenerate;
32
+ * the `code` UNIQUE constraint is the final backstop.
33
+ */
34
+ generateBatch(promotionId: string, req: GenerateCouponsRequest): Promise<{
35
+ batch: CouponBatch;
36
+ generated: number;
37
+ }>;
38
+ listBatchCodes(batchId: string): Promise<string[]>;
39
+ }
40
+ //# sourceMappingURL=coupon-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coupon-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/coupon-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAe,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAGtF,OAAO,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElE,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAsBvC,wBAAgB,eAAe,CAAC,GAAG,EAAE,sBAAsB,GAAG,MAAM,CAGnE;AAED;;;;;GAKG;AACH,qBAAa,aAAa;;IAEtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBADT,SAAS,EAAE,MAAM,aAAa,EAC9B,QAAQ,CAAC,EAAE,SAAS,YAAA;IAoBjC,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAQjE,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAI/D;;;OAGG;IACG,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EAAE,EACnB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,MAAM,CAAC;IAalB,4EAA4E;IACtE,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAuB/E;;;;OAIG;IACG,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,sBAAsB,GAC1B,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IA4E/C,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAQzD"}
@@ -0,0 +1,178 @@
1
+ import { randomInt } from 'node:crypto';
2
+ import { ERROR_CODES } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { Promotion } from '../entities/promotion.entity.js';
5
+ import { PromotionCoupon } from '../entities/promotion-coupon.entity.js';
6
+ import { CouponBatch } from '../entities/coupon-batch.entity.js';
7
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
8
+ /** Hard cap on a single generated batch (avoids a speculative queue, Principle X). */
9
+ export const COUPON_BATCH_MAX = 50_000;
10
+ const ALPHABETS = {
11
+ // Crockford-ish alphabet — no ambiguous I/O/0/1.
12
+ alnum: 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789',
13
+ digits: '0123456789',
14
+ letters: 'ABCDEFGHJKLMNPQRSTUVWXYZ',
15
+ };
16
+ function randomBody(length, alphabet) {
17
+ let out = '';
18
+ for (let i = 0; i < length; i += 1)
19
+ out += alphabet[randomInt(alphabet.length)];
20
+ return out;
21
+ }
22
+ function insertDashes(body, every) {
23
+ if (!every || every <= 0)
24
+ return body;
25
+ const parts = [];
26
+ for (let i = 0; i < body.length; i += every)
27
+ parts.push(body.slice(i, i + every));
28
+ return parts.join('-');
29
+ }
30
+ export function buildCouponCode(req) {
31
+ const body = insertDashes(randomBody(req.length, ALPHABETS[req.format]), req.dashEvery ?? 0);
32
+ return `${req.prefix ?? ''}${body}${req.suffix ?? ''}`;
33
+ }
34
+ /**
35
+ * Feature 045 (US3/US4) — coupon management. Single specified coupons land
36
+ * here with a globally-unique `code`; the generator (US4) bulk-inserts into
37
+ * the same table. The engine and the cart coupon flow resolve a presented
38
+ * code to its promotion through `findByCode`.
39
+ */
40
+ export class CouponService {
41
+ emFactory;
42
+ auditLog;
43
+ constructor(emFactory, auditLog) {
44
+ this.emFactory = emFactory;
45
+ this.auditLog = auditLog;
46
+ }
47
+ #audit(em, action, objectId, stateAfter) {
48
+ if (this.auditLog) {
49
+ recordAuditFromContext(this.auditLog, em, {
50
+ action,
51
+ objectType: 'promotion',
52
+ objectId,
53
+ stateBefore: null,
54
+ stateAfter,
55
+ });
56
+ }
57
+ }
58
+ async listForPromotion(promotionId) {
59
+ return this.emFactory().find(PromotionCoupon, { promotionId }, { orderBy: { createdAt: 'asc' } });
60
+ }
61
+ async findByCode(code) {
62
+ return this.emFactory().findOne(PromotionCoupon, { code, isActive: true });
63
+ }
64
+ /**
65
+ * Bulk activate/deactivate coupons by id, scoped to a promotion so an id from
66
+ * another promotion can never be flipped. Returns the number of rows updated.
67
+ */
68
+ async setActiveBulk(promotionId, couponIds, isActive) {
69
+ if (couponIds.length === 0)
70
+ return 0;
71
+ const em = this.emFactory();
72
+ const coupons = await em.find(PromotionCoupon, {
73
+ id: { $in: couponIds },
74
+ promotionId,
75
+ });
76
+ for (const c of coupons)
77
+ c.isActive = isActive;
78
+ this.#audit(em, 'coupon.set_active_bulk', promotionId, { isActive, count: coupons.length });
79
+ await em.flush();
80
+ return coupons.length;
81
+ }
82
+ /** Create a single specified coupon for a promotion. Rejects duplicates. */
83
+ async createSingle(promotionId, code) {
84
+ const em = this.emFactory();
85
+ const promotion = await em.findOne(Promotion, { id: promotionId });
86
+ if (!promotion) {
87
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Promotion ${promotionId} not found.`);
88
+ }
89
+ const existing = await em.findOne(PromotionCoupon, { code });
90
+ if (existing) {
91
+ throw new HttpError(409, ERROR_CODES.VALIDATION_FAILED, `coupon_code_taken: ${code}`);
92
+ }
93
+ const coupon = em.create(PromotionCoupon, {
94
+ promotionId,
95
+ batchId: null,
96
+ code,
97
+ limitScope: 'per_coupon',
98
+ isActive: true,
99
+ });
100
+ em.persist(coupon);
101
+ this.#audit(em, 'coupon.create_single', promotionId, { code });
102
+ await em.flush();
103
+ return coupon;
104
+ }
105
+ /**
106
+ * Generate a batch of unique coupon codes (US4). Uniqueness is guaranteed
107
+ * in-batch via a Set and cross-batch via a DB existence check + regenerate;
108
+ * the `code` UNIQUE constraint is the final backstop.
109
+ */
110
+ async generateBatch(promotionId, req) {
111
+ if (req.count > COUPON_BATCH_MAX) {
112
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, `batch_too_large: max ${COUPON_BATCH_MAX} per batch`);
113
+ }
114
+ const em = this.emFactory();
115
+ const promotion = await em.findOne(Promotion, { id: promotionId });
116
+ if (!promotion) {
117
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Promotion ${promotionId} not found.`);
118
+ }
119
+ // Generate unique-within-batch codes.
120
+ const codes = new Set();
121
+ let guard = 0;
122
+ const maxAttempts = req.count * 20 + 100;
123
+ while (codes.size < req.count && guard < maxAttempts) {
124
+ codes.add(buildCouponCode(req));
125
+ guard += 1;
126
+ }
127
+ if (codes.size < req.count) {
128
+ throw new HttpError(400, ERROR_CODES.VALIDATION_FAILED, 'coupon_space_exhausted: increase length or widen the format');
129
+ }
130
+ // Drop any that already exist in the DB, then regenerate replacements.
131
+ let candidates = [...codes];
132
+ const existing = await em.find(PromotionCoupon, { code: { $in: candidates } });
133
+ if (existing.length > 0) {
134
+ const taken = new Set(existing.map((c) => c.code));
135
+ const fresh = new Set(candidates.filter((c) => !taken.has(c)));
136
+ while (fresh.size < req.count && guard < maxAttempts) {
137
+ const c = buildCouponCode(req);
138
+ if (!taken.has(c))
139
+ fresh.add(c);
140
+ guard += 1;
141
+ }
142
+ candidates = [...fresh];
143
+ }
144
+ const batch = em.create(CouponBatch, {
145
+ promotionId,
146
+ count: req.count,
147
+ length: req.length,
148
+ format: req.format,
149
+ prefix: req.prefix ?? null,
150
+ suffix: req.suffix ?? null,
151
+ dashEvery: req.dashEvery ?? 0,
152
+ limitScope: req.limitScope,
153
+ });
154
+ // Flush the batch first so the coupons' batch_id FK is satisfied (the
155
+ // coupon entity has no ORM relation, so insert order isn't inferred).
156
+ await em.persistAndFlush(batch);
157
+ for (const code of candidates) {
158
+ em.persist(em.create(PromotionCoupon, {
159
+ promotionId,
160
+ batchId: batch.id,
161
+ code,
162
+ limitScope: req.limitScope,
163
+ isActive: true,
164
+ }));
165
+ }
166
+ this.#audit(em, 'coupon.generate_batch', promotionId, {
167
+ batchId: batch.id,
168
+ generated: candidates.length,
169
+ });
170
+ await em.flush();
171
+ return { batch, generated: candidates.length };
172
+ }
173
+ async listBatchCodes(batchId) {
174
+ const rows = await this.emFactory().find(PromotionCoupon, { batchId }, { orderBy: { createdAt: 'asc' } });
175
+ return rows.map((r) => r.code);
176
+ }
177
+ }
178
+ //# sourceMappingURL=coupon-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coupon-service.js","sourceRoot":"","sources":["../../../src/backend/services/coupon-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { PromotionAction } from '@endora-commerce/contracts';
2
+ import type { CartApplyContext, DiscountResult, PromotionActionDefinition } from '../actions/types.js';
3
+ /**
4
+ * Registry of promotion actions (FR-012). The 10 built-ins register at
5
+ * construction; other modules contribute additional action types via
6
+ * `register(...)` during composition — the same extension-point shape used
7
+ * by the payment / shipping adapter registries.
8
+ */
9
+ export declare class PromotionActionRegistry {
10
+ private readonly defs;
11
+ register<C extends PromotionAction>(def: PromotionActionDefinition<C>): void;
12
+ get(type: string): PromotionActionDefinition | null;
13
+ list(): PromotionActionDefinition[];
14
+ has(type: string): boolean;
15
+ /** Apply a configured action against a cart context. Throws on unknown type. */
16
+ apply(action: PromotionAction, ctx: CartApplyContext): DiscountResult;
17
+ }
18
+ /** Build a registry pre-loaded with all built-in actions. */
19
+ export declare function createPromotionActionRegistry(): PromotionActionRegistry;
20
+ //# sourceMappingURL=promotion-action-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promotion-action-registry.d.ts","sourceRoot":"","sources":["../../../src/backend/services/promotion-action-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAYvG;;;;;GAKG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgD;IAErE,QAAQ,CAAC,CAAC,SAAS,eAAe,EAAE,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC,GAAG,IAAI;IAO5E,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,yBAAyB,GAAG,IAAI;IAInD,IAAI,IAAI,yBAAyB,EAAE;IAInC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,gFAAgF;IAChF,KAAK,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,gBAAgB,GAAG,cAAc;CAKtE;AAED,6DAA6D;AAC7D,wBAAgB,6BAA6B,IAAI,uBAAuB,CAavE"}
@@ -0,0 +1,57 @@
1
+ import { freeDeliveryAction } from '../actions/free-delivery.js';
2
+ import { percentageOffCartAction } from '../actions/percentage-off-cart.js';
3
+ import { amountOffCartAction } from '../actions/amount-off-cart.js';
4
+ import { buyXGetYFreeAction } from '../actions/buy-x-get-y-free.js';
5
+ import { spendXPercentOffAction } from '../actions/spend-x-percent-off.js';
6
+ import { spendXAmountOffAction } from '../actions/spend-x-amount-off.js';
7
+ import { everyNthProductPercentOffAction } from '../actions/every-nth-product-percent-off.js';
8
+ import { buyXUnitsYFreeAction } from '../actions/buy-x-units-y-free.js';
9
+ import { buyXUnitsPercentOffAction } from '../actions/buy-x-units-percent-off.js';
10
+ import { buyXUnitsAmountOffAction } from '../actions/buy-x-units-amount-off.js';
11
+ /**
12
+ * Registry of promotion actions (FR-012). The 10 built-ins register at
13
+ * construction; other modules contribute additional action types via
14
+ * `register(...)` during composition — the same extension-point shape used
15
+ * by the payment / shipping adapter registries.
16
+ */
17
+ export class PromotionActionRegistry {
18
+ defs = new Map();
19
+ register(def) {
20
+ // Definitions are keyed by their action `type`. The cast widens the
21
+ // per-variant config back to the union so heterogeneous definitions can
22
+ // live in one map; `apply` is only ever invoked with the matching variant.
23
+ this.defs.set(def.type, def);
24
+ }
25
+ get(type) {
26
+ return this.defs.get(type) ?? null;
27
+ }
28
+ list() {
29
+ return [...this.defs.values()];
30
+ }
31
+ has(type) {
32
+ return this.defs.has(type);
33
+ }
34
+ /** Apply a configured action against a cart context. Throws on unknown type. */
35
+ apply(action, ctx) {
36
+ const def = this.defs.get(action.type);
37
+ if (!def)
38
+ throw new Error(`unknown_promotion_action: ${action.type}`);
39
+ return def.apply(action, ctx);
40
+ }
41
+ }
42
+ /** Build a registry pre-loaded with all built-in actions. */
43
+ export function createPromotionActionRegistry() {
44
+ const registry = new PromotionActionRegistry();
45
+ registry.register(freeDeliveryAction);
46
+ registry.register(percentageOffCartAction);
47
+ registry.register(amountOffCartAction);
48
+ registry.register(buyXGetYFreeAction);
49
+ registry.register(spendXPercentOffAction);
50
+ registry.register(spendXAmountOffAction);
51
+ registry.register(everyNthProductPercentOffAction);
52
+ registry.register(buyXUnitsYFreeAction);
53
+ registry.register(buyXUnitsPercentOffAction);
54
+ registry.register(buyXUnitsAmountOffAction);
55
+ return registry;
56
+ }
57
+ //# sourceMappingURL=promotion-action-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promotion-action-registry.js","sourceRoot":"","sources":["../../../src/backend/services/promotion-action-registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACzE,OAAO,EAAE,+BAA+B,EAAE,MAAM,6CAA6C,CAAC;AAC9F,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAEhF;;;;;GAKG;AACH,MAAM,OAAO,uBAAuB;IACjB,IAAI,GAAG,IAAI,GAAG,EAAqC,CAAC;IAErE,QAAQ,CAA4B,GAAiC;QACnE,oEAAoE;QACpE,wEAAwE;QACxE,2EAA2E;QAC3E,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAA2C,CAAC,CAAC;IACvE,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IACrC,CAAC;IAED,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,MAAuB,EAAE,GAAqB;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QACtE,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,CAAC;CACF;AAED,6DAA6D;AAC7D,MAAM,UAAU,6BAA6B;IAC3C,MAAM,QAAQ,GAAG,IAAI,uBAAuB,EAAE,CAAC;IAC/C,QAAQ,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IACtC,QAAQ,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IAC3C,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IACvC,QAAQ,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IACtC,QAAQ,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC;IAC1C,QAAQ,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,+BAA+B,CAAC,CAAC;IACnD,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC;IAC7C,QAAQ,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC;IAC5C,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { PromotionCodePort, ResolvedPromotionCode } from '@endora-commerce/contracts';
3
+ /**
4
+ * The coupon-resolution port `promotions` publishes (feature 075, Phase P).
5
+ *
6
+ * `carts` reaches both entity classes for exactly one question — "is this code
7
+ * live, and which promotion is it?" — across a three-step lookup: the legacy
8
+ * `promotions.code` column first, then the `promotion_coupons` table, then the
9
+ * promotion behind the coupon, each filtered on `isActive`. That order and
10
+ * those filters are this module's business, and reproducing them in `carts`
11
+ * meant reproducing them correctly.
12
+ *
13
+ * The answer is eight fields rather than the thirty-column row, because eight
14
+ * is what the question needs: which promotion, and — when the buyer may not
15
+ * use it — why not. The four eligibility fields joined the record in feature
16
+ * 075's `carts` cut; without them the caller could only answer `invalid_code`
17
+ * where it answers `expired`, `wrong_organization` and `below_min_spend`.
18
+ */
19
+ export declare class PromotionCodeService implements PromotionCodePort {
20
+ private readonly emFactory;
21
+ constructor(emFactory: () => EntityManager);
22
+ resolveByCode(code: string): Promise<ResolvedPromotionCode | null>;
23
+ }
24
+ //# sourceMappingURL=promotion-code-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promotion-code-port.d.ts","sourceRoot":"","sources":["../../../src/backend/services/promotion-code-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAI3F;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,oBAAqB,YAAW,iBAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAAT,SAAS,EAAE,MAAM,aAAa;IAErD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;CAiBzE"}