@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,194 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Entity, Index, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
11
+ import { GlobalEntity } from '@endora-commerce/platform/tenancy';
12
+ import { randomUUID } from 'crypto';
13
+ /**
14
+ * Promotion — a discount rule applied to a Cart snapshot (T129 / FR-052).
15
+ *
16
+ * Three kinds of effect:
17
+ * - `percentage_off` — `value` 0..100, applied to either the cart subtotal
18
+ * or the lines matching the optional category/product
19
+ * scope.
20
+ * - `amount_off` — `value` is money in `currency`.
21
+ * - `free_delivery` — zero out the cart's delivery cost.
22
+ *
23
+ * Eligibility filters: `code`, `minCartSubtotal`, `validFrom`/`validUntil`,
24
+ * `organizationId` / `customerGroupId`, `categoryId` / `productId`.
25
+ *
26
+ * Inactive (`isActive=false`) promotions are skipped without being deleted —
27
+ * useful for re-enabling seasonally.
28
+ */
29
+ let Promotion = class Promotion {
30
+ [OptionalProps];
31
+ id = randomUUID();
32
+ code;
33
+ name;
34
+ /** Legacy effect kind — nullable on feature-045 action-based promotions. */
35
+ kind;
36
+ /** Legacy effect value — nullable on feature-045 action-based promotions. */
37
+ value;
38
+ currency;
39
+ minCartSubtotal;
40
+ validFrom;
41
+ validUntil;
42
+ organizationId;
43
+ customerGroupId;
44
+ categoryId;
45
+ productId;
46
+ /**
47
+ * Feature 012 / US8 — line-level discriminated criteria. ANDed with
48
+ * the flat `categoryId` / `productId` scope. JSONB column with default
49
+ * `'[]'::jsonb` so existing rows behave exactly as before until the
50
+ * operator authors a criterion.
51
+ */
52
+ criteria = [];
53
+ isActive = true;
54
+ // --- Feature 045 — engine fields -----------------------------------------
55
+ description;
56
+ /** Application order across a cart's matching promotions (DESC). */
57
+ priority = 0;
58
+ /** When true, no lower-priority promotion applies once this one does. */
59
+ stopFurther = false;
60
+ /** Registered action key (null on legacy kind/value promotions). */
61
+ actionType;
62
+ /** Action parameters, validated against the registered action's schema. */
63
+ actionConfig = {};
64
+ /** Named-rule reference (mutually exclusive with `ruleDefinition`). */
65
+ ruleId;
66
+ /** Inline rule AST (mutually exclusive with `ruleId`). */
67
+ ruleDefinition;
68
+ usageLimitGlobal;
69
+ usageLimitPerOrganization;
70
+ usageLimitPerCustomer;
71
+ createdAt = new Date();
72
+ updatedAt = new Date();
73
+ };
74
+ __decorate([
75
+ PrimaryKey({ type: 'uuid' }),
76
+ __metadata("design:type", String)
77
+ ], Promotion.prototype, "id", void 0);
78
+ __decorate([
79
+ Property({ type: 'string', length: 64, nullable: true }),
80
+ Unique(),
81
+ __metadata("design:type", Object)
82
+ ], Promotion.prototype, "code", void 0);
83
+ __decorate([
84
+ Property({ type: 'string', length: 160 }),
85
+ __metadata("design:type", String)
86
+ ], Promotion.prototype, "name", void 0);
87
+ __decorate([
88
+ Property({ type: 'string', length: 24, nullable: true }),
89
+ Index(),
90
+ __metadata("design:type", Object)
91
+ ], Promotion.prototype, "kind", void 0);
92
+ __decorate([
93
+ Property({ type: 'decimal', precision: 14, scale: 4, nullable: true }),
94
+ __metadata("design:type", Object)
95
+ ], Promotion.prototype, "value", void 0);
96
+ __decorate([
97
+ Property({ type: 'string', length: 3, nullable: true }),
98
+ __metadata("design:type", Object)
99
+ ], Promotion.prototype, "currency", void 0);
100
+ __decorate([
101
+ Property({ type: 'decimal', precision: 14, scale: 2, nullable: true }),
102
+ __metadata("design:type", Object)
103
+ ], Promotion.prototype, "minCartSubtotal", void 0);
104
+ __decorate([
105
+ Property({ type: 'datetime', nullable: true }),
106
+ __metadata("design:type", Object)
107
+ ], Promotion.prototype, "validFrom", void 0);
108
+ __decorate([
109
+ Property({ type: 'datetime', nullable: true }),
110
+ __metadata("design:type", Object)
111
+ ], Promotion.prototype, "validUntil", void 0);
112
+ __decorate([
113
+ Property({ type: 'uuid', nullable: true }),
114
+ Index(),
115
+ __metadata("design:type", Object)
116
+ ], Promotion.prototype, "organizationId", void 0);
117
+ __decorate([
118
+ Property({ type: 'uuid', nullable: true }),
119
+ Index(),
120
+ __metadata("design:type", Object)
121
+ ], Promotion.prototype, "customerGroupId", void 0);
122
+ __decorate([
123
+ Property({ type: 'uuid', nullable: true }),
124
+ __metadata("design:type", Object)
125
+ ], Promotion.prototype, "categoryId", void 0);
126
+ __decorate([
127
+ Property({ type: 'uuid', nullable: true }),
128
+ __metadata("design:type", Object)
129
+ ], Promotion.prototype, "productId", void 0);
130
+ __decorate([
131
+ Property({ type: 'json', columnType: 'jsonb', default: "'[]'" }),
132
+ __metadata("design:type", Array)
133
+ ], Promotion.prototype, "criteria", void 0);
134
+ __decorate([
135
+ Property({ type: 'boolean' }),
136
+ Index(),
137
+ __metadata("design:type", Boolean)
138
+ ], Promotion.prototype, "isActive", void 0);
139
+ __decorate([
140
+ Property({ type: 'text', nullable: true }),
141
+ __metadata("design:type", Object)
142
+ ], Promotion.prototype, "description", void 0);
143
+ __decorate([
144
+ Property({ type: 'integer', default: 0 }),
145
+ Index(),
146
+ __metadata("design:type", Number)
147
+ ], Promotion.prototype, "priority", void 0);
148
+ __decorate([
149
+ Property({ type: 'boolean', default: false }),
150
+ __metadata("design:type", Boolean)
151
+ ], Promotion.prototype, "stopFurther", void 0);
152
+ __decorate([
153
+ Property({ type: 'string', length: 64, nullable: true }),
154
+ __metadata("design:type", Object)
155
+ ], Promotion.prototype, "actionType", void 0);
156
+ __decorate([
157
+ Property({ type: 'json', columnType: 'jsonb', default: "'{}'" }),
158
+ __metadata("design:type", Object)
159
+ ], Promotion.prototype, "actionConfig", void 0);
160
+ __decorate([
161
+ Property({ type: 'uuid', nullable: true }),
162
+ Index(),
163
+ __metadata("design:type", Object)
164
+ ], Promotion.prototype, "ruleId", void 0);
165
+ __decorate([
166
+ Property({ type: 'json', columnType: 'jsonb', nullable: true }),
167
+ __metadata("design:type", Object)
168
+ ], Promotion.prototype, "ruleDefinition", void 0);
169
+ __decorate([
170
+ Property({ type: 'integer', nullable: true }),
171
+ __metadata("design:type", Object)
172
+ ], Promotion.prototype, "usageLimitGlobal", void 0);
173
+ __decorate([
174
+ Property({ type: 'integer', nullable: true }),
175
+ __metadata("design:type", Object)
176
+ ], Promotion.prototype, "usageLimitPerOrganization", void 0);
177
+ __decorate([
178
+ Property({ type: 'integer', nullable: true }),
179
+ __metadata("design:type", Object)
180
+ ], Promotion.prototype, "usageLimitPerCustomer", void 0);
181
+ __decorate([
182
+ Property({ type: 'datetime', onCreate: () => new Date() }),
183
+ __metadata("design:type", Date)
184
+ ], Promotion.prototype, "createdAt", void 0);
185
+ __decorate([
186
+ Property({ type: 'datetime', onUpdate: () => new Date() }),
187
+ __metadata("design:type", Date)
188
+ ], Promotion.prototype, "updatedAt", void 0);
189
+ Promotion = __decorate([
190
+ GlobalEntity(),
191
+ Entity({ tableName: 'promotions' })
192
+ ], Promotion);
193
+ export { Promotion };
194
+ //# sourceMappingURL=promotion.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promotion.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/promotion.entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { ChannelMemberEntityType } from '@endora-commerce/contracts';
3
+ import type { CatalogProductReadPort, CatalogPromoAttributePort, DictionaryValidator } from '@endora-commerce/contracts';
4
+ import type { AuditPort, ModuleContext, OrganizationReadPort, RequireAdminFactory, SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
5
+ import { PromotionService } from './services/promotion-service.js';
6
+ import { Promotion } from './entities/promotion.entity.js';
7
+ import { PromotionRuleEntity } from './entities/promotion-rule.entity.js';
8
+ import { PromotionCoupon } from './entities/promotion-coupon.entity.js';
9
+ import { CouponBatch } from './entities/coupon-batch.entity.js';
10
+ import { PromotionUsage } from './entities/promotion-usage.entity.js';
11
+ import { PromotionUsageCounter } from './entities/promotion-usage-counter.entity.js';
12
+ import { CouponService } from './services/coupon-service.js';
13
+ import { PromotionRuleStore } from './services/promotion-rule-store.js';
14
+ import { PromotionStatsService } from './services/promotion-stats-service.js';
15
+ import type { PromotionRuleTargetPorts } from './routes.js';
16
+ /**
17
+ * `promotions` — six optional arguments, four of which were gates (feature 072,
18
+ * wave 2, T115; closed by issue #251).
19
+ *
20
+ * The one worth naming was `resolveOrganizationStatus`, documented as
21
+ * *"Defaults to 'skip' the gate when omitted (legacy composition)"*. Feature 026
22
+ * US5 exists so an org-targeted promotion only fires for an **active**
23
+ * Organization — a blocked or moderated customer should not keep receiving
24
+ * negotiated discounts. Omitting the resolver skipped that check entirely, so
25
+ * the promotion applied to every organization regardless of status.
26
+ *
27
+ * `salesChannelMembership`, `dictionaryValidator` and `auditLog` were the same
28
+ * story in quieter registers: without the first a new promotion bound to no
29
+ * channel and `applyToCart` did no channel filtering at all, without the second
30
+ * an unknown currency code was stored, without the third the write was
31
+ * unrecorded.
32
+ *
33
+ * All four are required arguments now. Both roots always passed them, so
34
+ * nothing was live — but `test/helpers/promotion-service.ts` passed none of
35
+ * them, so ten suites asserted against a service four gates quieter than the
36
+ * composed one. That is what an optional dependency production always supplies
37
+ * costs: the absent branch is untested by construction and only tests ever run
38
+ * it.
39
+ *
40
+ * **One bundle stays in the root**, for the reason `megamenu`'s did.
41
+ * `ruleTargets` reads `organizations`, `categories`, `payment_methods` and
42
+ * `delivery_methods` tables directly; it belongs to modules that have not
43
+ * converted, and pulling it in would give this module reads of storage it does
44
+ * not own.
45
+ *
46
+ * `catalogQueryPort` used to be the second, and this comment used to say the
47
+ * honest end state was for the two methods this module reached on
48
+ * `CatalogQueryService` to move behind a narrower port. Feature 075's Phase P
49
+ * did exactly that: `catalogPromoAttributePort` is those two questions, and
50
+ * `catalogProductReadPort` is the per-line attribute hydration that used to be
51
+ * an `em.find(Product, …)`. Both are resolved by contract type here.
52
+ */
53
+ export interface PromotionsCradle {
54
+ /**
55
+ * Feature 120 (FR-015) — the platform's channel-bridge registry, resolved by
56
+ * **container name** rather than imported. The shape is the narrow one this
57
+ * module needs, which is what `ctx.cradle<C>()` asks an author to declare;
58
+ * the registry itself is the kernel's and is published on no barrel.
59
+ */
60
+ readonly salesChannelBridgeRegistry: {
61
+ register(bridge: {
62
+ entityType: ChannelMemberEntityType;
63
+ table: string;
64
+ entityIdColumn: string;
65
+ }): void;
66
+ };
67
+ readonly emFactory: () => EntityManager;
68
+ readonly auditLogService: AuditPort;
69
+ readonly requireAdmin: RequireAdminFactory;
70
+ readonly salesChannelMembershipPort: SalesChannelMembershipPort;
71
+ readonly dictionaryValidator: DictionaryValidator;
72
+ /** Owned by `catalog`: the two promo-attribute questions the Rule Builder asks. */
73
+ readonly catalogPromoAttributePort: CatalogPromoAttributePort;
74
+ /** Owned by `catalog`: the product rows a cart line's attribute values come from. */
75
+ readonly catalogProductReadPort: CatalogProductReadPort;
76
+ /**
77
+ * The tenancy read port — the gate that keeps a suspended org out (T138).
78
+ * Was `organizationStatusResolver`, a raw `select "status" from
79
+ * "organizations"` each root spelled by hand against another module's table.
80
+ */
81
+ readonly organizationReadPort: OrganizationReadPort;
82
+ /** Rule Builder picker sources; root-shaped, reads four other modules' tables. */
83
+ readonly promotionRuleTargets: PromotionRuleTargetPorts;
84
+ readonly promotionService: PromotionService;
85
+ readonly promotionCouponService: CouponService;
86
+ readonly promotionRuleStore: PromotionRuleStore;
87
+ readonly promotionStatsService: PromotionStatsService;
88
+ }
89
+ export declare function registerModule(ctx: ModuleContext): void;
90
+ /**
91
+ * The module's persisted entity classes, on the `./backend` subpath, as one
92
+ * array and **no named class export** (D-168).
93
+ *
94
+ * This is the shape the platform reads when the package is *installed*: the
95
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
96
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
97
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
98
+ * the package pass of `check-entity-tenant-classification`. A missing array is
99
+ * answered with `[]` — zero entities registered, no error anywhere.
100
+ *
101
+ * Six classes, and `promotion_usages` is the one that had to be shown to keep a
102
+ * foreign key without a named export (D-169): `promotion_usages_order_fk` runs
103
+ * into `orders.id`, and a constraint is between two column names — it needs the
104
+ * **table**, never the class. Nothing another module can legitimately do with
105
+ * `PromotionUsage` is lost. D-32 forbids an ORM relation into it; what `orders`
106
+ * actually needs is the *method* placement calls, which leaves this package as
107
+ * `PromotionUsageFinalizer` on the type-only `./ports` subpath.
108
+ */
109
+ export declare const entities: (typeof Promotion | typeof PromotionRuleEntity | typeof PromotionCoupon | typeof CouponBatch | typeof PromotionUsage | typeof PromotionUsageCounter)[];
110
+ /**
111
+ * The sales-channel membership bridge this module owns (feature 120, FR-015).
112
+ *
113
+ * Exported as well as registered because the owning module is the only honest
114
+ * source for it: a caller that composes no platform — the bare-database test
115
+ * harness, which constructs the kernel's membership service directly — declares
116
+ * these from here rather than from a list of nine it keeps itself, which is
117
+ * exactly the total map this feature deleted.
118
+ */
119
+ export declare const salesChannelBridges: ReadonlyArray<{
120
+ readonly entityType: ChannelMemberEntityType;
121
+ readonly table: string;
122
+ readonly entityIdColumn: string;
123
+ }>;
124
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAGzB,mBAAmB,EAEpB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,0BAA0B,EAC3B,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AAErF,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAG5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,0BAA0B,EAAE;QACnC,QAAQ,CAAC,MAAM,EAAE;YACf,UAAU,EAAE,uBAAuB,CAAC;YACpC,KAAK,EAAE,MAAM,CAAC;YACd,cAAc,EAAE,MAAM,CAAC;SACxB,GAAG,IAAI,CAAC;KACV,CAAC;IACF,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,0BAA0B,EAAE,0BAA0B,CAAC;IAChE,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;IAClD,mFAAmF;IACnF,QAAQ,CAAC,yBAAyB,EAAE,yBAAyB,CAAC;IAC9D,qFAAqF;IACrF,QAAQ,CAAC,sBAAsB,EAAE,sBAAsB,CAAC;IACxD;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,EAAE,oBAAoB,CAAC;IACpD,kFAAkF;IAClF,QAAQ,CAAC,oBAAoB,EAAE,wBAAwB,CAAC;IACxD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,sBAAsB,EAAE,aAAa,CAAC;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAChD,QAAQ,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;CACvD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CA+KvD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,QAAQ,wJAOpB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,uBAAuB,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC,CAEA,CAAC"}
@@ -0,0 +1,182 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { PromotionService } from './services/promotion-service.js';
3
+ import { Promotion } from './entities/promotion.entity.js';
4
+ import { PromotionRuleEntity } from './entities/promotion-rule.entity.js';
5
+ import { PromotionCoupon } from './entities/promotion-coupon.entity.js';
6
+ import { CouponBatch } from './entities/coupon-batch.entity.js';
7
+ import { PromotionUsage } from './entities/promotion-usage.entity.js';
8
+ import { PromotionUsageCounter } from './entities/promotion-usage-counter.entity.js';
9
+ import { PromotionCodeService } from './services/promotion-code-port.js';
10
+ import { CouponService } from './services/coupon-service.js';
11
+ import { PromotionRuleStore } from './services/promotion-rule-store.js';
12
+ import { PromotionStatsService } from './services/promotion-stats-service.js';
13
+ import { registerPromotionRoutes } from './routes.js';
14
+ import { registerPromotionCurrencyReferences } from './services/promotion-currency-reference.js';
15
+ export function registerModule(ctx) {
16
+ /**
17
+ * Feature 120 (FR-015) — the sales-channel membership bridge this module owns.
18
+ *
19
+ * The platform used to hold a map **total** over
20
+ * `ChannelMemberEntityTypeSchema`, naming this module's bridge table from
21
+ * inside the kernel. That is D-52/D-53's prohibition expressed in a contract
22
+ * instead of an import, and it meant a membership call for 'promotion' on an
23
+ * instance that never installed this module executed SQL against a relation
24
+ * that is not there (D-226). The module whose migration creates the table is
25
+ * the one that says where it is; a member nothing registers now refuses at
26
+ * the call instead.
27
+ *
28
+ * A **contribution** hook and deliberately **unprobed** (D-62/D-68): a
29
+ * registration is an inert statement about the schema rather than a
30
+ * capability, the rows outlive an operator switching this module off, and
31
+ * probing would make a runtime activation flip require a restart. It is
32
+ * declared first in this module so any later hook of its own that binds a
33
+ * membership finds the bridge already there.
34
+ */
35
+ ctx.onBoot(() => {
36
+ const { salesChannelBridgeRegistry } = ctx.cradle();
37
+ for (const bridge of salesChannelBridges)
38
+ salesChannelBridgeRegistry.register(bridge);
39
+ });
40
+ ctx.di.register({
41
+ // Contribution point: a composition with no picker sources gets empty
42
+ // pickers, not a broken Rule Builder.
43
+ promotionRuleTargets: ctx
44
+ .asFunction(() => ({}))
45
+ .singleton(),
46
+ promotionCouponService: ctx
47
+ .asFunction(({ emFactory, auditLogService }) => new CouponService(emFactory, auditLogService))
48
+ .singleton(),
49
+ promotionRuleStore: ctx
50
+ .asFunction(({ emFactory, auditLogService }) => new PromotionRuleStore(emFactory, auditLogService))
51
+ .singleton(),
52
+ promotionStatsService: ctx
53
+ .asFunction(({ emFactory }) => new PromotionStatsService(emFactory))
54
+ .singleton(),
55
+ });
56
+ /**
57
+ * Feature 075, Phase P — the coupon-resolution port.
58
+ *
59
+ * `carts` reaches both promotion entities for one question: is this code
60
+ * live, and which promotion is it? The three-step lookup behind it — the
61
+ * legacy inline code, then the coupon table, then the promotion behind the
62
+ * coupon, each filtered on `isActive` — is this module's, and reproducing it
63
+ * in `carts` meant reproducing it correctly.
64
+ */
65
+ ctx.di.providePort('promotionCodePort', ctx
66
+ .asFunction(({ emFactory }) => new PromotionCodeService(emFactory))
67
+ .singleton());
68
+ /**
69
+ * D-94.5 — the co-transactional half of the promotion seam, as its own
70
+ * gated port.
71
+ *
72
+ * `orders` used to reach `finalizeUsage` through `promotionService`, typed
73
+ * by an interface `orders` wrote itself: `lazyPort<T>` is an unchecked cast,
74
+ * so with `T` on the consumer's side nothing verified that this module still
75
+ * satisfied it. The interface is `PromotionUsageFinalizer` and it is this
76
+ * module's to declare — `../ports/index.ts`, published on the type-only
77
+ * `./ports` subpath (R8), because the signature carries the caller's
78
+ * `EntityManager` and FR-034 keeps a MikroORM type out of
79
+ * `@endora-commerce/contracts`.
80
+ *
81
+ * It was declared beside the implementation until this module became a
82
+ * package, and `orders` reached it by a relative path — a `permanent: true`
83
+ * cross-module ledger entry naming `promotion_usages_order_fk`. D-171 retires
84
+ * that entry rather than the seam: a subpath the owner declared is contract
85
+ * surface, so the import is a package dependency and not a reach into
86
+ * internals. The foreign key, the transaction and the `EntityManager`
87
+ * parameter are all exactly where they were.
88
+ */
89
+ ctx.di.providePort('promotionUsageFinalizer', ctx
90
+ .asFunction(() => {
91
+ // Lazily, even though this module owns the name: `providePort` gates
92
+ // on the effective state, and a singleton that captured its own gate
93
+ // would answer after an operator switched this module off.
94
+ const service = lazyPort(ctx, 'promotionService');
95
+ return {
96
+ finalizeUsage: (em, input) => service.finalizeUsage(em, input),
97
+ };
98
+ })
99
+ .singleton());
100
+ ctx.di.providePort('promotionService', ctx
101
+ .asFunction(({ emFactory, auditLogService }) => new PromotionService(emFactory,
102
+ // Every argument up to and including `auditLog` is required
103
+ // (issues #164 and #251): each is resolved for every composition,
104
+ // so the type no longer says otherwise. `auditLogger` and
105
+ // `actionRegistry` follow, and are the only two with a default —
106
+ // both are genuine defaults (a console sink, the built-in action
107
+ // catalogue), not gates an omission opens.
108
+ lazyPort(ctx, 'catalogPromoAttributePort'), lazyPort(ctx, 'catalogProductReadPort'), lazyPort(ctx, 'salesChannelMembershipPort'), lazyPort(ctx, 'dictionaryValidator'), async (orgId) => (await ctx
109
+ .cradle()
110
+ .organizationReadPort.loadEffectiveOrganization(orgId))?.status ?? null, auditLogService))
111
+ .singleton());
112
+ ctx.routes(async (app) => {
113
+ const { promotionCouponService, promotionRuleStore, promotionStatsService, requireAdmin, promotionRuleTargets, } = ctx.cradle();
114
+ await registerPromotionRoutes(app, {
115
+ // Lazily, even though the module owns this port: route *registration* runs
116
+ // inside `buildServer` whatever the module's effective state is, so
117
+ // destructuring the gate here would stop the next start instead of
118
+ // stopping the routes (D-40).
119
+ promotionService: lazyPort(ctx, 'promotionService'),
120
+ couponService: promotionCouponService,
121
+ ruleStore: promotionRuleStore,
122
+ statsService: promotionStatsService,
123
+ requireAdmin,
124
+ catalogPromoAttributes: lazyPort(ctx, 'catalogPromoAttributePort'),
125
+ ruleTargets: promotionRuleTargets,
126
+ });
127
+ });
128
+ /**
129
+ * This module's rows carry a currency code, so it answers "who still points at
130
+ * this currency?" about its own tables (feature 077, D-87), where the owner used
131
+ * to count them with SQL naming this module's tables.
132
+ *
133
+ * A **contribution** hook: it pushes an inert descriptor into `currencyReferenceRegistry`,
134
+ * an ungated registry, and carries no presence probe (D-62/D-68). Probing
135
+ * would be wrong in the dangerous direction — a switched-off module still owns
136
+ * the rows, so its currency must still refuse the delete, which is the
137
+ * enumeration policy the registry states.
138
+ */
139
+ ctx.onBoot(() => {
140
+ registerPromotionCurrencyReferences(lazyPort(ctx, 'currencyReferenceRegistry'), ctx.cradle().emFactory);
141
+ });
142
+ }
143
+ /**
144
+ * The module's persisted entity classes, on the `./backend` subpath, as one
145
+ * array and **no named class export** (D-168).
146
+ *
147
+ * This is the shape the platform reads when the package is *installed*: the
148
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
149
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
150
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
151
+ * the package pass of `check-entity-tenant-classification`. A missing array is
152
+ * answered with `[]` — zero entities registered, no error anywhere.
153
+ *
154
+ * Six classes, and `promotion_usages` is the one that had to be shown to keep a
155
+ * foreign key without a named export (D-169): `promotion_usages_order_fk` runs
156
+ * into `orders.id`, and a constraint is between two column names — it needs the
157
+ * **table**, never the class. Nothing another module can legitimately do with
158
+ * `PromotionUsage` is lost. D-32 forbids an ORM relation into it; what `orders`
159
+ * actually needs is the *method* placement calls, which leaves this package as
160
+ * `PromotionUsageFinalizer` on the type-only `./ports` subpath.
161
+ */
162
+ export const entities = [
163
+ Promotion,
164
+ PromotionRuleEntity,
165
+ PromotionCoupon,
166
+ CouponBatch,
167
+ PromotionUsage,
168
+ PromotionUsageCounter,
169
+ ];
170
+ /**
171
+ * The sales-channel membership bridge this module owns (feature 120, FR-015).
172
+ *
173
+ * Exported as well as registered because the owning module is the only honest
174
+ * source for it: a caller that composes no platform — the bare-database test
175
+ * harness, which constructs the kernel's membership service directly — declares
176
+ * these from here rather than from a list of nine it keeps itself, which is
177
+ * exactly the total map this feature deleted.
178
+ */
179
+ export const salesChannelBridges = [
180
+ { entityType: 'promotion', table: 'sales_channel_promotions', entityIdColumn: 'promotion_id' },
181
+ ];
182
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AACtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,mCAAmC,EAAE,MAAM,4CAA4C,CAAC;AA6EjG,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C;;;;;;;;;;;;;;;;;;OAkBG;IACH,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE;QACd,MAAM,EAAE,0BAA0B,EAAE,GAAG,GAAG,CAAC,MAAM,EAAoB,CAAC;QACtE,KAAK,MAAM,MAAM,IAAI,mBAAmB;YAAE,0BAA0B,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC;QACd,sEAAsE;QACtE,sCAAsC;QACtC,oBAAoB,EAAE,GAAG;aACtB,UAAU,CAAC,GAA6B,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;aAChD,SAAS,EAAE;QAEd,sBAAsB,EAAE,GAAG;aACxB,UAAU,CACT,CAAC,EAAE,SAAS,EAAE,eAAe,EAAoB,EAAE,EAAE,CACnD,IAAI,aAAa,CAAC,SAAS,EAAE,eAAe,CAAC,CAChD;aACA,SAAS,EAAE;QAEd,kBAAkB,EAAE,GAAG;aACpB,UAAU,CACT,CAAC,EAAE,SAAS,EAAE,eAAe,EAAoB,EAAE,EAAE,CACnD,IAAI,kBAAkB,CAAC,SAAS,EAAE,eAAe,CAAC,CACrD;aACA,SAAS,EAAE;QAEd,qBAAqB,EAAE,GAAG;aACvB,UAAU,CAAC,CAAC,EAAE,SAAS,EAAoB,EAAE,EAAE,CAAC,IAAI,qBAAqB,CAAC,SAAS,CAAC,CAAC;aACrF,SAAS,EAAE;KACf,CAAC,CAAC;IAEH;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,mBAAmB,EACnB,GAAG;SACA,UAAU,CAAC,CAAC,EAAE,SAAS,EAAoB,EAAE,EAAE,CAAC,IAAI,oBAAoB,CAAC,SAAS,CAAC,CAAC;SACpF,SAAS,EAAE,CACf,CAAC;IAEF;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,yBAAyB,EACzB,GAAG;SACA,UAAU,CAAC,GAA4B,EAAE;QACxC,qEAAqE;QACrE,qEAAqE;QACrE,2DAA2D;QAC3D,MAAM,OAAO,GAAG,QAAQ,CAAmB,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACpE,OAAO;YACL,aAAa,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC;SAC/D,CAAC;IACJ,CAAC,CAAC;SACD,SAAS,EAAE,CACf,CAAC;IAEF,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,kBAAkB,EAClB,GAAG;SACA,UAAU,CACT,CAAC,EAAE,SAAS,EAAE,eAAe,EAAoB,EAAE,EAAE,CACnD,IAAI,gBAAgB,CAClB,SAAS;IACT,4DAA4D;IAC5D,kEAAkE;IAClE,0DAA0D;IAC1D,iEAAiE;IACjE,iEAAiE;IACjE,2CAA2C;IAC3C,QAAQ,CAA4B,GAAG,EAAE,2BAA2B,CAAC,EACrE,QAAQ,CAAyB,GAAG,EAAE,wBAAwB,CAAC,EAC/D,QAAQ,CAA6B,GAAG,EAAE,4BAA4B,CAAC,EACvE,QAAQ,CAAsB,GAAG,EAAE,qBAAqB,CAAC,EACzD,KAAK,EAAE,KAAa,EAAE,EAAE,CACtB,CACE,MAAM,GAAG;SACN,MAAM,EAAoB;SAC1B,oBAAoB,CAAC,yBAAyB,CAAC,KAAK,CAAC,CACzD,EAAE,MAAM,IAAI,IAAI,EACnB,eAAe,CAChB,CACJ;SACA,SAAS,EAAE,CACf,CAAC;IAEF,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,EACJ,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,EACrB,YAAY,EACZ,oBAAoB,GACrB,GAAG,GAAG,CAAC,MAAM,EAAoB,CAAC;QACnC,MAAM,uBAAuB,CAAC,GAAG,EAAE;YACjC,2EAA2E;YAC3E,oEAAoE;YACpE,mEAAmE;YACnE,8BAA8B;YAC9B,gBAAgB,EAAE,QAAQ,CAAmB,GAAG,EAAE,kBAAkB,CAAC;YACrE,aAAa,EAAE,sBAAsB;YACrC,SAAS,EAAE,kBAAkB;YAC7B,YAAY,EAAE,qBAAqB;YACnC,YAAY;YACZ,sBAAsB,EAAE,QAAQ,CAC9B,GAAG,EACH,2BAA2B,CAC5B;YACD,WAAW,EAAE,oBAAoB;SAClC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH;;;;;;;;;;OAUG;IACH,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE;QACd,mCAAmC,CACjC,QAAQ,CAAkC,GAAG,EAAE,2BAA2B,CAAC,EAC3E,GAAG,CAAC,MAAM,EAAoB,CAAC,SAAS,CACzC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,SAAS;IACT,mBAAmB;IACnB,eAAe;IACf,WAAW;IACX,cAAc;IACd,qBAAqB;CACtB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAI3B;IACH,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,0BAA0B,EAAE,cAAc,EAAE,cAAc,EAAE;CAC/F,CAAC"}
@@ -0,0 +1,88 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { PromotionService } from './services/promotion-service.js';
3
+ import type { CouponService } from './services/coupon-service.js';
4
+ import type { PromotionRuleStore } from './services/promotion-rule-store.js';
5
+ import type { PromotionStatsService } from './services/promotion-stats-service.js';
6
+ /**
7
+ * List ports feeding the Rule Builder pickers (feature 045, US1/T033).
8
+ *
9
+ * Declared here rather than in a module factory since feature 072 (T115): the
10
+ * factory is gone, and these are the routes' own option shape. Every member is
11
+ * optional because a composition may know how to list some target kinds and not
12
+ * others — an absent one yields an empty picker rather than a broken screen.
13
+ */
14
+ export interface PromotionRuleTargetPorts {
15
+ salesChannels?: () => Promise<Array<{
16
+ id: string;
17
+ code: string;
18
+ name: string;
19
+ }>>;
20
+ customerGroups?: () => Promise<Array<{
21
+ id: string;
22
+ code: string;
23
+ name: string;
24
+ }>>;
25
+ organizations?: () => Promise<Array<{
26
+ id: string;
27
+ name: string;
28
+ taxId: string | null;
29
+ }>>;
30
+ categories?: () => Promise<Array<{
31
+ id: string;
32
+ slug: string;
33
+ name: string;
34
+ parentCategoryId: string | null;
35
+ }>>;
36
+ paymentMethods?: () => Promise<Array<{
37
+ id: string;
38
+ code: string;
39
+ name: string;
40
+ }>>;
41
+ deliveryMethods?: () => Promise<Array<{
42
+ id: string;
43
+ code: string;
44
+ name: string;
45
+ }>>;
46
+ }
47
+ import type { CatalogPromoAttributePort } from '@endora-commerce/contracts';
48
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
49
+ export interface PromotionRoutesDeps {
50
+ promotionService: PromotionService;
51
+ couponService: CouponService;
52
+ ruleStore: PromotionRuleStore;
53
+ statsService: PromotionStatsService;
54
+ requireAdmin: RequireAdminFactory;
55
+ /**
56
+ * Feature 012 / US8 — feeds the rule-target picker endpoint. Since feature
57
+ * 075's Phase C this is `catalogPromoAttributePort`, the two questions this
58
+ * picker asks, rather than `catalog`'s 1400-line storefront query service.
59
+ *
60
+ * **Required** (issue #164), for the reason the two ports on
61
+ * `PromotionService` are. It was optional and the one call site there is —
62
+ * `backend.ts`, through `lazyPort('catalogPromoAttributePort')` — always
63
+ * supplied it, so the absent branch had never run; what it would have run
64
+ * was `503 { code: 'service_unavailable' }`, a code written nowhere else in
65
+ * the repository, in no `ERROR_CODES`, with no translation and no admin
66
+ * handler. That made it a second spelling of one absence, and the platform
67
+ * already spells that absence once: `lazyPort` resolves inside the forwarded
68
+ * call, so an owner that is not there throws `ModuleDisabledError` and the
69
+ * envelope answers 503 `MODULE_DISABLED` naming the module. Here even that
70
+ * cannot happen — `catalog` declares `activation.nonDeactivatable`, so the
71
+ * gate on this port has no closed state — which leaves exactly one absence
72
+ * this route can see, and it is the one `getAttributeWithOptions` already
73
+ * says in its return type: `null`, no such attribute.
74
+ */
75
+ catalogPromoAttributes: CatalogPromoAttributePort;
76
+ /**
77
+ * Feature 045 (T033) — list ports for the remaining rule-target pickers.
78
+ *
79
+ * The bundle is required and its **members** are not: the container
80
+ * registers `promotionRuleTargets` with a `{}` default that a composition
81
+ * root contributes over, so the routes always receive one, while which
82
+ * picker kinds it knows how to list stays the composition's business
83
+ * (issue #164 again — the outer `?.` guarded nothing).
84
+ */
85
+ ruleTargets: PromotionRuleTargetPorts;
86
+ }
87
+ export declare function registerPromotionRoutes(app: FastifyInstance, deps: PromotionRoutesDeps): Promise<void>;
88
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAS/C,OAAO,KAAK,EAAE,gBAAgB,EAAwB,MAAM,iCAAiC,CAAC;AAC9F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,KAAK,EAAE,qBAAqB,EAAc,MAAM,uCAAuC,CAAC;AAE/F;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACjF,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAClF,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC,CAAC;IACzF,UAAU,CAAC,EAAE,MAAM,OAAO,CACxB,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CACnF,CAAC;IACF,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAClF,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACpF;AAMD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAC5E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,kBAAkB,CAAC;IAC9B,YAAY,EAAE,qBAAqB,CAAC;IACpC,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,sBAAsB,EAAE,yBAAyB,CAAC;IAClD;;;;;;;;OAQG;IACH,WAAW,EAAE,wBAAwB,CAAC;CACvC;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,IAAI,CAAC,CAwQf"}