@pisell/pisellos 2.2.71 → 2.2.72
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/dist/model/strategy/adapter/index.d.ts +2 -0
- package/dist/model/strategy/adapter/index.js +2 -1
- package/dist/model/strategy/adapter/promotion/adapter.d.ts +66 -0
- package/dist/model/strategy/adapter/promotion/adapter.js +271 -0
- package/dist/model/strategy/adapter/promotion/evaluator.d.ts +213 -0
- package/dist/model/strategy/adapter/promotion/evaluator.js +1206 -0
- package/dist/model/strategy/adapter/promotion/examples.d.ts +138 -0
- package/dist/model/strategy/adapter/promotion/examples.js +166 -0
- package/dist/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/dist/model/strategy/adapter/promotion/index.js +0 -0
- package/dist/model/strategy/adapter/promotion/type.d.ts +447 -0
- package/dist/model/strategy/adapter/promotion/type.js +209 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -1
- package/dist/model/strategy/engine.d.ts +106 -0
- package/dist/model/strategy/engine.js +611 -0
- package/dist/model/strategy/index.d.ts +2 -93
- package/dist/model/strategy/index.js +6 -549
- package/dist/modules/BaseModule.d.ts +4 -0
- package/dist/modules/BaseModule.js +5 -0
- package/dist/modules/Rules/index.d.ts +1 -0
- package/dist/modules/Rules/index.js +28 -16
- package/lib/model/strategy/adapter/index.d.ts +2 -0
- package/lib/model/strategy/adapter/index.js +6 -0
- package/lib/model/strategy/adapter/promotion/adapter.d.ts +66 -0
- package/lib/model/strategy/adapter/promotion/adapter.js +217 -0
- package/lib/model/strategy/adapter/promotion/evaluator.d.ts +213 -0
- package/lib/model/strategy/adapter/promotion/evaluator.js +844 -0
- package/lib/model/strategy/adapter/promotion/examples.d.ts +138 -0
- package/lib/model/strategy/adapter/promotion/examples.js +192 -0
- package/lib/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/lib/model/strategy/adapter/promotion/index.js +0 -0
- package/lib/model/strategy/adapter/promotion/type.d.ts +447 -0
- package/lib/model/strategy/adapter/promotion/type.js +51 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -1
- package/lib/model/strategy/engine.d.ts +106 -0
- package/lib/model/strategy/engine.js +450 -0
- package/lib/model/strategy/index.d.ts +2 -93
- package/lib/model/strategy/index.js +6 -381
- package/lib/modules/BaseModule.d.ts +4 -0
- package/lib/modules/BaseModule.js +3 -0
- package/lib/modules/Rules/index.d.ts +1 -0
- package/lib/modules/Rules/index.js +23 -17
- package/package.json +1 -1
|
@@ -201,6 +201,13 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
201
201
|
productList: hasApplicableDiscount ? result.productList : productList
|
|
202
202
|
};
|
|
203
203
|
}
|
|
204
|
+
}, {
|
|
205
|
+
key: "filterDiscountListByType",
|
|
206
|
+
value: function filterDiscountListByType(discountList, type) {
|
|
207
|
+
return (discountList || []).filter(function (item) {
|
|
208
|
+
return item.type === type || item.tag === type;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
204
211
|
}, {
|
|
205
212
|
key: "calcDiscount",
|
|
206
213
|
value: function calcDiscount(_ref2, options) {
|
|
@@ -953,6 +960,9 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
953
960
|
}) && (!((_product$discount_lis5 = product.discount_list) !== null && _product$discount_lis5 !== void 0 && _product$discount_lis5.length) || ((_product11 = product) === null || _product11 === void 0 || (_product11 = _product11.discount_list) === null || _product11 === void 0 || (_product11$every = _product11.every) === null || _product11$every === void 0 ? void 0 : _product11$every.call(_product11, function (item) {
|
|
954
961
|
return item.type === 'product';
|
|
955
962
|
})));
|
|
963
|
+
if (product.inPromotion) {
|
|
964
|
+
isManualDiscount = false;
|
|
965
|
+
}
|
|
956
966
|
} else {
|
|
957
967
|
// bundle子商品:判断父主商品是否手动折扣
|
|
958
968
|
var parentProduct = flatItem.parentProduct;
|
|
@@ -1048,25 +1058,27 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
1048
1058
|
}),
|
|
1049
1059
|
price: product.price
|
|
1050
1060
|
}), {}, {
|
|
1051
|
-
discount_list:
|
|
1061
|
+
discount_list: _this3.filterDiscountListByType(product.discount_list, 'promotion')
|
|
1052
1062
|
}))]);
|
|
1053
1063
|
} else {
|
|
1064
|
+
var _ref7, _product$_promotion$f, _product12;
|
|
1054
1065
|
processedProductsMap.set(product._id, [_this3.hooks.setProduct(originProduct, _objectSpread(_objectSpread({}, isManualDiscount ? {
|
|
1055
1066
|
price: product.price,
|
|
1056
1067
|
main_product_selling_price: product.price
|
|
1057
1068
|
} : {
|
|
1058
1069
|
_id: product._id.split('___')[0] + '___' + index,
|
|
1059
|
-
total: product.origin_total || product.total,
|
|
1070
|
+
total: product.inPromotion ? (_ref7 = (_product$_promotion$f = (_product12 = product) === null || _product12 === void 0 || (_product12 = _product12._promotion) === null || _product12 === void 0 ? void 0 : _product12.finalPrice) !== null && _product$_promotion$f !== void 0 ? _product$_promotion$f : product.origin_total) !== null && _ref7 !== void 0 ? _ref7 : product.total : product.origin_total || product.total,
|
|
1060
1071
|
price: product.price,
|
|
1061
1072
|
main_product_selling_price: product.price
|
|
1062
1073
|
}), {}, {
|
|
1063
|
-
discount_list:
|
|
1074
|
+
discount_list: _this3.filterDiscountListByType(product.discount_list, 'promotion')
|
|
1064
1075
|
}))]);
|
|
1065
1076
|
}
|
|
1066
1077
|
} else {
|
|
1078
|
+
var _flatItem$bundleItem8;
|
|
1067
1079
|
// bundle子商品:保存到扁平化Map
|
|
1068
1080
|
processedFlatItemsMap.set(flatItem._id, [_objectSpread(_objectSpread({}, flatItem), {}, {
|
|
1069
|
-
discount_list: [],
|
|
1081
|
+
discount_list: _this3.filterDiscountListByType(((_flatItem$bundleItem8 = flatItem.bundleItem) === null || _flatItem$bundleItem8 === void 0 ? void 0 : _flatItem$bundleItem8.discount_list) || [], 'promotion'),
|
|
1070
1082
|
price: isManualDiscount ? flatItem.bundleItem.price : flatItem.bundleItem.original_price,
|
|
1071
1083
|
processed: true
|
|
1072
1084
|
})]);
|
|
@@ -1099,14 +1111,14 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
1099
1111
|
// 主商品:保持原有逻辑
|
|
1100
1112
|
if (splitCount < totalQuantity && isNeedSplit) {
|
|
1101
1113
|
arr.push(_this3.hooks.setProduct(originProduct, {
|
|
1102
|
-
discount_list:
|
|
1114
|
+
discount_list: _this3.filterDiscountListByType(product.discount_list, 'promotion'),
|
|
1103
1115
|
quantity: totalQuantity - splitCount,
|
|
1104
1116
|
_id: product._id.split('___')[0],
|
|
1105
1117
|
total: product.origin_total || product.total
|
|
1106
1118
|
}));
|
|
1107
1119
|
}
|
|
1108
1120
|
for (var i = 0; i < splitCount; i++) {
|
|
1109
|
-
var
|
|
1121
|
+
var _originProduct, _selectedDiscount$met, _selectedDiscount$met2;
|
|
1110
1122
|
// 如果用过折扣卡,也就不存在拆分的情况了,这里直接使用上面计算出来的折扣卡
|
|
1111
1123
|
var _selectedDiscount = selectedDiscountCard || applicableDiscounts[i];
|
|
1112
1124
|
// 标记优惠券为已使用
|
|
@@ -1123,7 +1135,7 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
1123
1135
|
// 优先从 origin_total拿,可能会拿不到(比如用户端预约在没有配置 original_price 的情况下)
|
|
1124
1136
|
var productOriginTotal = product.origin_total || product.total || 0;
|
|
1125
1137
|
// 如果当前 product 有 discount_list,则先从 origin_total 拿
|
|
1126
|
-
if ((
|
|
1138
|
+
if (_this3.filterDiscountListByType(product.discount_list, 'promotion').length && product.origin_total) {
|
|
1127
1139
|
productOriginTotal = product.origin_total;
|
|
1128
1140
|
}
|
|
1129
1141
|
// 如果originProduct?._productInit?.original_price为 0,product.origin_total可能为空,此时取 product.total
|
|
@@ -1205,7 +1217,7 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
1205
1217
|
}));
|
|
1206
1218
|
} else {
|
|
1207
1219
|
arr.push(_this3.hooks.setProduct(originProduct, {
|
|
1208
|
-
discount_list: [discountDetail],
|
|
1220
|
+
discount_list: _this3.filterDiscountListByType(product.discount_list, 'promotion').concat([discountDetail]),
|
|
1209
1221
|
_id: product._id.split('___')[0] + '___' + _selectedDiscount.id + index,
|
|
1210
1222
|
price: _selectedDiscount.tag === 'good_pass' ? 0 : product.price,
|
|
1211
1223
|
quantity: isNeedSplit ? 1 : product.quantity,
|
|
@@ -1376,7 +1388,7 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
1376
1388
|
var getDefaultProduct = function getDefaultProduct() {
|
|
1377
1389
|
if (product.isClient) {
|
|
1378
1390
|
return _this3.hooks.setProduct(originProduct, {
|
|
1379
|
-
discount_list:
|
|
1391
|
+
discount_list: _this3.filterDiscountListByType(product.discount_list, 'promotion'),
|
|
1380
1392
|
price: product.price,
|
|
1381
1393
|
origin_total: getProductOriginTotalPrice({
|
|
1382
1394
|
product: {
|
|
@@ -1397,7 +1409,7 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
1397
1409
|
});
|
|
1398
1410
|
} else {
|
|
1399
1411
|
return _this3.hooks.setProduct(originProduct, {
|
|
1400
|
-
discount_list:
|
|
1412
|
+
discount_list: _this3.filterDiscountListByType(product.discount_list, 'promotion'),
|
|
1401
1413
|
total: product.total,
|
|
1402
1414
|
origin_total: product.origin_total,
|
|
1403
1415
|
price: product.price
|
|
@@ -1858,10 +1870,10 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
1858
1870
|
return true; // 单独购买时可用
|
|
1859
1871
|
}
|
|
1860
1872
|
if (isBundleItem) {
|
|
1861
|
-
var _flatItem$
|
|
1873
|
+
var _flatItem$bundleItem9, _flatItem$bundleItem10;
|
|
1862
1874
|
// 套餐中购买时,判断是否为原价
|
|
1863
|
-
var priceType = (_flatItem$
|
|
1864
|
-
var priceTypeExt = (_flatItem$
|
|
1875
|
+
var priceType = (_flatItem$bundleItem9 = flatItem.bundleItem) === null || _flatItem$bundleItem9 === void 0 ? void 0 : _flatItem$bundleItem9.price_type;
|
|
1876
|
+
var priceTypeExt = (_flatItem$bundleItem10 = flatItem.bundleItem) === null || _flatItem$bundleItem10 === void 0 ? void 0 : _flatItem$bundleItem10.price_type_ext;
|
|
1865
1877
|
// original_price 对应:
|
|
1866
1878
|
// 1. price_type: "markup" && price_type_ext: "product_price"
|
|
1867
1879
|
// markup_price 对应:
|
|
@@ -1899,10 +1911,10 @@ export var RulesModule = /*#__PURE__*/function (_BaseModule) {
|
|
|
1899
1911
|
return false; // 单独购买时不可用
|
|
1900
1912
|
}
|
|
1901
1913
|
if (isBundleItem) {
|
|
1902
|
-
var _flatItem$
|
|
1914
|
+
var _flatItem$bundleItem11, _flatItem$bundleItem12;
|
|
1903
1915
|
// 套餐中购买时,判断是否为原价
|
|
1904
|
-
var _priceType = (_flatItem$
|
|
1905
|
-
var _priceTypeExt = (_flatItem$
|
|
1916
|
+
var _priceType = (_flatItem$bundleItem11 = flatItem.bundleItem) === null || _flatItem$bundleItem11 === void 0 ? void 0 : _flatItem$bundleItem11.price_type;
|
|
1917
|
+
var _priceTypeExt = (_flatItem$bundleItem12 = flatItem.bundleItem) === null || _flatItem$bundleItem12 === void 0 ? void 0 : _flatItem$bundleItem12.price_type_ext;
|
|
1906
1918
|
|
|
1907
1919
|
// original_price 对应:
|
|
1908
1920
|
// 1. price_type: "markup" && price_type_ext: "product_price"
|
|
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/model/strategy/adapter/index.ts
|
|
31
31
|
var adapter_exports = {};
|
|
32
32
|
__export(adapter_exports, {
|
|
33
|
+
PromotionAdapter: () => import_adapter.PromotionAdapter,
|
|
34
|
+
PromotionEvaluator: () => import_evaluator2.PromotionEvaluator,
|
|
33
35
|
WalletPassAdapter: () => import_walletPass.default,
|
|
34
36
|
WalletPassEvaluator: () => import_evaluator.WalletPassEvaluator
|
|
35
37
|
});
|
|
@@ -37,8 +39,12 @@ module.exports = __toCommonJS(adapter_exports);
|
|
|
37
39
|
__reExport(adapter_exports, require("./type"), module.exports);
|
|
38
40
|
var import_walletPass = __toESM(require("./walletPass"));
|
|
39
41
|
var import_evaluator = require("./walletPass/evaluator");
|
|
42
|
+
var import_evaluator2 = require("./promotion/evaluator");
|
|
43
|
+
var import_adapter = require("./promotion/adapter");
|
|
40
44
|
// Annotate the CommonJS export names for ESM import in node:
|
|
41
45
|
0 && (module.exports = {
|
|
46
|
+
PromotionAdapter,
|
|
47
|
+
PromotionEvaluator,
|
|
42
48
|
WalletPassAdapter,
|
|
43
49
|
WalletPassEvaluator,
|
|
44
50
|
...require("./type")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { EvaluationResult, RuntimeContext } from '../../type';
|
|
2
|
+
import type { BusinessAdapter } from '../type';
|
|
3
|
+
import type { PromotionBusinessData, PromotionTransformResult } from './type';
|
|
4
|
+
/**
|
|
5
|
+
* Promotion 适配器
|
|
6
|
+
*
|
|
7
|
+
* 用于将促销活动业务数据转换为策略引擎可识别的格式
|
|
8
|
+
* 策略引擎只负责匹配,具体的优惠计算由业务层完成
|
|
9
|
+
*/
|
|
10
|
+
export declare class PromotionAdapter implements BusinessAdapter {
|
|
11
|
+
name: string;
|
|
12
|
+
version: string;
|
|
13
|
+
/**
|
|
14
|
+
* 准备运行时上下文
|
|
15
|
+
*
|
|
16
|
+
* 将业务数据转换为策略引擎可识别的 RuntimeContext
|
|
17
|
+
*/
|
|
18
|
+
prepareContext(businessData: PromotionBusinessData): RuntimeContext;
|
|
19
|
+
/**
|
|
20
|
+
* 转换执行结果
|
|
21
|
+
*
|
|
22
|
+
* 将策略引擎的通用结果转换为业务层需要的格式
|
|
23
|
+
* 主要是整理 matchedActions,让业务层更容易使用
|
|
24
|
+
*/
|
|
25
|
+
transformResult(result: EvaluationResult, businessData?: PromotionBusinessData): PromotionTransformResult;
|
|
26
|
+
/**
|
|
27
|
+
* 格式化配置
|
|
28
|
+
*/
|
|
29
|
+
formatConfig(result: EvaluationResult, businessData?: PromotionBusinessData): {
|
|
30
|
+
result: EvaluationResult;
|
|
31
|
+
businessData?: PromotionBusinessData;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* 格式化日期时间
|
|
35
|
+
*/
|
|
36
|
+
private formatDateTime;
|
|
37
|
+
/**
|
|
38
|
+
* 解析 Action 详情
|
|
39
|
+
*
|
|
40
|
+
* 将 matchedAction 转换为更易用的结构
|
|
41
|
+
*/
|
|
42
|
+
private parseActionDetail;
|
|
43
|
+
/**
|
|
44
|
+
* 解析 X件Y元 Action
|
|
45
|
+
*/
|
|
46
|
+
private parseXItemsForYPriceAction;
|
|
47
|
+
/**
|
|
48
|
+
* 解析 买X送Y Action
|
|
49
|
+
*/
|
|
50
|
+
private parseBuyXGetYFreeAction;
|
|
51
|
+
/**
|
|
52
|
+
* 获取适用的商品列表
|
|
53
|
+
*
|
|
54
|
+
* 从购物车商品中筛选出符合策略条件的商品
|
|
55
|
+
*/
|
|
56
|
+
private getApplicableProducts;
|
|
57
|
+
/**
|
|
58
|
+
* 查找商品匹配规则
|
|
59
|
+
*/
|
|
60
|
+
private findProductMatchRule;
|
|
61
|
+
/**
|
|
62
|
+
* 检查商品是否匹配
|
|
63
|
+
*/
|
|
64
|
+
private isProductMatch;
|
|
65
|
+
}
|
|
66
|
+
export default PromotionAdapter;
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import type { StrategyConfig } from '../../type';
|
|
2
|
+
import { type PromotionProduct, type PromotionActionDetail, type PromotionEvaluatorInput, type ProductPromotionResult, type ApplicablePromotion, type CartEvaluationResult, type CartWithPricingResult } from './type';
|
|
3
|
+
/**
|
|
4
|
+
* Promotion 评估器
|
|
5
|
+
*
|
|
6
|
+
* 用于评估商品适用的促销活动
|
|
7
|
+
* 封装了 StrategyEngine 和 PromotionAdapter 的调用逻辑
|
|
8
|
+
*/
|
|
9
|
+
export declare class PromotionEvaluator {
|
|
10
|
+
private engine;
|
|
11
|
+
private adapter;
|
|
12
|
+
private strategyConfigs;
|
|
13
|
+
private locale;
|
|
14
|
+
private locales;
|
|
15
|
+
constructor();
|
|
16
|
+
/**
|
|
17
|
+
* 设置策略配置列表
|
|
18
|
+
*/
|
|
19
|
+
setStrategyConfigs(strategyConfigs: StrategyConfig[]): void;
|
|
20
|
+
/**
|
|
21
|
+
* 获取策略配置列表
|
|
22
|
+
*/
|
|
23
|
+
getStrategyConfigs(): StrategyConfig[];
|
|
24
|
+
/**
|
|
25
|
+
* 添加策略配置
|
|
26
|
+
*/
|
|
27
|
+
addStrategyConfig(strategyConfig: StrategyConfig): void;
|
|
28
|
+
/**
|
|
29
|
+
* 设置语言
|
|
30
|
+
*/
|
|
31
|
+
setLocale(locale: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* 设置自定义多语言文案
|
|
34
|
+
*/
|
|
35
|
+
setLocales(locales: Record<string, Record<string, string>>): void;
|
|
36
|
+
/**
|
|
37
|
+
* 获取多语言文案
|
|
38
|
+
*/
|
|
39
|
+
getText(key: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* 获取多语言名称
|
|
42
|
+
*/
|
|
43
|
+
getLocalizedName(name: string | Record<string, string>): string;
|
|
44
|
+
/**
|
|
45
|
+
* 评估商品列表
|
|
46
|
+
*
|
|
47
|
+
* 判断每个商品适用哪些促销活动
|
|
48
|
+
* 支持主商品和 bundle 子商品的匹配
|
|
49
|
+
*
|
|
50
|
+
* @param input 评估输入
|
|
51
|
+
* @returns 每个商品的促销评估结果
|
|
52
|
+
*/
|
|
53
|
+
evaluateProducts(input: PromotionEvaluatorInput): ProductPromotionResult[];
|
|
54
|
+
/**
|
|
55
|
+
* 评估购物车
|
|
56
|
+
*
|
|
57
|
+
* 返回所有适用的促销及按促销分组的商品
|
|
58
|
+
* 支持 bundle 子商品的数量计算(主商品数量 × 子商品数量)
|
|
59
|
+
*
|
|
60
|
+
* @param input 评估输入
|
|
61
|
+
* @returns 购物车评估结果
|
|
62
|
+
*/
|
|
63
|
+
evaluateCart(input: PromotionEvaluatorInput): CartEvaluationResult;
|
|
64
|
+
/**
|
|
65
|
+
* 评估购物车并计算定价
|
|
66
|
+
*
|
|
67
|
+
* 返回处理后的商品数组(包含拆分、finalPrice)和赠品信息
|
|
68
|
+
* - 对于 X_ITEMS_FOR_Y_PRICE:按原价比例均摊价格,优先使用高价商品
|
|
69
|
+
* - 对于 BUY_X_GET_Y_FREE:计算赠品数量和可选赠品列表
|
|
70
|
+
*
|
|
71
|
+
* @param input 评估输入
|
|
72
|
+
* @returns 带定价的购物车评估结果
|
|
73
|
+
*/
|
|
74
|
+
evaluateCartWithPricing(input: PromotionEvaluatorInput): CartWithPricingResult;
|
|
75
|
+
/**
|
|
76
|
+
* 获取商品适用的促销列表
|
|
77
|
+
*
|
|
78
|
+
* 简化方法,用于商品卡片展示
|
|
79
|
+
*
|
|
80
|
+
* @param product 商品
|
|
81
|
+
* @param strategyConfigs 策略配置(可选)
|
|
82
|
+
* @returns 适用的促销列表
|
|
83
|
+
*/
|
|
84
|
+
getProductPromotions(product: PromotionProduct, strategyConfigs?: StrategyConfig[]): ApplicablePromotion[];
|
|
85
|
+
/**
|
|
86
|
+
* 获取商品的促销标签
|
|
87
|
+
*
|
|
88
|
+
* 用于商品卡片展示
|
|
89
|
+
*
|
|
90
|
+
* @param product 商品
|
|
91
|
+
* @param strategyConfigs 策略配置(可选)
|
|
92
|
+
* @returns 促销标签列表
|
|
93
|
+
*/
|
|
94
|
+
getProductPromotionTags(product: PromotionProduct, strategyConfigs?: StrategyConfig[]): Array<{
|
|
95
|
+
text: string;
|
|
96
|
+
type: string;
|
|
97
|
+
strategyId: string;
|
|
98
|
+
}>;
|
|
99
|
+
/**
|
|
100
|
+
* 查找商品适用的策略配置
|
|
101
|
+
*
|
|
102
|
+
* @param product 商品
|
|
103
|
+
* @param strategyConfigs 策略配置列表(可选)
|
|
104
|
+
* @returns 适用的策略配置列表
|
|
105
|
+
*/
|
|
106
|
+
findApplicableStrategies(product: PromotionProduct, strategyConfigs?: StrategyConfig[]): StrategyConfig[];
|
|
107
|
+
/**
|
|
108
|
+
* 批量获取商品列表的适用策略信息
|
|
109
|
+
*
|
|
110
|
+
* 用于给商品列表追加策略标签信息
|
|
111
|
+
* 只检查商品 ID 匹配和策略时间范围
|
|
112
|
+
*
|
|
113
|
+
* @param input 评估输入(商品列表)
|
|
114
|
+
* @param matchVariant 是否需要匹配 product_variant_id,默认 true(严格匹配),false 时只匹配 product_id
|
|
115
|
+
* @returns 每个商品的适用策略完整数据
|
|
116
|
+
*/
|
|
117
|
+
getProductsApplicableStrategies(input: PromotionEvaluatorInput, matchVariant?: boolean): Array<{
|
|
118
|
+
product: PromotionProduct;
|
|
119
|
+
applicableStrategies: Array<{
|
|
120
|
+
strategyId: string;
|
|
121
|
+
strategyName: string | Record<string, string>;
|
|
122
|
+
strategyMetadata: any;
|
|
123
|
+
actionType: string;
|
|
124
|
+
actionDetail: PromotionActionDetail;
|
|
125
|
+
display?: {
|
|
126
|
+
text: string | Record<string, string>;
|
|
127
|
+
type: string;
|
|
128
|
+
};
|
|
129
|
+
strategyConfig: StrategyConfig;
|
|
130
|
+
/** 满足促销所需的商品数量 */
|
|
131
|
+
requiredQuantity: number;
|
|
132
|
+
/** 可参与此促销的商品列表 */
|
|
133
|
+
eligibleProducts: Array<{
|
|
134
|
+
product_id: number;
|
|
135
|
+
product_variant_id: number;
|
|
136
|
+
}>;
|
|
137
|
+
}>;
|
|
138
|
+
hasApplicableStrategy: boolean;
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* 获取促销所需数量和可参与商品列表
|
|
142
|
+
*
|
|
143
|
+
* @param actionType 促销类型
|
|
144
|
+
* @param actionDetail 促销详情
|
|
145
|
+
* @param config 策略配置
|
|
146
|
+
* @returns { requiredQuantity, eligibleProducts }
|
|
147
|
+
*/
|
|
148
|
+
private getPromotionRequirements;
|
|
149
|
+
/**
|
|
150
|
+
* 按优先级排序促销组
|
|
151
|
+
* 优先级从 strategyConfig.actions[0].priority 获取,数值越小优先级越高
|
|
152
|
+
*/
|
|
153
|
+
private sortPromotionGroupsByPriority;
|
|
154
|
+
/**
|
|
155
|
+
* 处理 X件Y元 促销
|
|
156
|
+
*
|
|
157
|
+
* 1. 展开商品为单件列表,按价格从高到低排序
|
|
158
|
+
* 2. 计算可凑成的组数
|
|
159
|
+
* 3. 按原价比例均摊价格
|
|
160
|
+
* 4. 拆分商品(部分参与促销、部分原价)
|
|
161
|
+
*
|
|
162
|
+
* 注意:每个商品按 id 独立处理,不会合并不同 id 的商品
|
|
163
|
+
*/
|
|
164
|
+
private processXItemsForYPrice;
|
|
165
|
+
/**
|
|
166
|
+
* 处理 买X送Y 促销
|
|
167
|
+
*
|
|
168
|
+
* 计算赠品数量,商品本身价格不变
|
|
169
|
+
* 支持 bundle 子商品的数量计算(主商品数量 × 子商品数量)
|
|
170
|
+
*/
|
|
171
|
+
private processBuyXGetYFree;
|
|
172
|
+
/**
|
|
173
|
+
* 获取未参与任何促销的商品
|
|
174
|
+
*/
|
|
175
|
+
private getRemainingProducts;
|
|
176
|
+
/**
|
|
177
|
+
* 价格统一保留两位小数
|
|
178
|
+
*/
|
|
179
|
+
private formatPrice;
|
|
180
|
+
/**
|
|
181
|
+
* 获取商品唯一标识 key
|
|
182
|
+
* 使用商品的 id 作为唯一标识,不同 id 的商品不会被合并
|
|
183
|
+
*/
|
|
184
|
+
private getProductKey;
|
|
185
|
+
/**
|
|
186
|
+
* 生成随机ID
|
|
187
|
+
*/
|
|
188
|
+
private generateRandomId;
|
|
189
|
+
/**
|
|
190
|
+
* 商品匹配结果信息
|
|
191
|
+
*/
|
|
192
|
+
private getProductMatchInfo;
|
|
193
|
+
/**
|
|
194
|
+
* 检查商品是否在策略的适用范围内
|
|
195
|
+
*/
|
|
196
|
+
private isProductInStrategy;
|
|
197
|
+
/**
|
|
198
|
+
* 查找商品匹配规则
|
|
199
|
+
*/
|
|
200
|
+
private findProductMatchRule;
|
|
201
|
+
/**
|
|
202
|
+
* 检查商品ID是否匹配配置列表
|
|
203
|
+
*/
|
|
204
|
+
private isProductIdMatch;
|
|
205
|
+
/**
|
|
206
|
+
* 检查商品是否匹配配置(兼容旧方法)
|
|
207
|
+
*/
|
|
208
|
+
private isProductMatch;
|
|
209
|
+
/**
|
|
210
|
+
* 获取展示配置
|
|
211
|
+
*/
|
|
212
|
+
private getDisplayConfig;
|
|
213
|
+
}
|