UNPKG

@pisell/pisellos

Version:

一个可扩展的前端模块化SDK框架,支持插件系统

1,279 lines (1,192 loc) 52.5 kB
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; }();