@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,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from 'react';
3
+ import { useNavigate } from 'react-router-dom';
4
+ import { BarChart3, Pencil, Plus, Trash2 } from 'lucide-react';
5
+ import { ApiError, apiClient, formatDateTime, useAuth } from '@endora-commerce/admin-kit/lib';
6
+ import { Alert, AlertDescription, Badge, Button, Card, CardContent, PageHeader, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@endora-commerce/admin-kit/ui';
7
+ import { useTranslation } from '@endora-commerce/admin-kit/i18n';
8
+ /** Human-readable summary of a promotion's effect (legacy kind or action). */
9
+ function describeEffect(p) {
10
+ if (p.action) {
11
+ const a = p.action;
12
+ if (a.type === 'percentage_off_cart')
13
+ return `${a.percent}% off cart`;
14
+ if (a.type === 'amount_off_cart')
15
+ return `${a.amount} ${a.currency} off cart`;
16
+ if (a.type === 'free_delivery')
17
+ return 'Free delivery';
18
+ return a.type;
19
+ }
20
+ if (p.kind === 'percentage_off')
21
+ return `${p.value}%`;
22
+ if (p.kind === 'amount_off')
23
+ return `${p.value} ${p.currency ?? ''}`.trim();
24
+ if (p.kind === 'free_delivery')
25
+ return 'Free delivery';
26
+ return '—';
27
+ }
28
+ export const PromotionsPage = () => {
29
+ const t = useTranslation('core');
30
+ const navigate = useNavigate();
31
+ const { hasPermission } = useAuth();
32
+ const canWrite = hasPermission('promotions:write');
33
+ const canDelete = hasPermission('promotions:delete');
34
+ const [rows, setRows] = useState([]);
35
+ const [loading, setLoading] = useState(true);
36
+ const [error, setError] = useState(null);
37
+ const [info, setInfo] = useState(null);
38
+ const refresh = useCallback(async () => {
39
+ setLoading(true);
40
+ setError(null);
41
+ try {
42
+ const res = await apiClient.get('/api/v1/admin/promotions');
43
+ setRows(res.data);
44
+ }
45
+ catch (err) {
46
+ setError(err instanceof ApiError ? err.envelope.error.message : t('promotions.error.load'));
47
+ }
48
+ finally {
49
+ setLoading(false);
50
+ }
51
+ }, [t]);
52
+ useEffect(() => {
53
+ void refresh();
54
+ }, [refresh]);
55
+ const handleDelete = useCallback(async (id) => {
56
+ if (!confirm(t('promotions.deleteConfirm')))
57
+ return;
58
+ try {
59
+ await apiClient.delete(`/api/v1/admin/promotions/${id}`);
60
+ setInfo(null);
61
+ await refresh();
62
+ }
63
+ catch (err) {
64
+ setError(err instanceof ApiError ? err.envelope.error.message : t('promotions.error.delete'));
65
+ }
66
+ }, [refresh, t]);
67
+ return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { title: t('promotions.page.title'), description: t('promotions.page.description') }), canWrite ? (_jsx("div", { className: "mb-4", children: _jsxs(Button, { type: "button", onClick: () => navigate('/promotions/new'), children: [_jsx(Plus, {}), " ", t('promotions.edit.titleNew')] }) })) : null, error ? (_jsx(Alert, { variant: "destructive", className: "mb-4", children: _jsx(AlertDescription, { children: error }) })) : null, info ? (_jsx(Alert, { variant: "success", className: "mb-4", children: _jsx(AlertDescription, { children: info }) })) : null, _jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('promotions.loading') })) : rows.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('promotions.empty') })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t('promotions.column.code') }), _jsx(TableHead, { children: t('promotions.column.name') }), _jsx(TableHead, { children: t('promotions.column.kind') }), _jsx(TableHead, { children: t('promotions.column.value') }), _jsx(TableHead, { children: t('promotions.column.minCart') }), _jsx(TableHead, { children: t('promotions.column.valid') }), _jsx(TableHead, { children: t('promotions.column.criteria') }), _jsx(TableHead, { children: t('promotions.column.active') }), _jsx(TableHead, {})] }) }), _jsx(TableBody, { children: rows.map((p) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("code", { className: "font-mono text-xs", children: p.code ?? '—' }) }), _jsx(TableCell, { className: "font-medium", children: p.name }), _jsx(TableCell, { children: p.action ? p.action.type : (p.kind ?? '—') }), _jsx(TableCell, { className: "tabular-nums", children: describeEffect(p) }), _jsx(TableCell, { className: "tabular-nums", children: p.minCartSubtotal != null ? p.minCartSubtotal.toFixed(2) : '—' }), _jsxs(TableCell, { className: "text-xs text-muted-foreground", children: [p.validFrom ? formatDateTime(p.validFrom) : '—', ' → ', p.validUntil ? formatDateTime(p.validUntil) : '—'] }), _jsx(TableCell, { className: "text-xs", children: p.criteria && p.criteria.length > 0
68
+ ? p.criteria
69
+ .map((c) => `${c.attributeKey} ${c.op} [${c.values.map((v) => String(v)).join(', ')}]`)
70
+ .join('; ')
71
+ : '—' }), _jsx(TableCell, { children: _jsx(Badge, { variant: p.isActive ? 'success' : 'secondary', children: p.isActive ? t('promotions.active.yes') : t('promotions.active.no') }) }), _jsxs(TableCell, { children: [canWrite ? (_jsxs(Button, { variant: "outline", size: "sm", type: "button", className: "mr-2", onClick: () => navigate(`/promotions/${p.id}`), children: [_jsx(Pencil, {}), t('promotions.edit.titleEdit')] })) : null, _jsxs(Button, { variant: "outline", size: "sm", type: "button", className: "mr-2", onClick: () => navigate(`/promotions/${p.id}/stats`), children: [_jsx(BarChart3, {}), t('promotionStats.title')] }), canDelete ? (_jsxs(Button, { variant: "destructive", size: "sm", type: "button", onClick: () => void handleDelete(p.id), children: [_jsx(Trash2, {}), t('promotions.action.delete')] })) : null] })] }, p.id))) })] })) }) })] }));
72
+ };
73
+ /**
74
+ * The default export the route declaration's dynamic-import factory takes
75
+ * (`contracts/admin-contribution.md` R6). The named export is kept because the
76
+ * screen is also the subject of this module's own admin tests.
77
+ */
78
+ export default PromotionsPage;
79
+ //# sourceMappingURL=PromotionsPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PromotionsPage.js","sourceRoot":"","sources":["../../../src/admin/pages/PromotionsPage.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type ActionConfigFor, type PromotionActionDefinition } from './types.js';
2
+ /** Y (amount) off the whole cart subtotal. Skips on currency mismatch. */
3
+ export declare const amountOffCartAction: PromotionActionDefinition<ActionConfigFor<'amount_off_cart'>>;
4
+ //# sourceMappingURL=amount-off-cart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amount-off-cart.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/amount-off-cart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,eAAe,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG/F,0EAA0E;AAC1E,eAAO,MAAM,mBAAmB,EAAE,yBAAyB,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAU7F,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { NO_DISCOUNT } from './types.js';
2
+ import { round2 } from './util.js';
3
+ /** Y (amount) off the whole cart subtotal. Skips on currency mismatch. */
4
+ export const amountOffCartAction = {
5
+ type: 'amount_off_cart',
6
+ labelKey: 'action.amount_off_cart',
7
+ apply: (config, ctx) => {
8
+ if (config.currency !== ctx.currency)
9
+ return NO_DISCOUNT;
10
+ return {
11
+ discountSubtotalDelta: round2(Math.min(config.amount, ctx.subtotal)),
12
+ discountDeliveryDelta: 0,
13
+ };
14
+ },
15
+ };
16
+ //# sourceMappingURL=amount-off-cart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amount-off-cart.js","sourceRoot":"","sources":["../../../src/backend/actions/amount-off-cart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAwD,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,mBAAmB,GAAkE;IAChG,IAAI,EAAE,iBAAiB;IACvB,QAAQ,EAAE,wBAAwB;IAClC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACrB,IAAI,MAAM,CAAC,QAAQ,KAAK,GAAG,CAAC,QAAQ;YAAE,OAAO,WAAW,CAAC;QACzD,OAAO;YACL,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;YACpE,qBAAqB,EAAE,CAAC;SACzB,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type ActionConfigFor, type PromotionActionDefinition } from './types.js';
2
+ /**
3
+ * Buy X get Y free across the whole cart. For every (X + Y) units the cart
4
+ * holds, Y units are free; the discounted units are the cheapest or the most
5
+ * expensive depending on `target`. Ties are resolved by the stable sort.
6
+ */
7
+ export declare const buyXGetYFreeAction: PromotionActionDefinition<ActionConfigFor<'buy_x_get_y_free'>>;
8
+ //# sourceMappingURL=buy-x-get-y-free.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buy-x-get-y-free.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/buy-x-get-y-free.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAGlF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,yBAAyB,CAAC,eAAe,CAAC,kBAAkB,CAAC,CAa7F,CAAC"}
@@ -0,0 +1,20 @@
1
+ import {} from './types.js';
2
+ import { expandUnits, round2 } from './util.js';
3
+ /**
4
+ * Buy X get Y free across the whole cart. For every (X + Y) units the cart
5
+ * holds, Y units are free; the discounted units are the cheapest or the most
6
+ * expensive depending on `target`. Ties are resolved by the stable sort.
7
+ */
8
+ export const buyXGetYFreeAction = {
9
+ type: 'buy_x_get_y_free',
10
+ labelKey: 'action.buy_x_get_y_free',
11
+ apply: (config, ctx) => {
12
+ const units = expandUnits(ctx.lines).sort((a, b) => config.target === 'cheapest' ? a.price - b.price : b.price - a.price);
13
+ const groupSize = config.buyQuantity + config.freeQuantity;
14
+ const groups = Math.floor(units.length / groupSize);
15
+ const freeCount = groups * config.freeQuantity;
16
+ const delta = round2(units.slice(0, freeCount).reduce((acc, u) => acc + u.price, 0));
17
+ return { discountSubtotalDelta: Math.min(delta, ctx.subtotal), discountDeliveryDelta: 0 };
18
+ },
19
+ };
20
+ //# sourceMappingURL=buy-x-get-y-free.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buy-x-get-y-free.js","sourceRoot":"","sources":["../../../src/backend/actions/buy-x-get-y-free.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAmE;IAChG,IAAI,EAAE,kBAAkB;IACxB,QAAQ,EAAE,yBAAyB;IACnC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACrB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACjD,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CACrE,CAAC;QACF,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC;QAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC;QAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACrF,OAAO,EAAE,qBAAqB,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC;IAC5F,CAAC;CACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type ActionConfigFor, type PromotionActionDefinition } from './types.js';
2
+ /** Buy X units of a specific product → Y (amount) off the whole cart. */
3
+ export declare const buyXUnitsAmountOffAction: PromotionActionDefinition<ActionConfigFor<'buy_x_units_amount_off'>>;
4
+ //# sourceMappingURL=buy-x-units-amount-off.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buy-x-units-amount-off.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/buy-x-units-amount-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,eAAe,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG/F,yEAAyE;AACzE,eAAO,MAAM,wBAAwB,EAAE,yBAAyB,CAC9D,eAAe,CAAC,wBAAwB,CAAC,CAa1C,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { NO_DISCOUNT } from './types.js';
2
+ import { expandUnits, round2 } from './util.js';
3
+ /** Buy X units of a specific product → Y (amount) off the whole cart. */
4
+ export const buyXUnitsAmountOffAction = {
5
+ type: 'buy_x_units_amount_off',
6
+ labelKey: 'action.buy_x_units_amount_off',
7
+ apply: (config, ctx) => {
8
+ if (config.currency !== ctx.currency)
9
+ return NO_DISCOUNT;
10
+ const count = expandUnits(ctx.lines).filter((u) => u.productId === config.productId).length;
11
+ if (count < config.buyUnits)
12
+ return NO_DISCOUNT;
13
+ return {
14
+ discountSubtotalDelta: round2(Math.min(config.amount, ctx.subtotal)),
15
+ discountDeliveryDelta: 0,
16
+ };
17
+ },
18
+ };
19
+ //# sourceMappingURL=buy-x-units-amount-off.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buy-x-units-amount-off.js","sourceRoot":"","sources":["../../../src/backend/actions/buy-x-units-amount-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAwD,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEhD,yEAAyE;AACzE,MAAM,CAAC,MAAM,wBAAwB,GAEjC;IACF,IAAI,EAAE,wBAAwB;IAC9B,QAAQ,EAAE,+BAA+B;IACzC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACrB,IAAI,MAAM,CAAC,QAAQ,KAAK,GAAG,CAAC,QAAQ;YAAE,OAAO,WAAW,CAAC;QACzD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QAC5F,IAAI,KAAK,GAAG,MAAM,CAAC,QAAQ;YAAE,OAAO,WAAW,CAAC;QAChD,OAAO;YACL,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;YACpE,qBAAqB,EAAE,CAAC;SACzB,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type ActionConfigFor, type PromotionActionDefinition } from './types.js';
2
+ /** Buy X units of a specific product → Y% off the whole cart. */
3
+ export declare const buyXUnitsPercentOffAction: PromotionActionDefinition<ActionConfigFor<'buy_x_units_percent_off'>>;
4
+ //# sourceMappingURL=buy-x-units-percent-off.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buy-x-units-percent-off.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/buy-x-units-percent-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,eAAe,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG/F,iEAAiE;AACjE,eAAO,MAAM,yBAAyB,EAAE,yBAAyB,CAC/D,eAAe,CAAC,yBAAyB,CAAC,CAU3C,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { NO_DISCOUNT } from './types.js';
2
+ import { expandUnits, round2 } from './util.js';
3
+ /** Buy X units of a specific product → Y% off the whole cart. */
4
+ export const buyXUnitsPercentOffAction = {
5
+ type: 'buy_x_units_percent_off',
6
+ labelKey: 'action.buy_x_units_percent_off',
7
+ apply: (config, ctx) => {
8
+ const count = expandUnits(ctx.lines).filter((u) => u.productId === config.productId).length;
9
+ if (count < config.buyUnits)
10
+ return NO_DISCOUNT;
11
+ const delta = Math.min(round2((ctx.subtotal * config.percent) / 100), ctx.subtotal);
12
+ return { discountSubtotalDelta: round2(Math.max(0, delta)), discountDeliveryDelta: 0 };
13
+ },
14
+ };
15
+ //# sourceMappingURL=buy-x-units-percent-off.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buy-x-units-percent-off.js","sourceRoot":"","sources":["../../../src/backend/actions/buy-x-units-percent-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAwD,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEhD,iEAAiE;AACjE,MAAM,CAAC,MAAM,yBAAyB,GAElC;IACF,IAAI,EAAE,yBAAyB;IAC/B,QAAQ,EAAE,gCAAgC;IAC1C,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACrB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QAC5F,IAAI,KAAK,GAAG,MAAM,CAAC,QAAQ;YAAE,OAAO,WAAW,CAAC;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC;IACzF,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type ActionConfigFor, type PromotionActionDefinition } from './types.js';
2
+ /**
3
+ * Buy X units of a specific product, get Y of that product free. For every
4
+ * (X + Y) units of the product, Y are free (the cheapest such units).
5
+ */
6
+ export declare const buyXUnitsYFreeAction: PromotionActionDefinition<ActionConfigFor<'buy_x_units_y_free'>>;
7
+ //# sourceMappingURL=buy-x-units-y-free.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buy-x-units-y-free.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/buy-x-units-y-free.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAGlF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,yBAAyB,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAajG,CAAC"}
@@ -0,0 +1,21 @@
1
+ import {} from './types.js';
2
+ import { expandUnits, round2 } from './util.js';
3
+ /**
4
+ * Buy X units of a specific product, get Y of that product free. For every
5
+ * (X + Y) units of the product, Y are free (the cheapest such units).
6
+ */
7
+ export const buyXUnitsYFreeAction = {
8
+ type: 'buy_x_units_y_free',
9
+ labelKey: 'action.buy_x_units_y_free',
10
+ apply: (config, ctx) => {
11
+ const units = expandUnits(ctx.lines)
12
+ .filter((u) => u.productId === config.productId)
13
+ .sort((a, b) => a.price - b.price);
14
+ const groupSize = config.buyUnits + config.freeUnits;
15
+ const groups = Math.floor(units.length / groupSize);
16
+ const freeCount = groups * config.freeUnits;
17
+ const delta = round2(units.slice(0, freeCount).reduce((acc, u) => acc + u.price, 0));
18
+ return { discountSubtotalDelta: Math.min(delta, ctx.subtotal), discountDeliveryDelta: 0 };
19
+ },
20
+ };
21
+ //# sourceMappingURL=buy-x-units-y-free.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buy-x-units-y-free.js","sourceRoot":"","sources":["../../../src/backend/actions/buy-x-units-y-free.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEhD;;;GAGG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAqE;IACpG,IAAI,EAAE,oBAAoB;IAC1B,QAAQ,EAAE,2BAA2B;IACrC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACrB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;aACjC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,CAAC;aAC/C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACrC,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC;QACrD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC;QAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACrF,OAAO,EAAE,qBAAqB,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC;IAC5F,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { type ActionConfigFor, type PromotionActionDefinition } from './types.js';
2
+ /**
3
+ * Every Nth product in the cart is Y% cheaper. The discounted units are the
4
+ * cheapest `floor(totalUnits / nth)` units (deterministic on ties).
5
+ */
6
+ export declare const everyNthProductPercentOffAction: PromotionActionDefinition<ActionConfigFor<'every_nth_product_percent_off'>>;
7
+ //# sourceMappingURL=every-nth-product-percent-off.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"every-nth-product-percent-off.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/every-nth-product-percent-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAGlF;;;GAGG;AACH,eAAO,MAAM,+BAA+B,EAAE,yBAAyB,CACrE,eAAe,CAAC,+BAA+B,CAAC,CAYjD,CAAC"}
@@ -0,0 +1,17 @@
1
+ import {} from './types.js';
2
+ import { expandUnits, round2 } from './util.js';
3
+ /**
4
+ * Every Nth product in the cart is Y% cheaper. The discounted units are the
5
+ * cheapest `floor(totalUnits / nth)` units (deterministic on ties).
6
+ */
7
+ export const everyNthProductPercentOffAction = {
8
+ type: 'every_nth_product_percent_off',
9
+ labelKey: 'action.every_nth_product_percent_off',
10
+ apply: (config, ctx) => {
11
+ const units = expandUnits(ctx.lines).sort((a, b) => a.price - b.price);
12
+ const count = Math.floor(units.length / config.nth);
13
+ const delta = round2(units.slice(0, count).reduce((acc, u) => acc + (u.price * config.percent) / 100, 0));
14
+ return { discountSubtotalDelta: Math.min(delta, ctx.subtotal), discountDeliveryDelta: 0 };
15
+ },
16
+ };
17
+ //# sourceMappingURL=every-nth-product-percent-off.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"every-nth-product-percent-off.js","sourceRoot":"","sources":["../../../src/backend/actions/every-nth-product-percent-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,MAAM,YAAY,CAAC;AAClF,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEhD;;;GAGG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAExC;IACF,IAAI,EAAE,+BAA+B;IACrC,QAAQ,EAAE,sCAAsC;IAChD,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACrB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,MAAM,CAClB,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CACpF,CAAC;QACF,OAAO,EAAE,qBAAqB,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC;IAC5F,CAAC;CACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ActionConfigFor, PromotionActionDefinition } from './types.js';
2
+ /** Zero the cart's delivery cost. */
3
+ export declare const freeDeliveryAction: PromotionActionDefinition<ActionConfigFor<'free_delivery'>>;
4
+ //# sourceMappingURL=free-delivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"free-delivery.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/free-delivery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG7E,qCAAqC;AACrC,eAAO,MAAM,kBAAkB,EAAE,yBAAyB,CAAC,eAAe,CAAC,eAAe,CAAC,CAO1F,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { round2 } from './util.js';
2
+ /** Zero the cart's delivery cost. */
3
+ export const freeDeliveryAction = {
4
+ type: 'free_delivery',
5
+ labelKey: 'action.free_delivery',
6
+ apply: (_config, ctx) => ({
7
+ discountSubtotalDelta: 0,
8
+ discountDeliveryDelta: round2(ctx.deliveryTotal),
9
+ }),
10
+ };
11
+ //# sourceMappingURL=free-delivery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"free-delivery.js","sourceRoot":"","sources":["../../../src/backend/actions/free-delivery.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,qCAAqC;AACrC,MAAM,CAAC,MAAM,kBAAkB,GAAgE;IAC7F,IAAI,EAAE,eAAe;IACrB,QAAQ,EAAE,sBAAsB;IAChC,KAAK,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;QACxB,qBAAqB,EAAE,CAAC;QACxB,qBAAqB,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC;KACjD,CAAC;CACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ActionConfigFor, PromotionActionDefinition } from './types.js';
2
+ /** X% off the whole cart subtotal. */
3
+ export declare const percentageOffCartAction: PromotionActionDefinition<ActionConfigFor<'percentage_off_cart'>>;
4
+ //# sourceMappingURL=percentage-off-cart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"percentage-off-cart.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/percentage-off-cart.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG7E,sCAAsC;AACtC,eAAO,MAAM,uBAAuB,EAAE,yBAAyB,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAOrG,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { round2 } from './util.js';
2
+ /** X% off the whole cart subtotal. */
3
+ export const percentageOffCartAction = {
4
+ type: 'percentage_off_cart',
5
+ labelKey: 'action.percentage_off_cart',
6
+ apply: (config, ctx) => {
7
+ const delta = Math.min(round2((ctx.subtotal * config.percent) / 100), ctx.subtotal);
8
+ return { discountSubtotalDelta: round2(Math.max(0, delta)), discountDeliveryDelta: 0 };
9
+ },
10
+ };
11
+ //# sourceMappingURL=percentage-off-cart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"percentage-off-cart.js","sourceRoot":"","sources":["../../../src/backend/actions/percentage-off-cart.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,sCAAsC;AACtC,MAAM,CAAC,MAAM,uBAAuB,GAAsE;IACxG,IAAI,EAAE,qBAAqB;IAC3B,QAAQ,EAAE,4BAA4B;IACtC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACrB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC;IACzF,CAAC;CACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type ActionConfigFor, type PromotionActionDefinition } from './types.js';
2
+ /** For every X (amount) spent, Y (amount) off the whole cart. */
3
+ export declare const spendXAmountOffAction: PromotionActionDefinition<ActionConfigFor<'spend_x_amount_off'>>;
4
+ //# sourceMappingURL=spend-x-amount-off.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-x-amount-off.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/spend-x-amount-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,eAAe,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG/F,iEAAiE;AACjE,eAAO,MAAM,qBAAqB,EAAE,yBAAyB,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAUlG,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { NO_DISCOUNT } from './types.js';
2
+ import { round2 } from './util.js';
3
+ /** For every X (amount) spent, Y (amount) off the whole cart. */
4
+ export const spendXAmountOffAction = {
5
+ type: 'spend_x_amount_off',
6
+ labelKey: 'action.spend_x_amount_off',
7
+ apply: (config, ctx) => {
8
+ if (config.currency !== ctx.currency)
9
+ return NO_DISCOUNT;
10
+ const steps = Math.floor(ctx.subtotal / config.spendStep);
11
+ if (steps <= 0)
12
+ return NO_DISCOUNT;
13
+ const delta = Math.min(round2(steps * config.amount), ctx.subtotal);
14
+ return { discountSubtotalDelta: round2(Math.max(0, delta)), discountDeliveryDelta: 0 };
15
+ },
16
+ };
17
+ //# sourceMappingURL=spend-x-amount-off.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-x-amount-off.js","sourceRoot":"","sources":["../../../src/backend/actions/spend-x-amount-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAwD,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,iEAAiE;AACjE,MAAM,CAAC,MAAM,qBAAqB,GAAqE;IACrG,IAAI,EAAE,oBAAoB;IAC1B,QAAQ,EAAE,2BAA2B;IACrC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACrB,IAAI,MAAM,CAAC,QAAQ,KAAK,GAAG,CAAC,QAAQ;YAAE,OAAO,WAAW,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1D,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO,WAAW,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC;IACzF,CAAC;CACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { type ActionConfigFor, type PromotionActionDefinition } from './types.js';
2
+ /** For every X (amount) spent, Y% off the whole cart (capped at 100%). */
3
+ export declare const spendXPercentOffAction: PromotionActionDefinition<ActionConfigFor<'spend_x_percent_off'>>;
4
+ //# sourceMappingURL=spend-x-percent-off.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-x-percent-off.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/spend-x-percent-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,eAAe,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAG/F,0EAA0E;AAC1E,eAAO,MAAM,sBAAsB,EAAE,yBAAyB,CAAC,eAAe,CAAC,qBAAqB,CAAC,CAWpG,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { NO_DISCOUNT } from './types.js';
2
+ import { round2 } from './util.js';
3
+ /** For every X (amount) spent, Y% off the whole cart (capped at 100%). */
4
+ export const spendXPercentOffAction = {
5
+ type: 'spend_x_percent_off',
6
+ labelKey: 'action.spend_x_percent_off',
7
+ apply: (config, ctx) => {
8
+ if (config.currency !== ctx.currency)
9
+ return NO_DISCOUNT;
10
+ const steps = Math.floor(ctx.subtotal / config.spendStep);
11
+ if (steps <= 0)
12
+ return NO_DISCOUNT;
13
+ const percent = Math.min(steps * config.percent, 100);
14
+ const delta = Math.min(round2((ctx.subtotal * percent) / 100), ctx.subtotal);
15
+ return { discountSubtotalDelta: round2(Math.max(0, delta)), discountDeliveryDelta: 0 };
16
+ },
17
+ };
18
+ //# sourceMappingURL=spend-x-percent-off.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-x-percent-off.js","sourceRoot":"","sources":["../../../src/backend/actions/spend-x-percent-off.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAwD,MAAM,YAAY,CAAC;AAC/F,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,sBAAsB,GAAsE;IACvG,IAAI,EAAE,qBAAqB;IAC3B,QAAQ,EAAE,4BAA4B;IACtC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACrB,IAAI,MAAM,CAAC,QAAQ,KAAK,GAAG,CAAC,QAAQ;YAAE,OAAO,WAAW,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;QAC1D,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO,WAAW,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,QAAQ,GAAG,OAAO,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC;IACzF,CAAC;CACF,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { CartLine, PromotionAction } from '@endora-commerce/contracts';
2
+ /**
3
+ * The cart facts an action operates on. The engine clamps the returned
4
+ * deltas so nothing goes below zero; individual actions also cap their own
5
+ * subtotal delta at `ctx.subtotal` defensively (FR-015).
6
+ */
7
+ export interface CartApplyContext {
8
+ lines: readonly CartLine[];
9
+ subtotal: number;
10
+ deliveryTotal: number;
11
+ currency: string;
12
+ }
13
+ export interface DiscountResult {
14
+ /** Amount removed from the cart subtotal (≥ 0). */
15
+ discountSubtotalDelta: number;
16
+ /** Amount removed from delivery (≥ 0; used by free_delivery). */
17
+ discountDeliveryDelta: number;
18
+ /** Optional per-line attribution for display/debug. */
19
+ perLine?: {
20
+ productId: string;
21
+ variantId: string | null;
22
+ amount: number;
23
+ }[];
24
+ }
25
+ /** Narrow the action union to the variant a definition handles. */
26
+ export type ActionConfigFor<T extends PromotionAction['type']> = Extract<PromotionAction, {
27
+ type: T;
28
+ }>;
29
+ /** A registrable promotion action — built-in or contributed by another module (FR-012). */
30
+ export interface PromotionActionDefinition<C extends PromotionAction = PromotionAction> {
31
+ type: C['type'];
32
+ /** i18n key for the admin action picker. */
33
+ labelKey: string;
34
+ apply(config: C, ctx: CartApplyContext): DiscountResult;
35
+ }
36
+ export declare const NO_DISCOUNT: DiscountResult;
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE5E;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,qBAAqB,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,uDAAuD;IACvD,OAAO,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC7E;AAED,mEAAmE;AACnE,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,eAAe,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,eAAe,EAAE;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC;AAEvG,2FAA2F;AAC3F,MAAM,WAAW,yBAAyB,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe;IACpF,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IAChB,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,gBAAgB,GAAG,cAAc,CAAC;CACzD;AAED,eAAO,MAAM,WAAW,EAAE,cAAuE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export const NO_DISCOUNT = { discountSubtotalDelta: 0, discountDeliveryDelta: 0 };
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/backend/actions/types.ts"],"names":[],"mappings":"AAkCA,MAAM,CAAC,MAAM,WAAW,GAAmB,EAAE,qBAAqB,EAAE,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { CartLine } from '@endora-commerce/contracts';
2
+ /** Round to 2 decimal places (money). */
3
+ export declare function round2(value: number): number;
4
+ export declare function cartSubtotal(lines: readonly CartLine[]): number;
5
+ /** A single purchasable unit expanded from a cart line (one per quantity). */
6
+ export interface CartUnit {
7
+ productId: string;
8
+ variantId: string | null;
9
+ price: number;
10
+ }
11
+ /** Expand cart lines into individual units (one entry per quantity). */
12
+ export declare function expandUnits(lines: readonly CartLine[]): CartUnit[];
13
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAE3D,yCAAyC;AACzC,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAE/D;AAED,8EAA8E;AAC9E,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAQlE"}
@@ -0,0 +1,18 @@
1
+ /** Round to 2 decimal places (money). */
2
+ export function round2(value) {
3
+ return Math.round(value * 100) / 100;
4
+ }
5
+ export function cartSubtotal(lines) {
6
+ return round2(lines.reduce((acc, l) => acc + l.unitPrice.amount * l.quantity, 0));
7
+ }
8
+ /** Expand cart lines into individual units (one entry per quantity). */
9
+ export function expandUnits(lines) {
10
+ const units = [];
11
+ for (const line of lines) {
12
+ for (let i = 0; i < line.quantity; i += 1) {
13
+ units.push({ productId: line.productId, variantId: line.variantId, price: line.unitPrice.amount });
14
+ }
15
+ }
16
+ return units;
17
+ }
18
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../../src/backend/actions/util.ts"],"names":[],"mappings":"AAEA,yCAAyC;AACzC,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAA0B;IACrD,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;AACpF,CAAC;AASD,wEAAwE;AACxE,MAAM,UAAU,WAAW,CAAC,KAA0B;IACpD,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;QACrG,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { OptionalProps } from '@mikro-orm/core';
2
+ /**
3
+ * Feature 045 — generator config for a batch of coupon codes (US4).
4
+ */
5
+ export declare class CouponBatch {
6
+ [OptionalProps]?: 'id' | 'createdAt' | 'prefix' | 'suffix' | 'dashEvery';
7
+ id: string;
8
+ promotionId: string;
9
+ count: number;
10
+ length: number;
11
+ /** `alnum` | `digits` | `letters`. */
12
+ format: 'alnum' | 'digits' | 'letters';
13
+ prefix?: string | null;
14
+ suffix?: string | null;
15
+ /** Insert a dash every N characters; 0 = none. */
16
+ dashEvery: number;
17
+ /** `per_coupon` | `shared_batch`. */
18
+ limitScope: 'per_coupon' | 'shared_batch';
19
+ createdAt: Date;
20
+ }
21
+ //# sourceMappingURL=coupon-batch.entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coupon-batch.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/coupon-batch.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAIrF;;GAEG;AACH,qBAEa,WAAW;IACtB,CAAC,aAAa,CAAC,CAAC,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IAGzE,EAAE,EAAE,MAAM,CAAgB;IAI1B,WAAW,EAAG,MAAM,CAAC;IAGrB,KAAK,EAAG,MAAM,CAAC;IAGf,MAAM,EAAG,MAAM,CAAC;IAEhB,sCAAsC;IAEtC,MAAM,EAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IAGxC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAGvB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB,kDAAkD;IAElD,SAAS,EAAE,MAAM,CAAK;IAEtB,qCAAqC;IAErC,UAAU,EAAG,YAAY,GAAG,cAAc,CAAC;IAG3C,SAAS,EAAE,IAAI,CAAc;CAC9B"}