@pisell/pisellos
Version:
一个可扩展的前端模块化SDK框架,支持插件系统
1,279 lines (1,192 loc) • 52.5 kB
JavaScript
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
import Decimal from 'decimal.js';
import { StrategyEngine } from "../../engine";
import { PromotionAdapter } from "./adapter";
import { PROMOTION_ACTION_TYPES } from "./type";
// ============================================
// 类型定义
// ============================================
// ============================================
// 多语言定义
// ============================================
var defaultLocales = {
en: {
no_applicable_promotion: 'No applicable promotion',
promotion_not_in_time_range: 'Promotion not in valid time range',
product_not_in_promotion: 'Product not in promotion scope'
},
'zh-CN': {
no_applicable_promotion: '无适用的促销活动',
promotion_not_in_time_range: '促销活动不在有效时间范围内',
product_not_in_promotion: '商品不在促销范围内'
},
'zh-HK': {
no_applicable_promotion: '無適用的促銷活動',
promotion_not_in_time_range: '促銷活動不在有效時間範圍內',
product_not_in_promotion: '商品不在促銷範圍內'
}
};
// ============================================
// PromotionEvaluator 实现
// ============================================
/**
* Promotion 评估器
*
* 用于评估商品适用的促销活动
* 封装了 StrategyEngine 和 PromotionAdapter 的调用逻辑
*/
export var PromotionEvaluator = /*#__PURE__*/function () {
function PromotionEvaluator() {
_classCallCheck(this, PromotionEvaluator);
_defineProperty(this, "engine", void 0);
_defineProperty(this, "adapter", void 0);
_defineProperty(this, "strategyConfigs", []);
_defineProperty(this, "locale", 'en');
_defineProperty(this, "locales", defaultLocales);
this.engine = new StrategyEngine({
debug: false,
enableTrace: false
});
this.adapter = new PromotionAdapter();
}
// ============================================
// 配置管理
// ============================================
/**
* 设置策略配置列表
*/
_createClass(PromotionEvaluator, [{
key: "setStrategyConfigs",
value: function setStrategyConfigs(strategyConfigs) {
var newStrategyConfigs = strategyConfigs.filter(function (item) {
return item.metadata.type === 'promotion';
});
this.strategyConfigs = newStrategyConfigs;
}
/**
* 获取策略配置列表
*/
}, {
key: "getStrategyConfigs",
value: function getStrategyConfigs() {
return this.strategyConfigs;
}
/**
* 添加策略配置
*/
}, {
key: "addStrategyConfig",
value: function addStrategyConfig(strategyConfig) {
this.strategyConfigs.push(strategyConfig);
}
/**
* 设置语言
*/
}, {
key: "setLocale",
value: function setLocale(locale) {
this.locale = locale;
}
/**
* 设置自定义多语言文案
*/
}, {
key: "setLocales",
value: function setLocales(locales) {
this.locales = _objectSpread(_objectSpread({}, defaultLocales), locales);
}
/**
* 获取多语言文案
*/
}, {
key: "getText",
value: function getText(key) {
var _this$locales;
return ((_this$locales = this.locales) === null || _this$locales === void 0 || (_this$locales = _this$locales[this.locale]) === null || _this$locales === void 0 ? void 0 : _this$locales[key]) || key;
}
/**
* 获取多语言名称
*/
}, {
key: "getLocalizedName",
value: function getLocalizedName(name) {
if (typeof name === 'string') {
return name;
}
return name[this.locale] || name['en'] || Object.values(name)[0] || '';
}
// ============================================
// 核心评估方法
// ============================================
/**
* 评估商品列表
*
* 判断每个商品适用哪些促销活动
* 支持主商品和 bundle 子商品的匹配
*
* @param input 评估输入
* @returns 每个商品的促销评估结果
*/
}, {
key: "evaluateProducts",
value: function evaluateProducts(input) {
var products = input.products,
strategyConfigs = input.strategyConfigs,
channel = input.channel;
var configs = strategyConfigs || this.strategyConfigs;
var results = [];
var _iterator = _createForOfIteratorHelper(products),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var product = _step.value;
var applicablePromotions = [];
// 遍历所有策略,检查此商品是否适用
var _iterator2 = _createForOfIteratorHelper(configs),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var config = _step2.value;
// 1. 先用 getProductMatchInfo 检查商品是否在策略的适用范围内(包含 bundle 子商品检查)
var matchInfo = this.getProductMatchInfo(product, config);
if (!matchInfo.isMatch) {
// 商品不在策略适用范围内,跳过
continue;
}
// 2. 准备上下文(如果是 bundle 子商品匹配,需要用 bundle 子商品的 ID)
var contextProduct = product;
if (matchInfo.matchedBundleIndex !== undefined && product.bundle) {
var bundleItem = product.bundle[matchInfo.matchedBundleIndex];
// 创建一个临时的"虚拟商品"用于策略引擎评估
// 这样策略引擎的 product_match 条件能正确通过
contextProduct = _objectSpread(_objectSpread({}, product), {}, {
product_id: bundleItem._bundle_product_id,
product_variant_id: bundleItem.product_variant_id
});
}
var context = this.adapter.prepareContext({
products: [contextProduct],
currentProduct: contextProduct,
channel: channel
});
// 3. 评估策略(检查其他条件如时间范围等)
var result = this.engine.evaluate(config, context);
if (result.applicable && result.matchedActions.length > 0) {
var action = result.matchedActions[0];
var transformedResult = this.adapter.transformResult(result, {
products: [product],
currentProduct: product,
channel: channel
});
if (transformedResult.actionDetail) {
applicablePromotions.push({
strategyId: config.metadata.id,
strategyName: config.metadata.name,
actionType: action.type,
actionDetail: transformedResult.actionDetail,
display: this.getDisplayConfig(config),
strategyConfig: config,
// 记录匹配的是哪个 bundle 子商品
matchedBundleIndex: matchInfo.matchedBundleIndex
});
}
}
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
results.push({
product: product,
applicablePromotions: applicablePromotions,
hasPromotion: applicablePromotions.length > 0
});
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return results;
}
/**
* 评估购物车
*
* 返回所有适用的促销及按促销分组的商品
* 支持 bundle 子商品的数量计算(主商品数量 × 子商品数量)
*
* @param input 评估输入
* @returns 购物车评估结果
*/
}, {
key: "evaluateCart",
value: function evaluateCart(input) {
var products = input.products,
strategyConfigs = input.strategyConfigs,
channel = input.channel;
var configs = strategyConfigs || this.strategyConfigs;
// 1. 先获取每个商品的促销结果
var productResults = this.evaluateProducts(input);
// 2. 收集所有适用的促销(去重)
var promotionMap = new Map();
var _iterator3 = _createForOfIteratorHelper(productResults),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var productResult = _step3.value;
var _iterator4 = _createForOfIteratorHelper(productResult.applicablePromotions),
_step4;
try {
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var promo = _step4.value;
var existing = promotionMap.get(promo.strategyId);
var product = productResult.product;
// 计算促销数量:如果是 bundle 子商品匹配,数量 = 主商品数量 × 子商品数量
var promoQuantity = product.quantity;
if (promo.matchedBundleIndex !== undefined && product.bundle) {
var bundleItem = product.bundle[promo.matchedBundleIndex];
promoQuantity = product.quantity * (bundleItem.quantity || 1);
}
if (existing) {
// 已存在,添加商品到列表
existing.applicableProducts.push(product);
existing.totalQuantity += promoQuantity;
existing.totalAmount += product.price * product.quantity;
// 记录每个商品的 matchedBundleIndex
if (!existing.productMatchedBundleIndexMap) {
existing.productMatchedBundleIndexMap = new Map();
}
existing.productMatchedBundleIndexMap.set(product.id, promo.matchedBundleIndex);
} else {
// 新促销
var productMatchedBundleIndexMap = new Map();
productMatchedBundleIndexMap.set(product.id, promo.matchedBundleIndex);
promotionMap.set(promo.strategyId, _objectSpread(_objectSpread({}, promo), {}, {
applicableProducts: [product],
totalQuantity: promoQuantity,
totalAmount: product.price * product.quantity,
productMatchedBundleIndexMap: productMatchedBundleIndexMap
}));
}
}
} catch (err) {
_iterator4.e(err);
} finally {
_iterator4.f();
}
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
var applicablePromotions = Array.from(promotionMap.values());
// 3. 按促销分组
var promotionGroups = applicablePromotions.map(function (promo) {
var _promo$strategyConfig;
return {
strategyId: promo.strategyId,
strategyName: promo.strategyName,
strategyMetadata: (_promo$strategyConfig = promo.strategyConfig) === null || _promo$strategyConfig === void 0 ? void 0 : _promo$strategyConfig.metadata,
strategyConfig: promo.strategyConfig,
actionType: promo.actionType,
actionDetail: promo.actionDetail,
products: promo.applicableProducts,
totalQuantity: promo.totalQuantity,
totalAmount: promo.totalAmount,
// 传递 productMatchedBundleIndexMap 到 group
productMatchedBundleIndexMap: promo.productMatchedBundleIndexMap
};
});
return {
productResults: productResults,
applicablePromotions: applicablePromotions,
promotionGroups: promotionGroups
};
}
/**
* 评估购物车并计算定价
*
* 返回处理后的商品数组(包含拆分、finalPrice)和赠品信息
* - 对于 X_ITEMS_FOR_Y_PRICE:按原价比例均摊价格,优先使用高价商品
* - 对于 BUY_X_GET_Y_FREE:计算赠品数量和可选赠品列表
*
* @param input 评估输入
* @returns 带定价的购物车评估结果
*/
}, {
key: "evaluateCartWithPricing",
value: function evaluateCartWithPricing(input) {
var products = input.products;
// 1. 先调用 evaluateCart 获取分组结果
var cartResult = this.evaluateCart(input);
// 2. 按 action priority 排序促销组(从 strategyConfig 获取 priority)
var sortedGroups = this.sortPromotionGroupsByPriority(cartResult.promotionGroups, cartResult.applicablePromotions);
// 3. 追踪已处理的商品(避免重复处理)
// key: `${product_id}_${product_variant_id}`
var processedQuantityMap = new Map();
// 4. 处理每个促销组,收集结果
var pricedProducts = [];
var gifts = [];
var totalOriginalAmount = new Decimal(0);
var totalFinalAmount = new Decimal(0);
var _iterator5 = _createForOfIteratorHelper(sortedGroups),
_step5;
try {
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
var group = _step5.value;
var actionType = group.actionType;
// 直接使用 evaluateCart 阶段计算好的 matchedBundleIndexMap
var matchedBundleIndexMap = group.productMatchedBundleIndexMap || new Map();
if (actionType === PROMOTION_ACTION_TYPES.X_ITEMS_FOR_Y_PRICE) {
// X件Y元处理
var result = this.processXItemsForYPrice(group, processedQuantityMap, matchedBundleIndexMap);
pricedProducts.push.apply(pricedProducts, _toConsumableArray(result.products));
totalOriginalAmount = totalOriginalAmount.plus(result.originalAmount);
totalFinalAmount = totalFinalAmount.plus(result.finalAmount);
} else if (actionType === PROMOTION_ACTION_TYPES.BUY_X_GET_Y_FREE) {
// 买X送Y处理
var _result = this.processBuyXGetYFree(group, processedQuantityMap, matchedBundleIndexMap);
pricedProducts.push.apply(pricedProducts, _toConsumableArray(_result.products));
totalOriginalAmount = totalOriginalAmount.plus(_result.originalAmount);
totalFinalAmount = totalFinalAmount.plus(_result.finalAmount);
// 添加赠品信息
if (_result.giftInfo) {
gifts.push(_result.giftInfo);
}
}
}
// 5. 处理未参与任何促销的商品
} catch (err) {
_iterator5.e(err);
} finally {
_iterator5.f();
}
var remainingProducts = this.getRemainingProducts(products, processedQuantityMap);
var _iterator6 = _createForOfIteratorHelper(remainingProducts),
_step6;
try {
for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
var product = _step6.value;
var amount = new Decimal(product.price).mul(product.quantity);
totalOriginalAmount = totalOriginalAmount.plus(amount);
totalFinalAmount = totalFinalAmount.plus(amount);
}
} catch (err) {
_iterator6.e(err);
} finally {
_iterator6.f();
}
pricedProducts.push.apply(pricedProducts, _toConsumableArray(remainingProducts));
// 6. 计算总优惠金额
var totalDiscount = totalOriginalAmount.minus(totalFinalAmount);
return {
products: pricedProducts,
gifts: gifts,
totalDiscount: Decimal.max(0, totalDiscount).toNumber()
};
}
/**
* 获取商品适用的促销列表
*
* 简化方法,用于商品卡片展示
*
* @param product 商品
* @param strategyConfigs 策略配置(可选)
* @returns 适用的促销列表
*/
}, {
key: "getProductPromotions",
value: function getProductPromotions(product, strategyConfigs) {
var _results$;
var results = this.evaluateProducts({
products: [product],
strategyConfigs: strategyConfigs
});
return ((_results$ = results[0]) === null || _results$ === void 0 ? void 0 : _results$.applicablePromotions) || [];
}
/**
* 获取商品的促销标签
*
* 用于商品卡片展示
*
* @param product 商品
* @param strategyConfigs 策略配置(可选)
* @returns 促销标签列表
*/
}, {
key: "getProductPromotionTags",
value: function getProductPromotionTags(product, strategyConfigs) {
var _this = this;
var promotions = this.getProductPromotions(product, strategyConfigs);
return promotions.filter(function (promo) {
return promo.display;
}).map(function (promo) {
return {
text: _this.getLocalizedName(promo.display.text),
type: promo.display.type,
strategyId: promo.strategyId
};
});
}
/**
* 查找商品适用的策略配置
*
* @param product 商品
* @param strategyConfigs 策略配置列表(可选)
* @returns 适用的策略配置列表
*/
}, {
key: "findApplicableStrategies",
value: function findApplicableStrategies(product, strategyConfigs) {
var _this2 = this;
var configs = strategyConfigs || this.strategyConfigs;
return configs.filter(function (config) {
return _this2.isProductInStrategy(product, config);
});
}
/**
* 批量获取商品列表的适用策略信息
*
* 用于给商品列表追加策略标签信息
* 只检查商品 ID 匹配和策略时间范围
*
* @param input 评估输入(商品列表)
* @param matchVariant 是否需要匹配 product_variant_id,默认 true(严格匹配),false 时只匹配 product_id
* @returns 每个商品的适用策略完整数据
*/
}, {
key: "getProductsApplicableStrategies",
value: function getProductsApplicableStrategies(input) {
var _this3 = this;
var matchVariant = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var products = input.products,
strategyConfigs = input.strategyConfigs,
channel = input.channel;
var configs = strategyConfigs || this.strategyConfigs;
var results = [];
var _iterator7 = _createForOfIteratorHelper(products),
_step7;
try {
var _loop = function _loop() {
var product = _step7.value;
var applicableStrategies = [];
// 遍历所有策略,检查此商品是否适用
var _iterator8 = _createForOfIteratorHelper(configs),
_step8;
try {
for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
var config = _step8.value;
var contextProduct = product;
if (!matchVariant) {
// 只匹配 product_id,忽略 product_variant_id
var productMatchRule = _this3.findProductMatchRule(config);
if (productMatchRule) {
var configProducts = productMatchRule.value;
var matchedConfig = configProducts.find(function (item) {
return item.product_id === product.product_id;
});
if (!matchedConfig) {
// product_id 不在策略适用范围内,跳过
continue;
}
// 用配置中的 variant_id 构造上下文,确保引擎 product_match 通过
if (matchedConfig.product_variant_id !== 0 && matchedConfig.product_variant_id !== product.product_variant_id) {
contextProduct = _objectSpread(_objectSpread({}, product), {}, {
product_variant_id: matchedConfig.product_variant_id
});
}
}
}
var context = _this3.adapter.prepareContext({
products: [contextProduct],
currentProduct: contextProduct,
channel: channel
});
// 评估策略
var result = _this3.engine.evaluate(config, context);
if (result.applicable && result.matchedActions.length > 0) {
var action = result.matchedActions[0];
var transformedResult = _this3.adapter.transformResult(result, {
products: [product],
currentProduct: product,
channel: channel
});
if (transformedResult.actionDetail) {
// 计算所需数量和可参与商品列表
var _this3$getPromotionRe = _this3.getPromotionRequirements(action.type, transformedResult.actionDetail, config),
requiredQuantity = _this3$getPromotionRe.requiredQuantity,
eligibleProducts = _this3$getPromotionRe.eligibleProducts;
applicableStrategies.push({
strategyId: config.metadata.id,
strategyName: config.metadata.name,
strategyMetadata: config.metadata,
actionType: action.type,
actionDetail: transformedResult.actionDetail,
display: _this3.getDisplayConfig(config),
strategyConfig: config,
requiredQuantity: requiredQuantity,
eligibleProducts: eligibleProducts
});
}
}
}
} catch (err) {
_iterator8.e(err);
} finally {
_iterator8.f();
}
results.push({
product: product,
applicableStrategies: applicableStrategies,
hasApplicableStrategy: applicableStrategies.length > 0
});
};
for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
_loop();
}
} catch (err) {
_iterator7.e(err);
} finally {
_iterator7.f();
}
return results;
}
/**
* 获取促销所需数量和可参与商品列表
*
* @param actionType 促销类型
* @param actionDetail 促销详情
* @param config 策略配置
* @returns { requiredQuantity, eligibleProducts }
*/
}, {
key: "getPromotionRequirements",
value: function getPromotionRequirements(actionType, actionDetail, config) {
var requiredQuantity = 0;
// 根据促销类型获取所需数量
if (actionType === 'BUY_X_GET_Y_FREE') {
var detail = actionDetail;
requiredQuantity = detail.buyQuantity || 0;
} else if (actionType === 'X_ITEMS_FOR_Y_PRICE') {
var _detail = actionDetail;
requiredQuantity = _detail.x || 0;
}
// 获取可参与商品列表
var productMatchRule = this.findProductMatchRule(config);
var eligibleProducts = (productMatchRule === null || productMatchRule === void 0 ? void 0 : productMatchRule.value) || [];
return {
requiredQuantity: requiredQuantity,
eligibleProducts: eligibleProducts
};
}
// ============================================
// 私有辅助方法
// ============================================
/**
* 按优先级排序促销组
* 优先级从 strategyConfig.actions[0].priority 获取,数值越小优先级越高
*/
}, {
key: "sortPromotionGroupsByPriority",
value: function sortPromotionGroupsByPriority(groups, promotions) {
// 创建策略ID到优先级的映射
var priorityMap = new Map();
var _iterator9 = _createForOfIteratorHelper(promotions),
_step9;
try {
for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
var _promo$strategyConfig2, _actions$0$priority, _actions$;
var promo = _step9.value;
var actions = ((_promo$strategyConfig2 = promo.strategyConfig) === null || _promo$strategyConfig2 === void 0 ? void 0 : _promo$strategyConfig2.actions) || [];
var priority = (_actions$0$priority = (_actions$ = actions[0]) === null || _actions$ === void 0 ? void 0 : _actions$.priority) !== null && _actions$0$priority !== void 0 ? _actions$0$priority : 999;
priorityMap.set(promo.strategyId, priority);
}
// 按优先级排序(数值越小优先级越高)
} catch (err) {
_iterator9.e(err);
} finally {
_iterator9.f();
}
return _toConsumableArray(groups).sort(function (a, b) {
var _priorityMap$get, _priorityMap$get2;
var priorityA = (_priorityMap$get = priorityMap.get(a.strategyId)) !== null && _priorityMap$get !== void 0 ? _priorityMap$get : 999;
var priorityB = (_priorityMap$get2 = priorityMap.get(b.strategyId)) !== null && _priorityMap$get2 !== void 0 ? _priorityMap$get2 : 999;
return priorityA - priorityB;
});
}
/**
* 处理 X件Y元 促销
*
* 1. 展开商品为单件列表,按价格从高到低排序
* 2. 计算可凑成的组数
* 3. 按原价比例均摊价格
* 4. 拆分商品(部分参与促销、部分原价)
*
* 注意:每个商品按 id 独立处理,不会合并不同 id 的商品
*/
}, {
key: "processXItemsForYPrice",
value: function processXItemsForYPrice(group, processedQuantityMap, matchedBundleIndexMap) {
var strategyId = group.strategyId,
strategyMetadata = group.strategyMetadata,
actionDetail = group.actionDetail,
groupProducts = group.products;
var detail = actionDetail;
var x = detail.x,
groupPrice = detail.price,
cumulative = detail.cumulative;
var result = [];
var originalAmount = new Decimal(0);
var finalAmount = new Decimal(0);
// 1. 展开所有商品为单件列表(排除已处理的数量)
// 每个单件记录来自哪个原商品(通过 product.id)
var unitProducts = [];
var _iterator10 = _createForOfIteratorHelper(groupProducts),
_step10;
try {
for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
var product = _step10.value;
var key = this.getProductKey(product);
var processedQty = processedQuantityMap.get(key) || 0;
var availableQty = product.quantity - processedQty;
if (availableQty <= 0) continue;
// 展开为单件,每个单件都记录来源商品
for (var _i = 0; _i < availableQty; _i++) {
unitProducts.push({
product: product,
price: product.price,
productId: product.id,
sourceAvailableQty: availableQty
});
}
}
} catch (err) {
_iterator10.e(err);
} finally {
_iterator10.f();
}
if (unitProducts.length === 0) {
return {
products: [],
originalAmount: 0,
finalAmount: 0
};
}
// 2. 排序:优先选择来源数量是 x 倍数的商品(可完整凑组不拆分),然后按价格从高到低
unitProducts.sort(function (a, b) {
// 优先级1:来源购物车项数量是 x 的倍数的排前面(这些商品可以完整凑组,避免拆分)
var aIsMultiple = a.sourceAvailableQty > 0 && a.sourceAvailableQty % x === 0 ? 1 : 0;
var bIsMultiple = b.sourceAvailableQty > 0 && b.sourceAvailableQty % x === 0 ? 1 : 0;
if (aIsMultiple !== bIsMultiple) return bIsMultiple - aIsMultiple;
// 优先级2:按价格从高到低(优先使用高价商品)
return b.price - a.price;
});
// 3. 计算可以凑成多少组
var totalUnits = unitProducts.length;
var groupCount = cumulative ? Math.floor(totalUnits / x) : totalUnits >= x ? 1 : 0;
var promotionUnits = groupCount * x;
// 4. 分离参与促销的和不参与的商品
var inPromotionUnits = unitProducts.slice(0, promotionUnits);
var notInPromotionUnits = unitProducts.slice(promotionUnits);
// 5. 按原商品 id 统计参与促销和不参与的数量
var promotionQtyByProductId = new Map();
var _iterator11 = _createForOfIteratorHelper(inPromotionUnits),
_step11;
try {
for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
var _unit = _step11.value;
var id = _unit.productId;
promotionQtyByProductId.set(id, (promotionQtyByProductId.get(id) || 0) + 1);
}
} catch (err) {
_iterator11.e(err);
} finally {
_iterator11.f();
}
var remainingQtyByProductId = new Map();
var _iterator12 = _createForOfIteratorHelper(notInPromotionUnits),
_step12;
try {
for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
var _unit2 = _step12.value;
var _id = _unit2.productId;
remainingQtyByProductId.set(_id, (remainingQtyByProductId.get(_id) || 0) + 1);
}
// 6. 为每个参与促销的单件计算均摊价格
// 按组内原价比例均摊
// 记录每个原商品的均摊价格累加(用于后续聚合)
} catch (err) {
_iterator12.e(err);
} finally {
_iterator12.f();
}
var allocatedPricesByProductId = new Map();
for (var g = 0; g < groupCount; g++) {
var startIdx = g * x;
var endIdx = startIdx + x;
var groupUnits = inPromotionUnits.slice(startIdx, endIdx);
// 计算组内原价总和
var groupOriginalTotal = groupUnits.reduce(function (sum, u) {
return sum.plus(u.price);
}, new Decimal(0));
// 按原价比例均摊
var groupPriceDecimal = new Decimal(groupPrice);
var allocatedSum = new Decimal(0);
for (var i = 0; i < groupUnits.length; i++) {
var unit = groupUnits[i];
var isLastUnit = i === groupUnits.length - 1;
var ratio = groupOriginalTotal.eq(0) ? new Decimal(0) : new Decimal(unit.price).div(groupOriginalTotal);
var allocatedDecimal = isLastUnit ? groupPriceDecimal.minus(allocatedSum) : groupPriceDecimal.mul(ratio);
// 记录这个单件的均摊价格
var productId = unit.productId;
var prices = allocatedPricesByProductId.get(productId) || [];
prices.push(allocatedDecimal);
allocatedPricesByProductId.set(productId, prices);
allocatedSum = allocatedSum.plus(allocatedDecimal);
}
}
// 7. 为每个原商品生成结果
var _iterator13 = _createForOfIteratorHelper(groupProducts),
_step13;
try {
for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
var _product = _step13.value;
var _key = this.getProductKey(_product);
var _processedQty = processedQuantityMap.get(_key) || 0;
var _availableQty = _product.quantity - _processedQty;
if (_availableQty <= 0) continue;
var promoQty = promotionQtyByProductId.get(_product.id) || 0;
var remainingQty = remainingQtyByProductId.get(_product.id) || 0;
var allocatedPrices = allocatedPricesByProductId.get(_product.id) || [];
// 获取当前商品的 matchedBundleIndex
var matchedBundleIndex = matchedBundleIndexMap === null || matchedBundleIndexMap === void 0 ? void 0 : matchedBundleIndexMap.get(_product.id);
if (promoQty > 0 && remainingQty === 0) {
// 全部参与促销:保留原 id,计算总 finalPrice
var totalAllocated = allocatedPrices.reduce(function (sum, p) {
return sum.plus(p);
}, new Decimal(0));
// 取促销价和原价的最小值,避免促销反而加价
var productOriginalAmount = new Decimal(_product.price).mul(promoQty);
var actualTotal = Decimal.min(totalAllocated, productOriginalAmount);
var finalPricePerUnit = this.formatPrice(actualTotal.div(promoQty));
result.push(_objectSpread(_objectSpread({}, _product), {}, {
quantity: promoQty,
finalPrice: finalPricePerUnit,
strategyId: strategyId,
strategyMetadata: strategyMetadata,
inPromotion: true,
isSplit: false,
matchedBundleIndex: matchedBundleIndex
}));
originalAmount = originalAmount.plus(productOriginalAmount);
finalAmount = finalAmount.plus(actualTotal);
} else if (promoQty === 0 && remainingQty > 0) {
// 全部不参与促销:保留原 id,finalPrice = 原价
result.push(_objectSpread(_objectSpread({}, _product), {}, {
quantity: remainingQty,
finalPrice: this.formatPrice(_product.price),
strategyId: undefined,
strategyMetadata: undefined,
inPromotion: false,
isSplit: false
}));
var amount = new Decimal(_product.price).mul(remainingQty);
originalAmount = originalAmount.plus(amount);
finalAmount = finalAmount.plus(amount);
} else if (promoQty > 0 && remainingQty > 0) {
// 部分参与促销,部分不参与:需要拆分
// 参与促销的部分:新 id
var _totalAllocated = allocatedPrices.reduce(function (sum, p) {
return sum.plus(p);
}, new Decimal(0));
// 取促销价和原价的最小值,避免促销反而加价
var promoOriginalAmount = new Decimal(_product.price).mul(promoQty);
var _actualTotal = Decimal.min(_totalAllocated, promoOriginalAmount);
var _finalPricePerUnit = this.formatPrice(_actualTotal.div(promoQty));
result.push(_objectSpread(_objectSpread({}, _product), {}, {
id: this.generateRandomId(),
originalId: _product.id,
quantity: promoQty,
finalPrice: _finalPricePerUnit,
strategyId: strategyId,
strategyMetadata: strategyMetadata,
inPromotion: true,
matchedBundleIndex: matchedBundleIndex,
isSplit: true
}));
originalAmount = originalAmount.plus(promoOriginalAmount);
finalAmount = finalAmount.plus(_actualTotal);
// 不参与促销的部分:保留原 id
result.push(_objectSpread(_objectSpread({}, _product), {}, {
quantity: remainingQty,
finalPrice: this.formatPrice(_product.price),
strategyId: undefined,
strategyMetadata: undefined,
inPromotion: false,
isSplit: true
}));
var remainingAmount = new Decimal(_product.price).mul(remainingQty);
originalAmount = originalAmount.plus(remainingAmount);
finalAmount = finalAmount.plus(remainingAmount);
}
// 更新已处理数量
processedQuantityMap.set(_key, _processedQty + _availableQty);
}
// 8. 尾差调整:确保促销商品的 finalPrice × quantity 之和精确等于 groupPrice × groupCount
} catch (err) {
_iterator13.e(err);
} finally {
_iterator13.f();
}
if (groupCount > 0) {
var expectedPromoTotal = new Decimal(groupPrice).mul(groupCount);
var promoItems = result.filter(function (p) {
return p.inPromotion;
});
var actualPromoTotal = new Decimal(0);
var _iterator14 = _createForOfIteratorHelper(promoItems),
_step14;
try {
for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
var _p = _step14.value;
actualPromoTotal = actualPromoTotal.plus(new Decimal(_p.finalPrice).mul(_p.quantity));
}
} catch (err) {
_iterator14.e(err);
} finally {
_iterator14.f();
}
var roundingDiff = actualPromoTotal.minus(expectedPromoTotal);
if (!roundingDiff.eq(0)) {
// 优先找 quantity=1 的促销商品调整(差额直接加减到单价上)
var adjustTarget = promoItems.find(function (p) {
return p.quantity === 1;
});
if (adjustTarget) {
adjustTarget.finalPrice = this.formatPrice(new Decimal(adjustTarget.finalPrice).minus(roundingDiff));
} else if (promoItems.length > 0) {
// 没有 quantity=1 的促销商品,从最后一个促销商品中拆出1件做尾差调整
var lastPromo = promoItems[promoItems.length - 1];
var adjustedUnitPrice = this.formatPrice(new Decimal(lastPromo.finalPrice).minus(roundingDiff));
// 原商品减少1件
lastPromo.quantity -= 1;
lastPromo.isSplit = true;
// 拆出1件用调整后的价格
result.push(_objectSpread(_objectSpread({}, lastPromo), {}, {
id: this.generateRandomId(),
originalId: lastPromo.originalId || lastPromo.id,
quantity: 1,
finalPrice: adjustedUnitPrice,
isSplit: true
}));
}
// 重新计算 finalAmount
finalAmount = new Decimal(0);
var _iterator15 = _createForOfIteratorHelper(result),
_step15;
try {
for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
var p = _step15.value;
finalAmount = finalAmount.plus(new Decimal(p.finalPrice).mul(p.quantity));
}
} catch (err) {
_iterator15.e(err);
} finally {
_iterator15.f();
}
}
}
return {
products: result,
originalAmount: originalAmount.toNumber(),
finalAmount: finalAmount.toNumber()
};
}
/**
* 处理 买X送Y 促销
*
* 计算赠品数量,商品本身价格不变
* 支持 bundle 子商品的数量计算(主商品数量 × 子商品数量)
*/
}, {
key: "processBuyXGetYFree",
value: function processBuyXGetYFree(group, processedQuantityMap, matchedBundleIndexMap) {
var strategyId = group.strategyId,
strategyName = group.strategyName,
strategyMetadata = group.strategyMetadata,
actionDetail = group.actionDetail,
groupProducts = group.products;
var detail = actionDetail;
var buyQuantity = detail.buyQuantity,
freeQuantity = detail.freeQuantity,
cumulative = detail.cumulative,
giftProducts = detail.giftProducts;
var result = [];
var originalAmount = new Decimal(0);
var finalAmount = new Decimal(0);
// 1. 先计算可用商品总数量(第一次遍历)
// 对于 bundle 子商品匹配,促销数量 = 主商品数量 × bundle子商品数量
var totalPromoQty = 0;
var productInfoMap = new Map();
var _iterator16 = _createForOfIteratorHelper(groupProducts),
_step16;
try {
for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
var product = _step16.value;
var key = this.getProductKey(product);
var processedQty = processedQuantityMap.get(key) || 0;
var availableQty = product.quantity - processedQty;
if (availableQty <= 0) continue;
// 获取 matchedBundleIndex
var matchedBundleIndex = matchedBundleIndexMap === null || matchedBundleIndexMap === void 0 ? void 0 : matchedBundleIndexMap.get(product.id);
// 计算用于促销的数量
var promoQty = availableQty;
if (matchedBundleIndex !== undefined && product.bundle) {
var bundleItem = product.bundle[matchedBundleIndex];
// 促销数量 = 主商品数量 × bundle子商品数量
promoQty = availableQty * (bundleItem.quantity || 1);
}
totalPromoQty += promoQty;
productInfoMap.set(key, {
availableQty: availableQty,
promoQty: promoQty,
matchedBundleIndex: matchedBundleIndex
});
}
// 2. 判断是否满足促销条件
} catch (err) {
_iterator16.e(err);
} finally {
_iterator16.f();
}
var isFulfilled = totalPromoQty >= buyQuantity;
// 3. 生成商品结果(第二次遍历)
var _iterator17 = _createForOfIteratorHelper(groupProducts),
_step17;
try {
for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
var _product2 = _step17.value;
var _key2 = this.getProductKey(_product2);
var info = productInfoMap.get(_key2);
if (!info || info.availableQty <= 0) continue;
var _processedQty2 = processedQuantityMap.get(_key2) || 0;
// 商品本身价格不变,只是触发赠品
// inPromotion 只有在满足条件时才为 true
result.push(_objectSpread(_objectSpread({}, _product2), {}, {
quantity: info.availableQty,
// 返回主商品数量
finalPrice: this.formatPrice(_product2.price),
strategyId: strategyId,
strategyMetadata: strategyMetadata,
inPromotion: isFulfilled,
isSplit: info.availableQty < _product2.quantity,
matchedBundleIndex: info.matchedBundleIndex
}));
var amount = new Decimal(_product2.price).mul(info.availableQty);
originalAmount = originalAmount.plus(amount);
finalAmount = finalAmount.plus(amount);
// 更新已处理数量(使用主商品数量)
processedQuantityMap.set(_key2, _processedQty2 + info.availableQty);
}
// 4. 计算赠品数量(使用促销数量)
} catch (err) {
_iterator17.e(err);
} finally {
_iterator17.f();
}
var giftCount = 0;
if (isFulfilled) {
giftCount = cumulative ? Math.floor(totalPromoQty / buyQuantity) * freeQuantity : freeQuantity;
}
// 5. 构建赠品信息
var giftInfo = giftCount > 0 ? {
strategyId: strategyId,
strategyName: strategyName,
giftCount: giftCount,
giftOptions: giftProducts || []
} : null;
return {
products: result,
originalAmount: originalAmount.toNumber(),
finalAmount: finalAmount.toNumber(),
giftInfo: giftInfo
};
}
/**
* 获取未参与任何促销的商品
*/
}, {
key: "getRemainingProducts",
value: function getRemainingProducts(allProducts, processedQuantityMap) {
var result = [];
var _iterator18 = _createForOfIteratorHelper(allProducts),
_step18;
try {
for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
var product = _step18.value;
var key = this.getProductKey(product);
var processedQty = processedQuantityMap.get(key) || 0;
var remainingQty = product.quantity - processedQty;
if (remainingQty > 0) {
result.push(_objectSpread(_objectSpread({}, product), {}, {
quantity: remainingQty,
finalPrice: this.formatPrice(product.price),
strategyId: undefined,
inPromotion: false,
isSplit: remainingQty < product.quantity
}));
}
}
} catch (err) {
_iterator18.e(err);
} finally {
_iterator18.f();
}
return result;
}
/**
* 价格统一保留两位小数
*/
}, {
key: "formatPrice",
value: function formatPrice(value) {
return new Decimal(value).toDecimalPlaces(2, Decimal.ROUND_HALF_UP).toNumber();
}
/**
* 获取商品唯一标识 key
* 使用商品的 id 作为唯一标识,不同 id 的商品不会被合并
*/
}, {
key: "getProductKey",
value: function getProductKey(product) {
return String(product.id);
}
/**
* 生成随机ID
*/
}, {
key: "generateRandomId",
value: function generateRandomId() {
return Math.floor(Math.random() * 1000000000) + Date.now();
}
/**
* 商品匹配结果信息
*/
}, {
key: "getProductMatchInfo",
value: function getProductMatchInfo(product, config) {
var productMatchRule = this.findProductMatchRule(config);
if (!productMatchRule) {
// 没有商品范围条件,默认适用所有商品(匹配主商品)
return {
isMatch: true
};
}
var configProducts = productMatchRule.value;
// 1. 先检查主商品是否匹配
if (this.isProductIdMatch(product.product_id, product.product_variant_id, configProducts)) {
return {
isMatch: true
};
}
// 2. 主商品不匹配,检查 bundle 子商品
if (product.bundle && product.bundle.length > 0) {
for (var i = 0; i < product.bundle.length; i++) {
var bundleItem = product.bundle[i];
if (this.isProductIdMatch(bundleItem._bundle_product_id, bundleItem.product_variant_id, configProducts)) {
return {
isMatch: true,
matchedBundleIndex: i
};
}
}
}
return {
isMatch: false
};
}
/**
* 检查商品是否在策略的适用范围内
*/
}, {
key: "isProductInStrategy",
value: function isProductInStrategy(product, config) {
return this.getProductMatchInfo(product, config).isMatch;
}
/**
* 查找商品匹配规则
*/
}, {
key: "findProductMatchRule",
value: function findProductMatchRule(config) {
var _config$conditions;
if (!(config !== null && config !== void 0 && (_config$conditions = config.conditions) !== null && _config$conditions !== void 0 && _config$conditions.rules)) {
return null;
}
return config.conditions.rules.find(function (rule) {
return rule.field === 'productIdAndVariantId' && (rule.operator === 'product_match' || rule.operator === 'object_in');
});
}
/**
* 检查商品ID是否匹配配置列表
*/
}, {
key: "isProductIdMatch",
value: function isProductIdMatch(productId, productVariantId, configProducts) {
return configProducts.some(function (config) {
// 首先检查 product_id
if (config.product_id !== productId) {
return false;
}
// 如果配置的 product_variant_id = 0,只需要 product_id 匹配
if (config.product_variant_id === 0) {
return true;
}
// 否则需要 product_variant_id 精确匹配
return config.product_variant_id === productVariantId;
});
}
/**
* 检查商品是否匹配配置(兼容旧方法)
*/
}, {
key: "isProductMatch",
value: function isProductMatch(product, configProducts) {
return this.isProductIdMatch(product.product_id, product.product_variant_id, configProducts);
}
/**
* 获取展示配置
*/
}, {
key: "getDisplayConfig",
value: function getDisplayConfig(config) {
var _custom$display;
var custom = config.metadata.custom;
if (custom !== null && custom !== void 0 && (_custom$display = custom.display) !== null && _custom$display !== void 0 && _custom$display.product_card) {
return {
text: custom.display.product_card.text,
type: custom.display.product_card.type
};
}
return undefined;
}
}]);
return PromotionEvaluator;
}();