@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.
- package/dist/model/strategy/adapter/index.d.ts +4 -0
- package/dist/model/strategy/adapter/index.js +4 -1
- package/dist/model/strategy/adapter/itemRule/adapter.d.ts +67 -0
- package/dist/model/strategy/adapter/itemRule/adapter.js +483 -0
- package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +43 -0
- package/dist/model/strategy/adapter/itemRule/evaluator.js +169 -0
- package/dist/model/strategy/adapter/itemRule/examples.d.ts +79 -0
- package/dist/model/strategy/adapter/itemRule/examples.js +382 -0
- package/dist/model/strategy/adapter/itemRule/index.d.ts +5 -0
- package/dist/model/strategy/adapter/itemRule/index.js +5 -0
- package/dist/model/strategy/adapter/itemRule/type.d.ts +225 -0
- package/dist/model/strategy/adapter/itemRule/type.js +101 -0
- package/dist/model/strategy/adapter/promotion/adapter.d.ts +66 -0
- package/dist/model/strategy/adapter/promotion/adapter.js +271 -0
- package/dist/model/strategy/adapter/promotion/evaluator.d.ts +213 -0
- package/dist/model/strategy/adapter/promotion/evaluator.js +1279 -0
- package/dist/model/strategy/adapter/promotion/examples.d.ts +138 -0
- package/dist/model/strategy/adapter/promotion/examples.js +166 -0
- package/dist/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/dist/model/strategy/adapter/promotion/index.js +0 -0
- package/dist/model/strategy/adapter/promotion/type.d.ts +447 -0
- package/dist/model/strategy/adapter/promotion/type.js +209 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.js +67 -10
- package/dist/model/strategy/adapter/walletPass/locales.js +12 -3
- package/dist/model/strategy/adapter/walletPass/type.d.ts +20 -2
- package/dist/model/strategy/adapter/walletPass/utils.d.ts +6 -6
- package/dist/model/strategy/adapter/walletPass/utils.js +143 -103
- package/dist/model/strategy/engine.d.ts +119 -0
- package/dist/model/strategy/engine.js +711 -0
- package/dist/model/strategy/index.d.ts +2 -93
- package/dist/model/strategy/index.js +6 -549
- package/dist/model/strategy/type.d.ts +17 -0
- package/dist/model/strategy/type.js +4 -0
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +1 -1
- package/lib/model/strategy/adapter/index.d.ts +4 -0
- package/lib/model/strategy/adapter/index.js +12 -0
- package/lib/model/strategy/adapter/itemRule/adapter.d.ts +67 -0
- package/lib/model/strategy/adapter/itemRule/adapter.js +377 -0
- package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +43 -0
- package/lib/model/strategy/adapter/itemRule/evaluator.js +124 -0
- package/lib/model/strategy/adapter/itemRule/examples.d.ts +79 -0
- package/lib/model/strategy/adapter/itemRule/examples.js +316 -0
- package/lib/model/strategy/adapter/itemRule/index.d.ts +5 -0
- package/lib/model/strategy/adapter/itemRule/index.js +60 -0
- package/lib/model/strategy/adapter/itemRule/type.d.ts +225 -0
- package/lib/model/strategy/adapter/itemRule/type.js +46 -0
- package/lib/model/strategy/adapter/promotion/adapter.d.ts +66 -0
- package/lib/model/strategy/adapter/promotion/adapter.js +217 -0
- package/lib/model/strategy/adapter/promotion/evaluator.d.ts +213 -0
- package/lib/model/strategy/adapter/promotion/evaluator.js +893 -0
- package/lib/model/strategy/adapter/promotion/examples.d.ts +138 -0
- package/lib/model/strategy/adapter/promotion/examples.js +192 -0
- package/lib/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/lib/model/strategy/adapter/promotion/index.js +0 -0
- package/lib/model/strategy/adapter/promotion/type.d.ts +447 -0
- package/lib/model/strategy/adapter/promotion/type.js +51 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.js +38 -5
- package/lib/model/strategy/adapter/walletPass/locales.js +12 -3
- package/lib/model/strategy/adapter/walletPass/type.d.ts +20 -2
- package/lib/model/strategy/adapter/walletPass/utils.d.ts +6 -6
- package/lib/model/strategy/adapter/walletPass/utils.js +153 -82
- package/lib/model/strategy/engine.d.ts +119 -0
- package/lib/model/strategy/engine.js +514 -0
- package/lib/model/strategy/index.d.ts +2 -93
- package/lib/model/strategy/index.js +6 -381
- package/lib/model/strategy/type.d.ts +17 -0
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +1 -1
- 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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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;
|
|
186
|
+
if (maxPassesPerItem <= 0) return products;
|
|
140
187
|
return products.filter(function (p) {
|
|
141
|
-
|
|
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[
|
|
271
|
+
return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
|
|
223
272
|
});
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
480
|
+
// 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
|
|
430
481
|
var maxProduct = applicableProducts.reduce(function (max, p) {
|
|
431
|
-
return p[
|
|
482
|
+
return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
|
|
432
483
|
});
|
|
433
|
-
//
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
|
463
|
-
var availableQty = Math.min(
|
|
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
|
-
//
|
|
545
|
+
// 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
|
|
492
546
|
var targetProduct = applicableProducts.reduce(function (max, p) {
|
|
493
|
-
return p[
|
|
547
|
+
return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
|
|
494
548
|
});
|
|
495
549
|
|
|
496
|
-
//
|
|
497
|
-
var
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
|
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(
|
|
667
|
+
return ((_itemPassUsageMap$get = itemPassUsageMap.get(walletPassProductId)) === null || _itemPassUsageMap$get === void 0 ? void 0 : _itemPassUsageMap$get.get(lineKey)) || 0;
|
|
619
668
|
};
|
|
620
|
-
var
|
|
621
|
-
if (
|
|
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
|
-
|
|
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 =
|
|
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
|
-
//
|
|
764
|
+
// 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
|
|
719
765
|
var targetProduct = applicableProducts.reduce(function (max, p) {
|
|
720
|
-
return p[
|
|
766
|
+
return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
|
|
721
767
|
});
|
|
722
768
|
|
|
723
|
-
//
|
|
724
|
-
var
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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 =
|
|
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
|
-
//
|
|
909
|
+
// 非跨商品券:选择单价最高的商品(优先抵扣高价商品)
|
|
868
910
|
var maxProduct = applicableProducts.reduce(function (max, p) {
|
|
869
|
-
return p[
|
|
911
|
+
return p[unitPriceField].greaterThan(max[unitPriceField]) ? p : max;
|
|
870
912
|
});
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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$
|
|
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;
|