@pisell/pisellos 1.0.142 → 1.0.143

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.
Files changed (72) hide show
  1. package/dist/model/strategy/adapter/index.d.ts +4 -0
  2. package/dist/model/strategy/adapter/index.js +4 -1
  3. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +67 -0
  4. package/dist/model/strategy/adapter/itemRule/adapter.js +483 -0
  5. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +43 -0
  6. package/dist/model/strategy/adapter/itemRule/evaluator.js +169 -0
  7. package/dist/model/strategy/adapter/itemRule/examples.d.ts +79 -0
  8. package/dist/model/strategy/adapter/itemRule/examples.js +382 -0
  9. package/dist/model/strategy/adapter/itemRule/index.d.ts +5 -0
  10. package/dist/model/strategy/adapter/itemRule/index.js +5 -0
  11. package/dist/model/strategy/adapter/itemRule/type.d.ts +225 -0
  12. package/dist/model/strategy/adapter/itemRule/type.js +101 -0
  13. package/dist/model/strategy/adapter/promotion/adapter.d.ts +66 -0
  14. package/dist/model/strategy/adapter/promotion/adapter.js +271 -0
  15. package/dist/model/strategy/adapter/promotion/evaluator.d.ts +213 -0
  16. package/dist/model/strategy/adapter/promotion/evaluator.js +1279 -0
  17. package/dist/model/strategy/adapter/promotion/examples.d.ts +138 -0
  18. package/dist/model/strategy/adapter/promotion/examples.js +166 -0
  19. package/dist/model/strategy/adapter/promotion/index.d.ts +4 -0
  20. package/dist/model/strategy/adapter/promotion/index.js +0 -0
  21. package/dist/model/strategy/adapter/promotion/type.d.ts +447 -0
  22. package/dist/model/strategy/adapter/promotion/type.js +209 -0
  23. package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  24. package/dist/model/strategy/adapter/walletPass/evaluator.js +67 -10
  25. package/dist/model/strategy/adapter/walletPass/locales.js +12 -3
  26. package/dist/model/strategy/adapter/walletPass/type.d.ts +20 -2
  27. package/dist/model/strategy/adapter/walletPass/utils.d.ts +6 -6
  28. package/dist/model/strategy/adapter/walletPass/utils.js +143 -103
  29. package/dist/model/strategy/engine.d.ts +119 -0
  30. package/dist/model/strategy/engine.js +711 -0
  31. package/dist/model/strategy/index.d.ts +2 -93
  32. package/dist/model/strategy/index.js +6 -549
  33. package/dist/model/strategy/type.d.ts +17 -0
  34. package/dist/model/strategy/type.js +4 -0
  35. package/dist/solution/BookingByStep/index.d.ts +1 -1
  36. package/dist/solution/BookingTicket/index.d.ts +1 -1
  37. package/lib/model/strategy/adapter/index.d.ts +4 -0
  38. package/lib/model/strategy/adapter/index.js +12 -0
  39. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +67 -0
  40. package/lib/model/strategy/adapter/itemRule/adapter.js +377 -0
  41. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +43 -0
  42. package/lib/model/strategy/adapter/itemRule/evaluator.js +124 -0
  43. package/lib/model/strategy/adapter/itemRule/examples.d.ts +79 -0
  44. package/lib/model/strategy/adapter/itemRule/examples.js +316 -0
  45. package/lib/model/strategy/adapter/itemRule/index.d.ts +5 -0
  46. package/lib/model/strategy/adapter/itemRule/index.js +60 -0
  47. package/lib/model/strategy/adapter/itemRule/type.d.ts +225 -0
  48. package/lib/model/strategy/adapter/itemRule/type.js +46 -0
  49. package/lib/model/strategy/adapter/promotion/adapter.d.ts +66 -0
  50. package/lib/model/strategy/adapter/promotion/adapter.js +217 -0
  51. package/lib/model/strategy/adapter/promotion/evaluator.d.ts +213 -0
  52. package/lib/model/strategy/adapter/promotion/evaluator.js +893 -0
  53. package/lib/model/strategy/adapter/promotion/examples.d.ts +138 -0
  54. package/lib/model/strategy/adapter/promotion/examples.js +192 -0
  55. package/lib/model/strategy/adapter/promotion/index.d.ts +4 -0
  56. package/lib/model/strategy/adapter/promotion/index.js +0 -0
  57. package/lib/model/strategy/adapter/promotion/type.d.ts +447 -0
  58. package/lib/model/strategy/adapter/promotion/type.js +51 -0
  59. package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  60. package/lib/model/strategy/adapter/walletPass/evaluator.js +38 -5
  61. package/lib/model/strategy/adapter/walletPass/locales.js +12 -3
  62. package/lib/model/strategy/adapter/walletPass/type.d.ts +20 -2
  63. package/lib/model/strategy/adapter/walletPass/utils.d.ts +6 -6
  64. package/lib/model/strategy/adapter/walletPass/utils.js +153 -82
  65. package/lib/model/strategy/engine.d.ts +119 -0
  66. package/lib/model/strategy/engine.js +514 -0
  67. package/lib/model/strategy/index.d.ts +2 -93
  68. package/lib/model/strategy/index.js +6 -381
  69. package/lib/model/strategy/type.d.ts +17 -0
  70. package/lib/solution/BookingByStep/index.d.ts +1 -1
  71. package/lib/solution/BookingTicket/index.d.ts +1 -1
  72. package/package.json +1 -1
@@ -12,6 +12,63 @@ function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symb
12
12
  function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
13
13
  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; }
14
14
  import Decimal from 'decimal.js';
15
+ /** 订单商品数量 */
16
+ export var getProductQuantity = function getProductQuantity(product) {
17
+ return product.quantity || product.product_quantity || 1;
18
+ };
19
+
20
+ /**
21
+ * 订单商品行唯一键,用于 maxPassesPerItem 按行、按件配额(同 SPU 不同规格视为不同行)。
22
+ */
23
+ export function resolveWalletPassLineKey(product, indexInOrder) {
24
+ var m = (product === null || product === void 0 ? void 0 : product.metadata) || {};
25
+ var u1 = m.product_unique;
26
+ if (u1 != null && String(u1).length > 0) return String(u1);
27
+ var u2 = m.unique_identification_number;
28
+ if (u2 != null && String(u2).length > 0) return String(u2);
29
+ if ((product === null || product === void 0 ? void 0 : product.id) != null && String(product.id).length > 0) return "order_item:".concat(product.id);
30
+ if ((product === null || product === void 0 ? void 0 : product.product_unique_string) != null && String(product.product_unique_string).length > 0) {
31
+ return String(product.product_unique_string);
32
+ }
33
+ return "order_line_".concat(indexInOrder);
34
+ }
35
+ function getExpandedOrderLineQuantity(p) {
36
+ if ((p === null || p === void 0 ? void 0 : p._orderLineQuantity) != null && p._orderLineQuantity > 0) return p._orderLineQuantity;
37
+ return getProductQuantity(p);
38
+ }
39
+
40
+ /** 该行允许消耗的「券次」上限:maxPassesPerItem × 订单行件数 */
41
+ function getMaxPassSlotsForExpandedLine(maxPassesPerItem, p) {
42
+ if (maxPassesPerItem <= 0) return Infinity;
43
+ return maxPassesPerItem * getExpandedOrderLineQuantity(p);
44
+ }
45
+
46
+ /**
47
+ * 本次抵扣占用多少「券次」配额:有抵扣时至少占 1,并与按金额折算的件数挂钩,不超过剩余额度。
48
+ */
49
+ function computePassSlotsIncrement(deductAmount, deductQty, maxPassesPerItem, orderLineQuantity, currentUsage) {
50
+ if (maxPassesPerItem <= 0 || !deductAmount.greaterThan(0)) return 0;
51
+ var cap = maxPassesPerItem * orderLineQuantity;
52
+ var room = Math.max(0, cap - currentUsage);
53
+ if (room <= 0) return 0;
54
+ var raw = Math.max(1, Math.ceil(Number(deductQty)) || 1);
55
+ return Math.min(raw, room);
56
+ }
57
+ function applyMaxPassUsageIncrements(usageMap, walletPassProductId, maxPassesPerItem, deductionDetails) {
58
+ deductionDetails.forEach(function (detail) {
59
+ var _usageMap$get;
60
+ var lineKey = detail.lineKey;
61
+ if (!lineKey || maxPassesPerItem <= 0) return;
62
+ var orderLineQty = detail.orderLineQuantity != null && detail.orderLineQuantity > 0 ? detail.orderLineQuantity : 1;
63
+ var cur = ((_usageMap$get = usageMap.get(walletPassProductId)) === null || _usageMap$get === void 0 ? void 0 : _usageMap$get.get(lineKey)) || 0;
64
+ var inc = computePassSlotsIncrement(new Decimal(detail.deductAmount), detail.deductQuantity, maxPassesPerItem, orderLineQty, cur);
65
+ if (inc <= 0) return;
66
+ if (!usageMap.has(walletPassProductId)) usageMap.set(walletPassProductId, new Map());
67
+ var inner = usageMap.get(walletPassProductId);
68
+ inner.set(lineKey, cur + inc);
69
+ });
70
+ }
71
+
15
72
  // 辅助函数:根据 deductTaxAndFee 配置获取推荐使用金额
16
73
  var getRecommendedAmount = function getRecommendedAmount(voucher) {
17
74
  var _config$deductTaxAndF;
@@ -118,27 +175,19 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
118
175
  var productsCopy = expandProductsWithBundleItems(products, true);
119
176
  var remainingOrderAmount = new Decimal(orderTotalAmount); // 订单剩余应付金额
120
177
 
121
- // ========== 辅助工具:maxPassesPerItem(单商品可用卡券上限)追踪 ==========
122
- // 获取指定 Wallet Pass 商品对指定订单商品行的已用卡券次数
123
- var getItemPassUsage = function getItemPassUsage(usageMap, walletPassProductId, orderItemProductId) {
124
- var _usageMap$get;
125
- return ((_usageMap$get = usageMap.get(walletPassProductId)) === null || _usageMap$get === void 0 ? void 0 : _usageMap$get.get(orderItemProductId)) || 0;
126
- };
127
-
128
- // 更新指定 Wallet Pass 商品对指定订单商品行的已用卡券次数 +1
129
- var incrementItemPassUsage = function incrementItemPassUsage(usageMap, walletPassProductId, orderItemProductId) {
130
- if (!usageMap.has(walletPassProductId)) {
131
- usageMap.set(walletPassProductId, new Map());
132
- }
133
- var innerMap = usageMap.get(walletPassProductId);
134
- innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
178
+ // ========== 辅助工具:maxPassesPerItem(按订单行 + 件数)追踪 ==========
179
+ var getItemPassUsage = function getItemPassUsage(usageMap, walletPassProductId, lineKey) {
180
+ var _usageMap$get2;
181
+ return ((_usageMap$get2 = usageMap.get(walletPassProductId)) === null || _usageMap$get2 === void 0 ? void 0 : _usageMap$get2.get(lineKey)) || 0;
135
182
  };
136
183
 
137
- // 按 maxPassesPerItem 过滤商品:排除已达到单商品可用卡券上限的商品
184
+ // 按 maxPassesPerItem 过滤:排除已达到该行「券次」上限的展开行(按 _walletPassLineKey)
138
185
  var filterByMaxPassesPerItem = function filterByMaxPassesPerItem(products, usageMap, walletPassProductId, maxPassesPerItem) {
139
- if (maxPassesPerItem <= 0) return products; // 0 = 不限制
186
+ if (maxPassesPerItem <= 0) return products;
140
187
  return products.filter(function (p) {
141
- return getItemPassUsage(usageMap, walletPassProductId, p.product_id) < maxPassesPerItem;
188
+ var lineKey = p._walletPassLineKey;
189
+ if (!lineKey) return true;
190
+ return getItemPassUsage(usageMap, walletPassProductId, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
142
191
  });
143
192
  };
144
193
  // ================================================================
@@ -217,16 +266,16 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
217
266
  }, new Decimal(0));
218
267
  }
219
268
  } else {
220
- // 非跨商品券:只能抵扣单个商品(剩余金额最高的)
269
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
221
270
  var maxProduct = applicableProducts.reduce(function (max, p) {
222
- return p[amountField].greaterThan(max[amountField]) ? p : max;
271
+ return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
223
272
  });
224
- // 动态计算当前可抵扣数量
225
- var _currentAvailableQty = Math.ceil(maxProduct[amountField].dividedBy(maxProduct[unitPriceField]).toNumber());
226
- // 非跨商品券也受 applicableProductLimit 限制
227
- var _deductQty = applicableProductLimit > 0 ? Math.min(_currentAvailableQty, applicableProductLimit) : _currentAvailableQty;
228
- // 实际可抵扣金额 = min(数量 * 单价, 剩余金额)
229
- finalApplicableAmount = Decimal.min(maxProduct[unitPriceField].times(_deductQty), maxProduct[amountField]);
273
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
274
+ if (maxPassesPerItem > 0) {
275
+ finalApplicableAmount = Decimal.min(maxProduct[unitPriceField].times(maxPassesPerItem), maxProduct[amountField]);
276
+ } else {
277
+ finalApplicableAmount = maxProduct[amountField];
278
+ }
230
279
  }
231
280
 
232
281
  // 返回最小值
@@ -287,7 +336,9 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
287
336
  var availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter(function (p) {
288
337
  return p[amountField].greaterThan(0);
289
338
  }).filter(function (p) {
290
- return getItemPassUsage(itemPassUsage, product_id, p.product_id) < maxPassesPerItem;
339
+ var lineKey = p._walletPassLineKey;
340
+ if (!lineKey) return true;
341
+ return getItemPassUsage(itemPassUsage, product_id, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
291
342
  });
292
343
  if (availableAfterPassLimit.length === 0) {
293
344
  return {
@@ -323,7 +374,7 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
323
374
  // 重置商品余额追踪(同时维护含税和不含税两个金额池)
324
375
  var productsForRecommendation = expandProductsWithBundleItems(products, true);
325
376
  remainingOrderAmount = new Decimal(orderTotalAmount);
326
- // 追踪推荐阶段每个 Wallet Pass 商品对每个订单商品行的已使用卡券次数
377
+ // 追踪推荐阶段每个 Wallet Pass 商品对每个订单商品行的已使用卡券次数(按行唯一键)
327
378
  var itemPassUsageMap = new Map();
328
379
 
329
380
  /**
@@ -426,15 +477,16 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
426
477
  }, new Decimal(0));
427
478
  }
428
479
  } else {
429
- // 非跨商品券:单个剩余金额最高的商品,也受 applicableProductLimit 限制
480
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
430
481
  var maxProduct = applicableProducts.reduce(function (max, p) {
431
- return p[amountField].greaterThan(max[amountField]) ? p : max;
482
+ return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
432
483
  });
433
- // 动态计算当前可抵扣数量
434
- var _currentAvailableQty2 = Math.ceil(maxProduct[amountField].dividedBy(maxProduct[unitPriceField]).toNumber());
435
- var _deductQty2 = applicableProductLimit > 0 ? Math.min(_currentAvailableQty2, applicableProductLimit) : _currentAvailableQty2;
436
- // 实际可抵扣金额 = min(数量 * 单价, 剩余金额)
437
- calculatedAvailableMaxAmount = Decimal.min(maxProduct[unitPriceField].times(_deductQty2), maxProduct[amountField]);
484
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
485
+ if (maxPassesPerItem > 0) {
486
+ calculatedAvailableMaxAmount = Decimal.min(maxProduct[unitPriceField].times(maxPassesPerItem), maxProduct[amountField]);
487
+ } else {
488
+ calculatedAvailableMaxAmount = maxProduct[amountField];
489
+ }
438
490
  }
439
491
 
440
492
  // 取最小值:min(recommended_usage_amount, maxDeductionAmount, 适用商品金额, 订单剩余金额)
@@ -459,8 +511,8 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
459
511
  if (deductionLeft.lessThanOrEqualTo(0) || _remainingLimit <= 0) break;
460
512
 
461
513
  // 动态计算当前可抵扣数量 = ceil(剩余金额 / 单价)
462
- var _currentAvailableQty3 = Math.ceil(_product[amountField].dividedBy(_product[unitPriceField]).toNumber());
463
- var availableQty = Math.min(_currentAvailableQty3, _remainingLimit);
514
+ var _currentAvailableQty = Math.ceil(_product[amountField].dividedBy(_product[unitPriceField]).toNumber());
515
+ var availableQty = Math.min(_currentAvailableQty, _remainingLimit);
464
516
 
465
517
  // 计算本商品最大可抵扣金额 = min(数量 * 单价, 剩余金额)
466
518
  var maxDeductForProduct = Decimal.min(_product[unitPriceField].times(availableQty), _product[amountField]);
@@ -477,6 +529,8 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
477
529
  product_id: _product.product_id,
478
530
  parent_product_id: _product.parent_product_id || null,
479
531
  is_bundle_item: _product.is_bundle_item || false,
532
+ lineKey: _product._walletPassLineKey,
533
+ orderLineQuantity: getExpandedOrderLineQuantity(_product),
480
534
  deductAmount: actualDeductAmount.toNumber(),
481
535
  // 转换为数字
482
536
  deductQuantity: actualDeductQty // 抵扣涉及的数量(用于记录)
@@ -488,17 +542,16 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
488
542
  _iterator3.f();
489
543
  }
490
544
  } else {
491
- // 非跨商品券:只抵扣一个商品(剩余金额最高的),也受 applicableProductLimit 限制
545
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
492
546
  var targetProduct = applicableProducts.reduce(function (max, p) {
493
- return p[amountField].greaterThan(max[amountField]) ? p : max;
547
+ return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
494
548
  });
495
549
 
496
- // 动态计算当前可抵扣数量
497
- var _currentAvailableQty4 = Math.ceil(targetProduct[amountField].dividedBy(targetProduct[unitPriceField]).toNumber());
498
- var _availableQty = applicableProductLimit > 0 ? Math.min(_currentAvailableQty4, applicableProductLimit) : _currentAvailableQty4;
499
-
500
- // 计算本商品最大可抵扣金额 = min(数量 * 单价, 剩余金额)
501
- var _maxDeductForProduct = Decimal.min(targetProduct[unitPriceField].times(_availableQty), targetProduct[amountField]);
550
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
551
+ var _maxDeductForProduct = targetProduct[amountField];
552
+ if (maxPassesPerItem > 0) {
553
+ _maxDeductForProduct = Decimal.min(targetProduct[unitPriceField].times(maxPassesPerItem), targetProduct[amountField]);
554
+ }
502
555
  var _actualDeductAmount = Decimal.min(deductionLeft, _maxDeductForProduct);
503
556
 
504
557
  // 计算实际抵扣的数量
@@ -511,9 +564,10 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
511
564
  product_id: targetProduct.product_id,
512
565
  parent_product_id: targetProduct.parent_product_id || null,
513
566
  is_bundle_item: targetProduct.is_bundle_item || false,
567
+ lineKey: targetProduct._walletPassLineKey,
568
+ orderLineQuantity: getExpandedOrderLineQuantity(targetProduct),
514
569
  deductAmount: _actualDeductAmount.toNumber(),
515
- // 转换为数字
516
- deductQuantity: _actualDeductQty // 抵扣涉及的数量(用于记录)
570
+ deductQuantity: _actualDeductQty
517
571
  });
518
572
  }
519
573
  var totalDeducted = maxDeduction.minus(deductionLeft);
@@ -523,11 +577,7 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
523
577
 
524
578
  // 更新券使用次数(按 product_id 统计)
525
579
  usedVoucherCounts.set(product_id, (usedVoucherCounts.get(product_id) || 0) + 1);
526
-
527
- // 更新 maxPassesPerItem 追踪:记录每个被抵扣的商品行
528
- deductionDetails.forEach(function (detail) {
529
- incrementItemPassUsage(itemPassUsageMap, product_id, detail.product_id);
530
- });
580
+ applyMaxPassUsageIncrements(itemPassUsageMap, product_id, maxPassesPerItem, deductionDetails);
531
581
 
532
582
  // 添加到推荐列表(包含基于当前剩余金额计算的 available_max_amount)
533
583
  recommendedVouchers.push(_objectSpread(_objectSpread({}, voucher), {}, {
@@ -610,24 +660,18 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
610
660
  var remainingOrderAmount = new Decimal(orderTotalAmount);
611
661
  var selectedWithDetails = [];
612
662
 
613
- // 追踪每个 Wallet Pass 商品对每个订单商品行的已使用卡券次数
614
- // Map<walletPassProductId, Map<orderItemProductId, usedCount>>
663
+ // 追踪每个 Wallet Pass 商品对每个订单商品行的已使用卡券次数(按行唯一键)
615
664
  var itemPassUsageMap = new Map();
616
- var getItemPassUsage = function getItemPassUsage(walletPassProductId, orderItemProductId) {
665
+ var getItemPassUsageRecalc = function getItemPassUsageRecalc(walletPassProductId, lineKey) {
617
666
  var _itemPassUsageMap$get;
618
- return ((_itemPassUsageMap$get = itemPassUsageMap.get(walletPassProductId)) === null || _itemPassUsageMap$get === void 0 ? void 0 : _itemPassUsageMap$get.get(orderItemProductId)) || 0;
667
+ return ((_itemPassUsageMap$get = itemPassUsageMap.get(walletPassProductId)) === null || _itemPassUsageMap$get === void 0 ? void 0 : _itemPassUsageMap$get.get(lineKey)) || 0;
619
668
  };
620
- var incrementItemPassUsage = function incrementItemPassUsage(walletPassProductId, orderItemProductId) {
621
- if (!itemPassUsageMap.has(walletPassProductId)) {
622
- itemPassUsageMap.set(walletPassProductId, new Map());
623
- }
624
- var innerMap = itemPassUsageMap.get(walletPassProductId);
625
- innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
626
- };
627
- var filterByMaxPassesPerItem = function filterByMaxPassesPerItem(products, walletPassProductId, maxPassesPerItem) {
628
- if (maxPassesPerItem <= 0) return products; // 0 = 不限制
669
+ var filterByMaxPassesPerItemRecalc = function filterByMaxPassesPerItemRecalc(products, walletPassProductId, maxPassesPerItem) {
670
+ if (maxPassesPerItem <= 0) return products;
629
671
  return products.filter(function (p) {
630
- return getItemPassUsage(walletPassProductId, p.product_id) < maxPassesPerItem;
672
+ var lineKey = p._walletPassLineKey;
673
+ if (!lineKey) return true;
674
+ return getItemPassUsageRecalc(walletPassProductId, lineKey) < getMaxPassSlotsForExpandedLine(maxPassesPerItem, p);
631
675
  });
632
676
  };
633
677
 
@@ -652,8 +696,8 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
652
696
  return p[amountField].greaterThan(0);
653
697
  });
654
698
 
655
- // 按 maxPassesPerItem 过滤:排除已达到单商品可用卡券上限的商品
656
- applicableProducts = filterByMaxPassesPerItem(applicableProducts, selectedVoucher.product_id, maxPassesPerItem);
699
+ // 按 maxPassesPerItem 过滤:排除已达到该行券次上限的展开行
700
+ applicableProducts = filterByMaxPassesPerItemRecalc(applicableProducts, selectedVoucher.product_id, maxPassesPerItem);
657
701
  if (applicableProducts.length === 0) {
658
702
  // 无适用商品,跳过
659
703
  selectedWithDetails.push(_objectSpread(_objectSpread({}, selectedVoucher), {}, {
@@ -704,6 +748,8 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
704
748
  product_id: product.product_id,
705
749
  parent_product_id: product.parent_product_id || null,
706
750
  is_bundle_item: product.is_bundle_item || false,
751
+ lineKey: product._walletPassLineKey,
752
+ orderLineQuantity: getExpandedOrderLineQuantity(product),
707
753
  deductAmount: actualDeductAmount.toNumber(),
708
754
  // 转换为数字
709
755
  deductQuantity: actualDeductQty // 抵扣涉及的数量(用于记录)
@@ -715,17 +761,16 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
715
761
  _iterator4.f();
716
762
  }
717
763
  } else {
718
- // 非跨商品券:只抵扣一个商品(剩余金额最高的),也受 applicableProductLimit 限制
764
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
719
765
  var targetProduct = applicableProducts.reduce(function (max, p) {
720
- return p[amountField].greaterThan(max[amountField]) ? p : max;
766
+ return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
721
767
  });
722
768
 
723
- // 动态计算当前可抵扣数量
724
- var _currentAvailableQty5 = Math.ceil(targetProduct[amountField].dividedBy(targetProduct[unitPriceField]).toNumber());
725
- var _availableQty2 = applicableProductLimit > 0 ? Math.min(_currentAvailableQty5, applicableProductLimit) : _currentAvailableQty5;
726
-
727
- // 计算本商品最大可抵扣金额 = min(数量 * 单价, 剩余金额)
728
- var _maxDeductForProduct2 = Decimal.min(targetProduct[unitPriceField].times(_availableQty2), targetProduct[amountField]);
769
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
770
+ var _maxDeductForProduct2 = targetProduct[amountField];
771
+ if (maxPassesPerItem > 0) {
772
+ _maxDeductForProduct2 = Decimal.min(targetProduct[unitPriceField].times(maxPassesPerItem), targetProduct[amountField]);
773
+ }
729
774
  var _actualDeductAmount2 = Decimal.min(deductionLeft, _maxDeductForProduct2);
730
775
 
731
776
  // 计算实际抵扣的数量
@@ -738,20 +783,17 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
738
783
  product_id: targetProduct.product_id,
739
784
  parent_product_id: targetProduct.parent_product_id || null,
740
785
  is_bundle_item: targetProduct.is_bundle_item || false,
786
+ lineKey: targetProduct._walletPassLineKey,
787
+ orderLineQuantity: getExpandedOrderLineQuantity(targetProduct),
741
788
  deductAmount: _actualDeductAmount2.toNumber(),
742
- // 转换为数字
743
- deductQuantity: _actualDeductQty2 // 抵扣涉及的数量(用于记录)
789
+ deductQuantity: _actualDeductQty2
744
790
  });
745
791
  }
746
792
  var totalDeducted = maxDeduction.minus(deductionLeft);
747
793
 
748
794
  // 更新订单剩余金额
749
795
  remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
750
-
751
- // 更新 maxPassesPerItem 追踪:记录每个被抵扣的商品行
752
- deductionDetails.forEach(function (detail) {
753
- incrementItemPassUsage(selectedVoucher.product_id, detail.product_id);
754
- });
796
+ applyMaxPassUsageIncrements(itemPassUsageMap, selectedVoucher.product_id, maxPassesPerItem, deductionDetails);
755
797
  selectedWithDetails.push(_objectSpread(_objectSpread({}, selectedVoucher), {}, {
756
798
  actualDeduction: totalDeducted.toNumber(),
757
799
  // 转换为数字
@@ -824,7 +866,7 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
824
866
  });
825
867
 
826
868
  // 按 maxPassesPerItem 过滤:排除已达到单商品可用卡券上限的商品
827
- applicableProducts = filterByMaxPassesPerItem(applicableProducts, product_id, maxPassesPerItem);
869
+ applicableProducts = filterByMaxPassesPerItemRecalc(applicableProducts, product_id, maxPassesPerItem);
828
870
  if (applicableProducts.length === 0) {
829
871
  isAvailable = false;
830
872
  reasonCode = 'not_meet_the_required_conditions';
@@ -864,15 +906,16 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
864
906
  }, new Decimal(0));
865
907
  }
866
908
  } else {
867
- // 非跨商品券:单个剩余金额最高的商品,也受 applicableProductLimit 限制
909
+ // 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
868
910
  var maxProduct = applicableProducts.reduce(function (max, p) {
869
- return p[amountField].greaterThan(max[amountField]) ? p : max;
911
+ return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
870
912
  });
871
- // 动态计算当前可抵扣数量
872
- var _currentAvailableQty6 = Math.ceil(maxProduct[amountField].dividedBy(maxProduct[unitPriceField]).toNumber());
873
- var _deductQty3 = applicableProductLimit > 0 ? Math.min(_currentAvailableQty6, applicableProductLimit) : _currentAvailableQty6;
874
- // 实际可抵扣金额 = min(数量 * 单价, 剩余金额)
875
- calculatedMaxAmount = Decimal.min(maxProduct[unitPriceField].times(_deductQty3), maxProduct[amountField]);
913
+ // maxPassesPerItem 限制每张券最多抵扣的单位数
914
+ if (maxPassesPerItem > 0) {
915
+ calculatedMaxAmount = Decimal.min(maxProduct[unitPriceField].times(maxPassesPerItem), maxProduct[amountField]);
916
+ } else {
917
+ calculatedMaxAmount = maxProduct[amountField];
918
+ }
876
919
  }
877
920
  calculatedMaxAmount = Decimal.min(baseAmount, calculatedMaxAmount, remainingOrderAmount);
878
921
  if (calculatedMaxAmount.lessThanOrEqualTo(0)) {
@@ -937,7 +980,7 @@ export var getProductDiscountDifference = function getProductDiscountDifference(
937
980
  * @returns 商品单价
938
981
  */
939
982
  export var getMainProductPrice = function getMainProductPrice(product, isDeductTaxAndFee) {
940
- var _product$metadata4, _product$metadata5, _product$metadata6;
983
+ var _product$metadata4, _product$metadata5, _product$metadata6, _product$metadata7, _product$metadata8;
941
984
  var mainProductPrice = new Decimal((product === null || product === void 0 ? void 0 : product.main_product_selling_price) || ((_product$metadata4 = product.metadata) === null || _product$metadata4 === void 0 ? void 0 : _product$metadata4.main_product_selling_price) || 0);
942
985
  var _iterator6 = _createForOfIteratorHelper((product === null || product === void 0 ? void 0 : product.product_bundle) || []),
943
986
  _step6;
@@ -957,14 +1000,14 @@ export var getMainProductPrice = function getMainProductPrice(product, isDeductT
957
1000
  } finally {
958
1001
  _iterator6.f();
959
1002
  }
960
- var taxFee = new Decimal((product === null || product === void 0 ? void 0 : product.tax_fee) || (product === null || product === void 0 || (_product$metadata5 = product.metadata) === null || _product$metadata5 === void 0 ? void 0 : _product$metadata5.main_product_attached_bundle_tax_fee) || 0);
1003
+ var taxFee = new Decimal((product === null || product === void 0 ? void 0 : product.tax_fee) || (product === null || product === void 0 || (_product$metadata5 = product.metadata) === null || _product$metadata5 === void 0 ? void 0 : _product$metadata5.main_product_attached_bundle_tax_fee) || 0).add((product === null || product === void 0 || (_product$metadata6 = product.metadata) === null || _product$metadata6 === void 0 ? void 0 : _product$metadata6.tax_fee_rounding_remainder) || 0);
961
1004
  if (product.is_price_include_tax === 1) {
962
1005
  taxFee = new Decimal(0);
963
1006
  }
964
1007
 
965
1008
  // 税费
966
1009
  // 附加费
967
- var surchargeFee = new Decimal((product === null || product === void 0 || (_product$metadata6 = product.metadata) === null || _product$metadata6 === void 0 ? void 0 : _product$metadata6.main_product_attached_bundle_surcharge_fee) || 0);
1010
+ var surchargeFee = new Decimal((product === null || product === void 0 || (_product$metadata7 = product.metadata) === null || _product$metadata7 === void 0 ? void 0 : _product$metadata7.main_product_attached_bundle_surcharge_fee) || 0).add((product === null || product === void 0 || (_product$metadata8 = product.metadata) === null || _product$metadata8 === void 0 ? void 0 : _product$metadata8.surcharge_rounding_remainder) || 0);
968
1011
 
969
1012
  // 税费附加费总额
970
1013
  var taxAndFeeTotal = taxFee.add(surchargeFee);
@@ -1046,8 +1089,9 @@ export var getBundleItemPrice = function getBundleItemPrice(bundleItem, parentQu
1046
1089
  */
1047
1090
  var expandProductsWithBundleItems = function expandProductsWithBundleItems(products, deductTaxAndFee) {
1048
1091
  var expandedProducts = [];
1049
- products.forEach(function (product) {
1092
+ products.forEach(function (product, indexInOrder) {
1050
1093
  var productQuantity = getProductQuantity(product);
1094
+ var parentLineKey = resolveWalletPassLineKey(product, indexInOrder);
1051
1095
 
1052
1096
  // 计算主商品单价(含税和不含税)
1053
1097
  var unitPriceWithTax = getMainProductPrice(product, true);
@@ -1062,6 +1106,8 @@ var expandProductsWithBundleItems = function expandProductsWithBundleItems(produ
1062
1106
  expandedProducts.push(_objectSpread(_objectSpread({}, product), {}, {
1063
1107
  is_bundle_item: false,
1064
1108
  parent_product_id: null,
1109
+ _walletPassLineKey: parentLineKey,
1110
+ _orderLineQuantity: productQuantity,
1065
1111
  // 单价(用于按 quantity 计算抵扣)
1066
1112
  unitPriceWithTax: new Decimal(unitPriceWithTax),
1067
1113
  unitPricePure: new Decimal(unitPricePure),
@@ -1078,6 +1124,7 @@ var expandProductsWithBundleItems = function expandProductsWithBundleItems(produ
1078
1124
  product.product_bundle.forEach(function (bundleItem) {
1079
1125
  if (getBundleItemIsOriginalPrice(bundleItem)) {
1080
1126
  var bundleQuantity = bundleItem.num * productQuantity;
1127
+ var bundleLineKey = "".concat(parentLineKey, "#bundle:").concat(bundleItem.bundle_id, ":").concat(bundleItem.bundle_product_id);
1081
1128
  // 计算子商品单价(注意:getBundleItemPrice 返回的是总价,需要除以数量得到单价)
1082
1129
  var bundleUnitPriceWithTax = new Decimal(getBundleItemPrice(bundleItem, 1, true)).dividedBy(bundleItem.num);
1083
1130
  var bundleUnitPricePure = new Decimal(getBundleItemPrice(bundleItem, 1, false)).dividedBy(bundleItem.num);
@@ -1095,6 +1142,8 @@ var expandProductsWithBundleItems = function expandProductsWithBundleItems(produ
1095
1142
  parent_product_id: product.product_id,
1096
1143
  quantity: bundleQuantity,
1097
1144
  // 子商品数量 * 主商品数量
1145
+ _walletPassLineKey: bundleLineKey,
1146
+ _orderLineQuantity: bundleQuantity,
1098
1147
  // 单价(用于按 quantity 计算抵扣)
1099
1148
  unitPriceWithTax: bundleUnitPriceWithTax,
1100
1149
  unitPricePure: bundleUnitPricePure,
@@ -1112,15 +1161,6 @@ var expandProductsWithBundleItems = function expandProductsWithBundleItems(produ
1112
1161
  return expandedProducts;
1113
1162
  };
1114
1163
 
1115
- /**
1116
- * 获取商品数量
1117
- * @param product 商品
1118
- * @returns 商品数量
1119
- */
1120
- export var getProductQuantity = function getProductQuantity(product) {
1121
- return product.quantity || product.product_quantity || 1;
1122
- };
1123
-
1124
1164
  // bundle商品是否是原价
1125
1165
  export var getBundleItemIsOriginalPrice = function getBundleItemIsOriginalPrice(item) {
1126
1166
  return (item === null || item === void 0 ? void 0 : item.price_type) === 'markup' && (item === null || item === void 0 ? void 0 : item.price_type_ext) === 'product_price';
@@ -0,0 +1,119 @@
1
+ import { StrategyConfig, RuntimeContext, EvaluationResult, EngineOptions, OperatorHandler } from './type';
2
+ /**
3
+ * 策略模型 - 核心实现
4
+ *
5
+ * 完全业务无关的通用策略引擎
6
+ * 支持条件评估、动作匹配、结果返回
7
+ */
8
+ /**
9
+ * 策略引擎
10
+ *
11
+ * 核心职责:
12
+ * 1. 递归评估条件组
13
+ * 2. 收集满足条件的 actionIds
14
+ * 3. 根据 actionIds 获取 ActionEffect 对象
15
+ * 4. 按 priority 排序返回
16
+ */
17
+ export declare class StrategyEngine {
18
+ private options;
19
+ private operatorHandlers;
20
+ constructor(options?: EngineOptions);
21
+ /**
22
+ * 评估策略
23
+ */
24
+ evaluate(config: StrategyConfig, context: RuntimeContext): EvaluationResult;
25
+ /**
26
+ * 递归评估条件组
27
+ *
28
+ * @param group 条件组
29
+ * @param context 运行时上下文
30
+ * @returns 评估结果对象,包含条件是否满足、收集到的 actionIds、以及失败的原子规则信息
31
+ */
32
+ private evaluateConditionGroup;
33
+ /**
34
+ * 评估条件组的逻辑运算(带失败规则收集)
35
+ *
36
+ * 失败规则收集策略:
37
+ * - `and`:返回第一个失败的原子规则(嵌套 group 递归取其首个失败原子规则)
38
+ * - `or`:所有分支失败时,聚合每个分支的失败规则
39
+ * - `not`:被取反规则实际通过时,记录该规则
40
+ */
41
+ private evaluateGroupLogicDetailed;
42
+ /**
43
+ * 评估单个规则(带失败规则收集)
44
+ */
45
+ private evaluateRuleDetailed;
46
+ /**
47
+ * 评估原子规则(不收集失败信息,仅返回布尔结果)
48
+ */
49
+ private evaluateAtomicRule;
50
+ /**
51
+ * 构建失败规则信息
52
+ */
53
+ private buildFailedRuleInfo;
54
+ /**
55
+ * 评估代码模式条件
56
+ */
57
+ private evaluateCodeCondition;
58
+ /**
59
+ * 评估运算符模式条件
60
+ */
61
+ private evaluateOperatorCondition;
62
+ /**
63
+ * 获取字段值
64
+ */
65
+ private getFieldValue;
66
+ /**
67
+ * 评估内置运算符
68
+ */
69
+ private evaluateBuiltInOperator;
70
+ /**
71
+ * 根据 actionIds 获取 ActionEffect 对象
72
+ */
73
+ private getActionsByIds;
74
+ /**
75
+ * 按 priority 排序(降序)
76
+ */
77
+ private sortActionsByPriority;
78
+ /**
79
+ * 判断是否为条件组
80
+ */
81
+ private isConditionGroup;
82
+ /**
83
+ * 验证配置
84
+ */
85
+ private validateConfig;
86
+ /**
87
+ * 初始化内置运算符处理器
88
+ */
89
+ private initializeBuiltInOperators;
90
+ /**
91
+ * 评估商品匹配运算符
92
+ *
93
+ * 匹配规则:
94
+ * - 首先匹配 product_id
95
+ * - 如果配置的 product_variant_id = 0,只需要 product_id 匹配(表示任意变体)
96
+ * - 如果配置的 product_variant_id != 0,还需要 product_variant_id 精确匹配
97
+ *
98
+ * @param fieldValue 实际商品 { product_id: number, product_variant_id: number }
99
+ * @param configValue 配置的商品列表 Array<{ product_id: number, product_variant_id: number }>
100
+ * @returns 是否匹配
101
+ */
102
+ private evaluateProductMatch;
103
+ /**
104
+ * 注册自定义运算符
105
+ */
106
+ registerOperator(operator: string, handler: OperatorHandler): void;
107
+ /**
108
+ * 获取调试信息
109
+ */
110
+ getDebugInfo(): Record<string, any>;
111
+ }
112
+ /**
113
+ * 创建策略引擎实例
114
+ */
115
+ export declare function createStrategyEngine(options?: EngineOptions): StrategyEngine;
116
+ /**
117
+ * 快速评估(使用默认引擎配置)
118
+ */
119
+ export declare function evaluate(config: StrategyConfig, context: RuntimeContext): EvaluationResult;