@pisell/pisellos 0.0.461 → 0.0.462

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.
@@ -0,0 +1,217 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/model/strategy/adapter/promotion/adapter.ts
20
+ var adapter_exports = {};
21
+ __export(adapter_exports, {
22
+ PromotionAdapter: () => PromotionAdapter,
23
+ default: () => adapter_default
24
+ });
25
+ module.exports = __toCommonJS(adapter_exports);
26
+ var import_type = require("./type");
27
+ var PromotionAdapter = class {
28
+ constructor() {
29
+ this.name = "PromotionAdapter";
30
+ this.version = "1.0.0";
31
+ }
32
+ /**
33
+ * 准备运行时上下文
34
+ *
35
+ * 将业务数据转换为策略引擎可识别的 RuntimeContext
36
+ */
37
+ prepareContext(businessData) {
38
+ const { products, currentProduct, channel, custom } = businessData;
39
+ const now = /* @__PURE__ */ new Date();
40
+ const currentDateTime = this.formatDateTime(now);
41
+ const evaluatingProduct = currentProduct || (products.length > 0 ? products[0] : null);
42
+ return {
43
+ entities: {
44
+ products,
45
+ currentProduct: evaluatingProduct
46
+ },
47
+ attributes: {
48
+ // 当前时间(格式化字符串,用于时间条件判断)
49
+ currentDateTime,
50
+ // 当前评估的商品信息(用于 product_match 运算符)
51
+ productIdAndVariantId: evaluatingProduct ? {
52
+ product_id: evaluatingProduct.product_id,
53
+ product_variant_id: evaluatingProduct.product_variant_id
54
+ } : null,
55
+ // 渠道
56
+ channel: channel || "",
57
+ // 商品总数量
58
+ totalQuantity: products.reduce((sum, p) => sum + p.quantity, 0),
59
+ // 商品总金额
60
+ totalAmount: products.reduce((sum, p) => sum + p.price * p.quantity, 0),
61
+ // 自定义属性
62
+ ...custom
63
+ },
64
+ metadata: {
65
+ timestamp: Date.now()
66
+ }
67
+ };
68
+ }
69
+ /**
70
+ * 转换执行结果
71
+ *
72
+ * 将策略引擎的通用结果转换为业务层需要的格式
73
+ * 主要是整理 matchedActions,让业务层更容易使用
74
+ */
75
+ transformResult(result, businessData) {
76
+ const applicableProducts = businessData ? this.getApplicableProducts(result, businessData) : [];
77
+ if (!result.applicable) {
78
+ return {
79
+ isApplicable: false,
80
+ applicableProducts: [],
81
+ reason: result.message,
82
+ reasonCode: result.code,
83
+ strategyResult: result
84
+ };
85
+ }
86
+ const matchedAction = result.matchedActions[0];
87
+ if (!matchedAction) {
88
+ return {
89
+ isApplicable: false,
90
+ applicableProducts: [],
91
+ reason: "No matched action",
92
+ reasonCode: "NO_ACTION",
93
+ strategyResult: result
94
+ };
95
+ }
96
+ const actionDetail = this.parseActionDetail(matchedAction);
97
+ return {
98
+ isApplicable: true,
99
+ actionType: matchedAction.type,
100
+ actionDetail,
101
+ applicableProducts,
102
+ strategyResult: result
103
+ };
104
+ }
105
+ /**
106
+ * 格式化配置
107
+ */
108
+ formatConfig(result, businessData) {
109
+ return { result, businessData };
110
+ }
111
+ // ============================================
112
+ // 私有辅助方法
113
+ // ============================================
114
+ /**
115
+ * 格式化日期时间
116
+ */
117
+ formatDateTime(date) {
118
+ const year = date.getFullYear();
119
+ const month = String(date.getMonth() + 1).padStart(2, "0");
120
+ const day = String(date.getDate()).padStart(2, "0");
121
+ const hours = String(date.getHours()).padStart(2, "0");
122
+ const minutes = String(date.getMinutes()).padStart(2, "0");
123
+ const seconds = String(date.getSeconds()).padStart(2, "0");
124
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
125
+ }
126
+ /**
127
+ * 解析 Action 详情
128
+ *
129
+ * 将 matchedAction 转换为更易用的结构
130
+ */
131
+ parseActionDetail(action) {
132
+ switch (action.type) {
133
+ case import_type.PROMOTION_ACTION_TYPES.X_ITEMS_FOR_Y_PRICE:
134
+ return this.parseXItemsForYPriceAction(action);
135
+ case import_type.PROMOTION_ACTION_TYPES.BUY_X_GET_Y_FREE:
136
+ return this.parseBuyXGetYFreeAction(action);
137
+ default:
138
+ return void 0;
139
+ }
140
+ }
141
+ /**
142
+ * 解析 X件Y元 Action
143
+ */
144
+ parseXItemsForYPriceAction(action) {
145
+ const value = action.value || {};
146
+ const config = action.config || {};
147
+ return {
148
+ type: import_type.PROMOTION_ACTION_TYPES.X_ITEMS_FOR_Y_PRICE,
149
+ x: value.x || 2,
150
+ price: value.price || 0,
151
+ allowCrossProduct: config.allowCrossProduct ?? true,
152
+ cumulative: config.cumulative ?? true
153
+ };
154
+ }
155
+ /**
156
+ * 解析 买X送Y Action
157
+ */
158
+ parseBuyXGetYFreeAction(action) {
159
+ const value = action.value || {};
160
+ const config = action.config || {};
161
+ return {
162
+ type: import_type.PROMOTION_ACTION_TYPES.BUY_X_GET_Y_FREE,
163
+ buyQuantity: value.buyQuantity || 1,
164
+ freeQuantity: value.freeQuantity || 1,
165
+ giftSelectionMode: config.giftSelectionMode || "user_select",
166
+ cumulative: config.cumulative ?? true,
167
+ giftProducts: config.giftProducts || []
168
+ };
169
+ }
170
+ /**
171
+ * 获取适用的商品列表
172
+ *
173
+ * 从购物车商品中筛选出符合策略条件的商品
174
+ */
175
+ getApplicableProducts(result, businessData) {
176
+ const { products } = businessData;
177
+ const productMatchRule = this.findProductMatchRule(result.config);
178
+ if (!productMatchRule) {
179
+ return products;
180
+ }
181
+ const configProducts = productMatchRule.value;
182
+ return products.filter(
183
+ (product) => this.isProductMatch(product, configProducts)
184
+ );
185
+ }
186
+ /**
187
+ * 查找商品匹配规则
188
+ */
189
+ findProductMatchRule(config) {
190
+ var _a;
191
+ if (!((_a = config == null ? void 0 : config.conditions) == null ? void 0 : _a.rules)) {
192
+ return null;
193
+ }
194
+ return config.conditions.rules.find(
195
+ (rule) => rule.field === "productIdAndVariantId" && (rule.operator === "product_match" || rule.operator === "object_in")
196
+ );
197
+ }
198
+ /**
199
+ * 检查商品是否匹配
200
+ */
201
+ isProductMatch(product, configProducts) {
202
+ return configProducts.some((config) => {
203
+ if (config.product_id !== product.product_id) {
204
+ return false;
205
+ }
206
+ if (config.product_variant_id === 0) {
207
+ return true;
208
+ }
209
+ return config.product_variant_id === product.product_variant_id;
210
+ });
211
+ }
212
+ };
213
+ var adapter_default = PromotionAdapter;
214
+ // Annotate the CommonJS export names for ESM import in node:
215
+ 0 && (module.exports = {
216
+ PromotionAdapter
217
+ });
@@ -111,9 +111,10 @@ export declare class PromotionEvaluator {
111
111
  * 只检查商品 ID 匹配和策略时间范围
112
112
  *
113
113
  * @param input 评估输入(商品列表)
114
+ * @param matchVariant 是否需要匹配 product_variant_id,默认 true(严格匹配),false 时只匹配 product_id
114
115
  * @returns 每个商品的适用策略完整数据
115
116
  */
116
- getProductsApplicableStrategies(input: PromotionEvaluatorInput): Array<{
117
+ getProductsApplicableStrategies(input: PromotionEvaluatorInput, matchVariant?: boolean): Array<{
117
118
  product: PromotionProduct;
118
119
  applicableStrategies: Array<{
119
120
  strategyId: string;
@@ -34,7 +34,7 @@ __export(evaluator_exports, {
34
34
  module.exports = __toCommonJS(evaluator_exports);
35
35
  var import_decimal = __toESM(require("decimal.js"));
36
36
  var import_engine = require("../../engine");
37
- var import_index = require("./index");
37
+ var import_adapter = require("./adapter");
38
38
  var import_type = require("./type");
39
39
  var defaultLocales = {
40
40
  en: {
@@ -62,7 +62,7 @@ var PromotionEvaluator = class {
62
62
  debug: false,
63
63
  enableTrace: false
64
64
  });
65
- this.adapter = new import_index.PromotionAdapter();
65
+ this.adapter = new import_adapter.PromotionAdapter();
66
66
  }
67
67
  // ============================================
68
68
  // 配置管理
@@ -369,18 +369,38 @@ var PromotionEvaluator = class {
369
369
  * 只检查商品 ID 匹配和策略时间范围
370
370
  *
371
371
  * @param input 评估输入(商品列表)
372
+ * @param matchVariant 是否需要匹配 product_variant_id,默认 true(严格匹配),false 时只匹配 product_id
372
373
  * @returns 每个商品的适用策略完整数据
373
374
  */
374
- getProductsApplicableStrategies(input) {
375
+ getProductsApplicableStrategies(input, matchVariant = true) {
375
376
  const { products, strategyConfigs, channel } = input;
376
377
  const configs = strategyConfigs || this.strategyConfigs;
377
378
  const results = [];
378
379
  for (const product of products) {
379
380
  const applicableStrategies = [];
380
381
  for (const config of configs) {
382
+ let contextProduct = product;
383
+ if (!matchVariant) {
384
+ const productMatchRule = this.findProductMatchRule(config);
385
+ if (productMatchRule) {
386
+ const configProducts = productMatchRule.value;
387
+ const matchedConfig = configProducts.find(
388
+ (item) => item.product_id === product.product_id
389
+ );
390
+ if (!matchedConfig) {
391
+ continue;
392
+ }
393
+ if (matchedConfig.product_variant_id !== 0 && matchedConfig.product_variant_id !== product.product_variant_id) {
394
+ contextProduct = {
395
+ ...product,
396
+ product_variant_id: matchedConfig.product_variant_id
397
+ };
398
+ }
399
+ }
400
+ }
381
401
  const context = this.adapter.prepareContext({
382
- products: [product],
383
- currentProduct: product,
402
+ products: [contextProduct],
403
+ currentProduct: contextProduct,
384
404
  channel
385
405
  });
386
406
  const result = this.engine.evaluate(config, context);
@@ -0,0 +1,138 @@
1
+ /**
2
+ * X件Y元策略配置示例
3
+ *
4
+ * 业务规则:
5
+ * - 每X件商品固定价格Y元(可累计)
6
+ * - 支持跨商品组合(A+B可以凑成一组)
7
+ * - 买5件 = 2组优惠 + 1件原价
8
+ *
9
+ * 商品匹配规则:
10
+ * - product_variant_id = 0 表示匹配任意变体
11
+ * - product_variant_id != 0 表示精确匹配该变体
12
+ */
13
+ export declare const X_ITEMS_FOR_Y_PRICE_STRATEGY: {
14
+ metadata: {
15
+ id: string;
16
+ name: {
17
+ en: string;
18
+ 'zh-CN': string;
19
+ 'zh-HK': string;
20
+ };
21
+ type: string;
22
+ custom: {
23
+ display: {
24
+ product_card: {
25
+ text: {
26
+ en: string;
27
+ 'zh-CN': string;
28
+ 'zh-HK': string;
29
+ };
30
+ type: string;
31
+ };
32
+ };
33
+ };
34
+ };
35
+ conditions: {
36
+ operator: string;
37
+ rules: ({
38
+ type: string;
39
+ field: string;
40
+ value: string;
41
+ operator: string;
42
+ } | {
43
+ type: string;
44
+ field: string;
45
+ value: {
46
+ product_id: number;
47
+ product_variant_id: number;
48
+ }[];
49
+ operator: string;
50
+ })[];
51
+ actionIds: string[];
52
+ };
53
+ actions: {
54
+ id: string;
55
+ type: string;
56
+ value: {
57
+ x: number;
58
+ price: number;
59
+ };
60
+ valueType: string;
61
+ target: string;
62
+ priority: number;
63
+ config: {
64
+ allowCrossProduct: boolean;
65
+ cumulative: boolean;
66
+ };
67
+ }[];
68
+ };
69
+ /**
70
+ * 买X送Y策略配置示例
71
+ *
72
+ * 业务规则:
73
+ * - 买X件送Y件(可累计:买2送2、买3送3...)
74
+ * - 赠品由用户从列表中选择
75
+ *
76
+ * 商品匹配规则:
77
+ * - product_variant_id = 0 表示匹配任意变体
78
+ * - product_variant_id != 0 表示精确匹配该变体
79
+ */
80
+ export declare const BUY_X_GET_Y_FREE_STRATEGY: {
81
+ metadata: {
82
+ id: string;
83
+ name: {
84
+ en: string;
85
+ 'zh-CN': string;
86
+ 'zh-HK': string;
87
+ };
88
+ type: string;
89
+ custom: {
90
+ display: {
91
+ product_card: {
92
+ text: {
93
+ en: string;
94
+ 'zh-CN': string;
95
+ 'zh-HK': string;
96
+ };
97
+ type: string;
98
+ };
99
+ };
100
+ };
101
+ };
102
+ conditions: {
103
+ operator: string;
104
+ rules: ({
105
+ type: string;
106
+ field: string;
107
+ value: string;
108
+ operator: string;
109
+ } | {
110
+ type: string;
111
+ field: string;
112
+ value: {
113
+ product_id: number;
114
+ product_variant_id: number;
115
+ }[];
116
+ operator: string;
117
+ })[];
118
+ actionIds: string[];
119
+ };
120
+ actions: {
121
+ id: string;
122
+ type: string;
123
+ value: {
124
+ buyQuantity: number;
125
+ freeQuantity: number;
126
+ };
127
+ valueType: string;
128
+ target: string;
129
+ priority: number;
130
+ config: {
131
+ cumulative: boolean;
132
+ giftProducts: {
133
+ product_id: number;
134
+ product_variant_id: number;
135
+ }[];
136
+ };
137
+ }[];
138
+ };
@@ -0,0 +1,192 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/model/strategy/adapter/promotion/examples.ts
20
+ var examples_exports = {};
21
+ __export(examples_exports, {
22
+ BUY_X_GET_Y_FREE_STRATEGY: () => BUY_X_GET_Y_FREE_STRATEGY,
23
+ X_ITEMS_FOR_Y_PRICE_STRATEGY: () => X_ITEMS_FOR_Y_PRICE_STRATEGY
24
+ });
25
+ module.exports = __toCommonJS(examples_exports);
26
+ var X_ITEMS_FOR_Y_PRICE_STRATEGY = {
27
+ metadata: {
28
+ id: "STRATEGY_001",
29
+ name: {
30
+ en: "2 items for $10",
31
+ "zh-CN": "2杯10元",
32
+ "zh-HK": "2杯10元"
33
+ },
34
+ type: "promotion",
35
+ custom: {
36
+ display: {
37
+ product_card: {
38
+ text: {
39
+ en: "2 for $10",
40
+ "zh-CN": "2杯10元",
41
+ "zh-HK": "2杯10元"
42
+ },
43
+ type: "tag"
44
+ }
45
+ }
46
+ }
47
+ },
48
+ conditions: {
49
+ operator: "and",
50
+ rules: [
51
+ {
52
+ type: "operator",
53
+ field: "currentDateTime",
54
+ value: "2023-01-01 00:00:00",
55
+ operator: ">="
56
+ },
57
+ {
58
+ type: "operator",
59
+ field: "currentDateTime",
60
+ value: "2027-01-01 00:00:00",
61
+ operator: "<="
62
+ },
63
+ {
64
+ type: "operator",
65
+ field: "productIdAndVariantId",
66
+ value: [
67
+ {
68
+ product_id: 60736,
69
+ product_variant_id: 0
70
+ // 0 = 匹配任意变体
71
+ },
72
+ {
73
+ product_id: 60737,
74
+ product_variant_id: 0
75
+ // 0 = 匹配任意变体
76
+ }
77
+ ],
78
+ operator: "product_match"
79
+ }
80
+ ],
81
+ actionIds: ["1"]
82
+ },
83
+ actions: [
84
+ {
85
+ id: "1",
86
+ type: "X_ITEMS_FOR_Y_PRICE",
87
+ value: {
88
+ x: 2,
89
+ // 每X件
90
+ price: 10
91
+ // 固定价格Y元
92
+ },
93
+ valueType: "object",
94
+ target: "product",
95
+ priority: 1,
96
+ config: {
97
+ allowCrossProduct: true,
98
+ // 允许跨商品组合(A+B可以凑成一组)
99
+ cumulative: true
100
+ // 可累计(买5件 = 2组优惠 + 1件原价)
101
+ }
102
+ }
103
+ ]
104
+ };
105
+ var BUY_X_GET_Y_FREE_STRATEGY = {
106
+ metadata: {
107
+ id: "STRATEGY_002",
108
+ name: {
109
+ en: "Buy 1 Get 1 Free",
110
+ "zh-CN": "买1送1",
111
+ "zh-HK": "買1送1"
112
+ },
113
+ type: "promotion",
114
+ custom: {
115
+ display: {
116
+ product_card: {
117
+ text: {
118
+ en: "Buy 1 Get 1",
119
+ "zh-CN": "买1送1",
120
+ "zh-HK": "買1送1"
121
+ },
122
+ type: "tag"
123
+ }
124
+ }
125
+ }
126
+ },
127
+ conditions: {
128
+ operator: "and",
129
+ rules: [
130
+ {
131
+ type: "operator",
132
+ field: "currentDateTime",
133
+ value: "2023-01-01 00:00:00",
134
+ operator: ">="
135
+ },
136
+ {
137
+ type: "operator",
138
+ field: "currentDateTime",
139
+ value: "2027-01-01 00:00:00",
140
+ operator: "<="
141
+ },
142
+ {
143
+ type: "operator",
144
+ field: "productIdAndVariantId",
145
+ value: [
146
+ {
147
+ product_id: 60736,
148
+ product_variant_id: 0
149
+ // 0 = 匹配任意变体
150
+ },
151
+ {
152
+ product_id: 60737,
153
+ product_variant_id: 0
154
+ // 0 = 匹配任意变体
155
+ }
156
+ ],
157
+ operator: "product_match"
158
+ }
159
+ ],
160
+ actionIds: ["1"]
161
+ },
162
+ actions: [
163
+ {
164
+ id: "1",
165
+ type: "BUY_X_GET_Y_FREE",
166
+ value: {
167
+ buyQuantity: 1,
168
+ // 买X件
169
+ freeQuantity: 1
170
+ // 送Y件
171
+ },
172
+ valueType: "object",
173
+ target: "product",
174
+ priority: 1,
175
+ config: {
176
+ // 可累计(买2送2、买3送3...)
177
+ cumulative: true,
178
+ // 可选的赠品列表
179
+ giftProducts: [
180
+ { product_id: 60757, product_variant_id: 0 },
181
+ { product_id: 38782, product_variant_id: 0 },
182
+ { product_id: 60749, product_variant_id: 27267 }
183
+ ]
184
+ }
185
+ }
186
+ ]
187
+ };
188
+ // Annotate the CommonJS export names for ESM import in node:
189
+ 0 && (module.exports = {
190
+ BUY_X_GET_Y_FREE_STRATEGY,
191
+ X_ITEMS_FOR_Y_PRICE_STRATEGY
192
+ });