@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.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/admin/api/promotions-client.d.ts +98 -0
- package/dist/admin/api/promotions-client.d.ts.map +1 -0
- package/dist/admin/api/promotions-client.js +40 -0
- package/dist/admin/api/promotions-client.js.map +1 -0
- package/dist/admin/components/CouponGeneratorForm.d.ts +10 -0
- package/dist/admin/components/CouponGeneratorForm.d.ts.map +1 -0
- package/dist/admin/components/CouponGeneratorForm.js +56 -0
- package/dist/admin/components/CouponGeneratorForm.js.map +1 -0
- package/dist/admin/index.d.ts +43 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +69 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/PromotionEditPage.d.ts +9 -0
- package/dist/admin/pages/PromotionEditPage.d.ts.map +1 -0
- package/dist/admin/pages/PromotionEditPage.js +333 -0
- package/dist/admin/pages/PromotionEditPage.js.map +1 -0
- package/dist/admin/pages/PromotionRulesPage.d.ts +14 -0
- package/dist/admin/pages/PromotionRulesPage.d.ts.map +1 -0
- package/dist/admin/pages/PromotionRulesPage.js +86 -0
- package/dist/admin/pages/PromotionRulesPage.js.map +1 -0
- package/dist/admin/pages/PromotionStatsPage.d.ts +13 -0
- package/dist/admin/pages/PromotionStatsPage.d.ts.map +1 -0
- package/dist/admin/pages/PromotionStatsPage.js +44 -0
- package/dist/admin/pages/PromotionStatsPage.js.map +1 -0
- package/dist/admin/pages/PromotionsPage.d.ts +9 -0
- package/dist/admin/pages/PromotionsPage.d.ts.map +1 -0
- package/dist/admin/pages/PromotionsPage.js +79 -0
- package/dist/admin/pages/PromotionsPage.js.map +1 -0
- package/dist/backend/actions/amount-off-cart.d.ts +4 -0
- package/dist/backend/actions/amount-off-cart.d.ts.map +1 -0
- package/dist/backend/actions/amount-off-cart.js +16 -0
- package/dist/backend/actions/amount-off-cart.js.map +1 -0
- package/dist/backend/actions/buy-x-get-y-free.d.ts +8 -0
- package/dist/backend/actions/buy-x-get-y-free.d.ts.map +1 -0
- package/dist/backend/actions/buy-x-get-y-free.js +20 -0
- package/dist/backend/actions/buy-x-get-y-free.js.map +1 -0
- package/dist/backend/actions/buy-x-units-amount-off.d.ts +4 -0
- package/dist/backend/actions/buy-x-units-amount-off.d.ts.map +1 -0
- package/dist/backend/actions/buy-x-units-amount-off.js +19 -0
- package/dist/backend/actions/buy-x-units-amount-off.js.map +1 -0
- package/dist/backend/actions/buy-x-units-percent-off.d.ts +4 -0
- package/dist/backend/actions/buy-x-units-percent-off.d.ts.map +1 -0
- package/dist/backend/actions/buy-x-units-percent-off.js +15 -0
- package/dist/backend/actions/buy-x-units-percent-off.js.map +1 -0
- package/dist/backend/actions/buy-x-units-y-free.d.ts +7 -0
- package/dist/backend/actions/buy-x-units-y-free.d.ts.map +1 -0
- package/dist/backend/actions/buy-x-units-y-free.js +21 -0
- package/dist/backend/actions/buy-x-units-y-free.js.map +1 -0
- package/dist/backend/actions/every-nth-product-percent-off.d.ts +7 -0
- package/dist/backend/actions/every-nth-product-percent-off.d.ts.map +1 -0
- package/dist/backend/actions/every-nth-product-percent-off.js +17 -0
- package/dist/backend/actions/every-nth-product-percent-off.js.map +1 -0
- package/dist/backend/actions/free-delivery.d.ts +4 -0
- package/dist/backend/actions/free-delivery.d.ts.map +1 -0
- package/dist/backend/actions/free-delivery.js +11 -0
- package/dist/backend/actions/free-delivery.js.map +1 -0
- package/dist/backend/actions/percentage-off-cart.d.ts +4 -0
- package/dist/backend/actions/percentage-off-cart.d.ts.map +1 -0
- package/dist/backend/actions/percentage-off-cart.js +11 -0
- package/dist/backend/actions/percentage-off-cart.js.map +1 -0
- package/dist/backend/actions/spend-x-amount-off.d.ts +4 -0
- package/dist/backend/actions/spend-x-amount-off.d.ts.map +1 -0
- package/dist/backend/actions/spend-x-amount-off.js +17 -0
- package/dist/backend/actions/spend-x-amount-off.js.map +1 -0
- package/dist/backend/actions/spend-x-percent-off.d.ts +4 -0
- package/dist/backend/actions/spend-x-percent-off.d.ts.map +1 -0
- package/dist/backend/actions/spend-x-percent-off.js +18 -0
- package/dist/backend/actions/spend-x-percent-off.js.map +1 -0
- package/dist/backend/actions/types.d.ts +37 -0
- package/dist/backend/actions/types.d.ts.map +1 -0
- package/dist/backend/actions/types.js +2 -0
- package/dist/backend/actions/types.js.map +1 -0
- package/dist/backend/actions/util.d.ts +13 -0
- package/dist/backend/actions/util.d.ts.map +1 -0
- package/dist/backend/actions/util.js +18 -0
- package/dist/backend/actions/util.js.map +1 -0
- package/dist/backend/entities/coupon-batch.entity.d.ts +21 -0
- package/dist/backend/entities/coupon-batch.entity.d.ts.map +1 -0
- package/dist/backend/entities/coupon-batch.entity.js +78 -0
- package/dist/backend/entities/coupon-batch.entity.js.map +1 -0
- package/dist/backend/entities/promotion-coupon.entity.d.ts +17 -0
- package/dist/backend/entities/promotion-coupon.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion-coupon.entity.js +64 -0
- package/dist/backend/entities/promotion-coupon.entity.js.map +1 -0
- package/dist/backend/entities/promotion-rule.entity.d.ts +16 -0
- package/dist/backend/entities/promotion-rule.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion-rule.entity.js +57 -0
- package/dist/backend/entities/promotion-rule.entity.js.map +1 -0
- package/dist/backend/entities/promotion-usage-counter.entity.d.ts +15 -0
- package/dist/backend/entities/promotion-usage-counter.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion-usage-counter.entity.js +48 -0
- package/dist/backend/entities/promotion-usage-counter.entity.js.map +1 -0
- package/dist/backend/entities/promotion-usage.entity.d.ts +21 -0
- package/dist/backend/entities/promotion-usage.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion-usage.entity.js +89 -0
- package/dist/backend/entities/promotion-usage.entity.js.map +1 -0
- package/dist/backend/entities/promotion.entity.d.ts +63 -0
- package/dist/backend/entities/promotion.entity.d.ts.map +1 -0
- package/dist/backend/entities/promotion.entity.js +194 -0
- package/dist/backend/entities/promotion.entity.js.map +1 -0
- package/dist/backend/index.d.ts +124 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +182 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.d.ts +88 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +256 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/services/coupon-service.d.ts +40 -0
- package/dist/backend/services/coupon-service.d.ts.map +1 -0
- package/dist/backend/services/coupon-service.js +178 -0
- package/dist/backend/services/coupon-service.js.map +1 -0
- package/dist/backend/services/promotion-action-registry.d.ts +20 -0
- package/dist/backend/services/promotion-action-registry.d.ts.map +1 -0
- package/dist/backend/services/promotion-action-registry.js +57 -0
- package/dist/backend/services/promotion-action-registry.js.map +1 -0
- package/dist/backend/services/promotion-code-port.d.ts +24 -0
- package/dist/backend/services/promotion-code-port.d.ts.map +1 -0
- package/dist/backend/services/promotion-code-port.js +53 -0
- package/dist/backend/services/promotion-code-port.js.map +1 -0
- package/dist/backend/services/promotion-currency-reference.d.ts +13 -0
- package/dist/backend/services/promotion-currency-reference.d.ts.map +1 -0
- package/dist/backend/services/promotion-currency-reference.js +25 -0
- package/dist/backend/services/promotion-currency-reference.js.map +1 -0
- package/dist/backend/services/promotion-rule-evaluator.d.ts +32 -0
- package/dist/backend/services/promotion-rule-evaluator.d.ts.map +1 -0
- package/dist/backend/services/promotion-rule-evaluator.js +131 -0
- package/dist/backend/services/promotion-rule-evaluator.js.map +1 -0
- package/dist/backend/services/promotion-rule-service.d.ts +62 -0
- package/dist/backend/services/promotion-rule-service.d.ts.map +1 -0
- package/dist/backend/services/promotion-rule-service.js +95 -0
- package/dist/backend/services/promotion-rule-service.js.map +1 -0
- package/dist/backend/services/promotion-rule-store.d.ts +33 -0
- package/dist/backend/services/promotion-rule-store.d.ts.map +1 -0
- package/dist/backend/services/promotion-rule-store.js +88 -0
- package/dist/backend/services/promotion-rule-store.js.map +1 -0
- package/dist/backend/services/promotion-service.d.ts +312 -0
- package/dist/backend/services/promotion-service.d.ts.map +1 -0
- package/dist/backend/services/promotion-service.js +762 -0
- package/dist/backend/services/promotion-service.js.map +1 -0
- package/dist/backend/services/promotion-stats-service.d.ts +15 -0
- package/dist/backend/services/promotion-stats-service.d.ts.map +1 -0
- package/dist/backend/services/promotion-stats-service.js +66 -0
- package/dist/backend/services/promotion-stats-service.js.map +1 -0
- package/dist/backend/services/promotion-usage-service.d.ts +46 -0
- package/dist/backend/services/promotion-usage-service.d.ts.map +1 -0
- package/dist/backend/services/promotion-usage-service.js +168 -0
- package/dist/backend/services/promotion-usage-service.js.map +1 -0
- package/dist/manifest.d.ts +172 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +140 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260505T074605_promotions_criteria.d.ts +19 -0
- package/dist/migrations/20260505T074605_promotions_criteria.d.ts.map +1 -0
- package/dist/migrations/20260505T074605_promotions_criteria.js +23 -0
- package/dist/migrations/20260505T074605_promotions_criteria.js.map +1 -0
- package/dist/migrations/20260618T100727_promotions_engine.d.ts +19 -0
- package/dist/migrations/20260618T100727_promotions_engine.d.ts.map +1 -0
- package/dist/migrations/20260618T100727_promotions_engine.js +148 -0
- package/dist/migrations/20260618T100727_promotions_engine.js.map +1 -0
- package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.d.ts +42 -0
- package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.d.ts.map +1 -0
- package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.js +76 -0
- package/dist/migrations/20260818T081251_promotions_promotion_usage_order_fk.js.map +1 -0
- package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.d.ts +27 -0
- package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.d.ts.map +1 -0
- package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.js +44 -0
- package/dist/migrations/20260912T094715_promotions_sales_channel_promotions.js.map +1 -0
- package/dist/migrations/index.d.ts +39 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +44 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +106 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/promotions.md +96 -0
- package/i18n/en.json +34 -0
- package/i18n/pl.json +34 -0
- package/package.json +101 -0
- 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 @@
|
|
|
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 @@
|
|
|
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"}
|