@pisell/pisellos 2.2.308 → 2.2.310
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/promotion/index.js +9 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
- package/dist/modules/BookingContext/index.js +4 -4
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
- package/dist/modules/Customer/index.js +6 -3
- package/dist/modules/Customer/types.d.ts +2 -0
- package/dist/modules/Discount/availability.d.ts +7 -0
- package/dist/modules/Discount/availability.js +49 -0
- package/dist/modules/Discount/entitlementPass.d.ts +34 -0
- package/dist/modules/Discount/entitlementPass.js +147 -0
- package/dist/modules/Discount/index.d.ts +2 -2
- package/dist/modules/Discount/index.js +37 -151
- package/dist/modules/Discount/types.d.ts +36 -5
- package/dist/modules/OpenData/index.d.ts +1 -1
- package/dist/modules/OpenData/index.js +21 -12
- package/dist/modules/Order/index.d.ts +33 -4
- package/dist/modules/Order/index.js +830 -411
- package/dist/modules/Order/types.d.ts +17 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
- package/dist/modules/Order/utils/weighedProduct.d.ts +12 -0
- package/dist/modules/Order/utils/weighedProduct.js +94 -0
- package/dist/modules/Order/utils.d.ts +14 -2
- package/dist/modules/Order/utils.js +280 -79
- package/dist/modules/Payment/index.d.ts +0 -5
- package/dist/modules/Payment/index.js +35 -85
- package/dist/modules/Payment/types.d.ts +21 -1
- package/dist/modules/Payment/types.js +2 -0
- package/dist/modules/Payment/walletpass.d.ts +1 -1
- package/dist/modules/Payment/walletpass.js +80 -47
- package/dist/modules/ProductList/index.js +18 -5
- package/dist/modules/Rules/index.d.ts +2 -2
- package/dist/modules/Rules/index.js +484 -188
- package/dist/modules/Rules/types.d.ts +6 -0
- package/dist/server/index.js +8 -5
- package/dist/server/modules/order/index.js +6 -0
- package/dist/server/modules/order/types.d.ts +12 -0
- package/dist/server/modules/payment/index.d.ts +25 -6
- package/dist/server/modules/payment/index.js +660 -133
- package/dist/server/utils/small-ticket.d.ts +8 -5
- package/dist/server/utils/small-ticket.js +1269 -199
- package/dist/solution/BaseSales/index.d.ts +11 -2
- package/dist/solution/BaseSales/index.js +1463 -1173
- package/dist/solution/BaseSales/types.d.ts +7 -3
- package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +40 -25
- package/dist/solution/BaseSales/utils.js +3 -2
- package/dist/solution/BookingTicket/index.d.ts +11 -2
- package/dist/solution/BookingTicket/index.js +663 -542
- package/dist/solution/BookingTicket/types.d.ts +5 -1
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +338 -147
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +19 -6
- package/dist/solution/ScanOrder/utils.js +3 -2
- package/dist/solution/ShopDiscount/index.d.ts +2 -0
- package/dist/solution/ShopDiscount/index.js +99 -42
- package/dist/solution/ShopDiscount/utils.d.ts +4 -2
- package/dist/solution/ShopDiscount/utils.js +43 -23
- package/dist/solution/UnifiedBookingSales/index.d.ts +2 -2
- package/dist/solution/UnifiedBookingSales/index.js +5 -5
- package/dist/utils/os-private-payload.d.ts +5 -0
- package/dist/utils/os-private-payload.js +17 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
- package/lib/modules/BookingContext/index.js +3 -3
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
- package/lib/modules/Customer/index.js +5 -1
- package/lib/modules/Customer/types.d.ts +2 -0
- package/lib/modules/Discount/availability.d.ts +7 -0
- package/lib/modules/Discount/availability.js +43 -0
- package/lib/modules/Discount/entitlementPass.d.ts +34 -0
- package/lib/modules/Discount/entitlementPass.js +141 -0
- package/lib/modules/Discount/index.d.ts +2 -2
- package/lib/modules/Discount/index.js +23 -129
- package/lib/modules/Discount/types.d.ts +36 -5
- package/lib/modules/OpenData/index.d.ts +1 -1
- package/lib/modules/OpenData/index.js +11 -2
- package/lib/modules/Order/index.d.ts +33 -4
- package/lib/modules/Order/index.js +411 -81
- package/lib/modules/Order/types.d.ts +17 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
- package/lib/modules/Order/utils/weighedProduct.d.ts +12 -0
- package/lib/modules/Order/utils/weighedProduct.js +89 -0
- package/lib/modules/Order/utils.d.ts +14 -2
- package/lib/modules/Order/utils.js +189 -5
- package/lib/modules/Payment/index.d.ts +0 -5
- package/lib/modules/Payment/index.js +3 -28
- package/lib/modules/Payment/types.d.ts +21 -1
- package/lib/modules/Payment/types.js +1 -0
- package/lib/modules/Payment/walletpass.d.ts +1 -1
- package/lib/modules/Payment/walletpass.js +29 -9
- package/lib/modules/ProductList/index.js +14 -1
- package/lib/modules/Rules/index.d.ts +2 -2
- package/lib/modules/Rules/index.js +361 -112
- package/lib/modules/Rules/types.d.ts +6 -0
- package/lib/server/index.js +8 -5
- package/lib/server/modules/order/index.js +6 -0
- package/lib/server/modules/order/types.d.ts +12 -0
- package/lib/server/modules/payment/index.d.ts +25 -6
- package/lib/server/modules/payment/index.js +350 -78
- package/lib/server/utils/small-ticket.d.ts +8 -5
- package/lib/server/utils/small-ticket.js +1009 -121
- package/lib/solution/BaseSales/index.d.ts +11 -2
- package/lib/solution/BaseSales/index.js +164 -30
- package/lib/solution/BaseSales/types.d.ts +7 -3
- package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +24 -9
- package/lib/solution/BaseSales/utils.js +2 -1
- package/lib/solution/BookingTicket/index.d.ts +11 -2
- package/lib/solution/BookingTicket/index.js +103 -41
- package/lib/solution/BookingTicket/types.d.ts +5 -1
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +174 -42
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +11 -3
- package/lib/solution/ScanOrder/utils.js +2 -1
- package/lib/solution/ShopDiscount/index.d.ts +2 -0
- package/lib/solution/ShopDiscount/index.js +55 -9
- package/lib/solution/ShopDiscount/utils.d.ts +4 -2
- package/lib/solution/ShopDiscount/utils.js +42 -25
- package/lib/solution/UnifiedBookingSales/index.d.ts +2 -2
- package/lib/solution/UnifiedBookingSales/index.js +5 -5
- package/lib/utils/os-private-payload.d.ts +5 -0
- package/lib/utils/os-private-payload.js +22 -0
- package/package.json +1 -1
|
@@ -11,6 +11,8 @@ var _utils2 = require("../Cart/utils");
|
|
|
11
11
|
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
12
12
|
var _lodashEs = require("lodash-es");
|
|
13
13
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
14
|
+
var _entitlementPass = require("../Discount/entitlementPass");
|
|
15
|
+
var _availability = require("../Discount/availability");
|
|
14
16
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
15
17
|
class RulesModule extends _BaseModule.BaseModule {
|
|
16
18
|
defaultName = 'rules';
|
|
@@ -49,12 +51,20 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
+
* 商品未开启 Holder 选择时跳过匹配;已开启时,特定 form Holder 卡
|
|
55
|
+
* 只适用于同一 Holder Record,是否必填不影响匹配规则。
|
|
54
56
|
*/
|
|
55
|
-
checkHolderMatch(discount, product, holders) {
|
|
57
|
+
checkHolderMatch(discount, product, holderSource, holders) {
|
|
56
58
|
// 非表单类型 holder 视为匹配
|
|
57
59
|
if (discount.holder?.holder_type !== 'form') return true;
|
|
60
|
+
|
|
61
|
+
// getProduct 会裁剪商品字段,从原始商品读取配置,套餐子项沿用父商品。
|
|
62
|
+
const holderConfig = holderSource?.holder_config ?? holderSource?.metadata?.holder_config ?? holderSource?.origin?.holder_config ?? holderSource?.metadata?.origin?.holder_config ?? holderSource?._origin?.holder_config ?? holderSource?._extend?.holder_config ?? holderSource?._extend?.product?.holder_config ?? holderSource?._extend?.product?.metadata?.holder_config;
|
|
63
|
+
const holderStatus = holderConfig?.status;
|
|
64
|
+
const holderEnabled = holderStatus === true || ['enable', '1'].includes(String(holderStatus).trim().toLowerCase());
|
|
65
|
+
if (!holderEnabled || holderConfig?.resource_id === undefined || holderConfig?.resource_id === null || holderConfig?.resource_id === '') {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
58
68
|
const discountHolderIds = this.normalizeHolderRecordIds(discount.holder.holder_id);
|
|
59
69
|
if (!discountHolderIds.length) return false;
|
|
60
70
|
const productHolderIds = this.normalizeHolderRecordIds(product.holder_id);
|
|
@@ -94,10 +104,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
94
104
|
unavailableReason: _types.UnavailableReason.AlreadyUsed
|
|
95
105
|
};
|
|
96
106
|
}
|
|
107
|
+
const scannedEntitlementPassIds = newDiscountList.filter(discount => (0, _entitlementPass.isEntitlementPass)(discount)).map(discount => discount.id);
|
|
108
|
+
const scannedEntitlementPassIdSet = new Set(scannedEntitlementPassIds.map(String));
|
|
97
109
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
const filteredOldDiscountList = oldDiscountList.filter(discount => !discount.isEditMode && (discount
|
|
110
|
+
// 客户次卡必须保留在完整列表中;同一实例以本次扫码数据更新。
|
|
111
|
+
// 历史折扣不参与试算,商品券继续沿用仅保留扫码来源的规则。
|
|
112
|
+
const filteredOldDiscountList = oldDiscountList.filter(discount => !discount.isEditMode && (!(0, _entitlementPass.isFullCoveragePass)(discount) || (0, _entitlementPass.isEntitlementPass)(discount) || discount.isScan) && !scannedEntitlementPassIdSet.has(String(discount.id)));
|
|
101
113
|
const mergedDiscountList = (0, _utils.uniqueById)([...filteredOldDiscountList, ...newDiscountList]);
|
|
102
114
|
const result = this.calcDiscount({
|
|
103
115
|
discountList: mergedDiscountList,
|
|
@@ -106,7 +118,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
106
118
|
holders,
|
|
107
119
|
isFormSubject
|
|
108
120
|
}, {
|
|
109
|
-
scan: true
|
|
121
|
+
scan: true,
|
|
122
|
+
preferredEntitlementPassIds: scannedEntitlementPassIds
|
|
110
123
|
});
|
|
111
124
|
|
|
112
125
|
// 检查是否有产品使用了新折扣
|
|
@@ -206,12 +219,14 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
206
219
|
isFormSubject,
|
|
207
220
|
orderTotalAmount
|
|
208
221
|
}, options) {
|
|
222
|
+
const preferredEntitlementPassIds = new Set((options?.preferredEntitlementPassIds || []).map(String));
|
|
209
223
|
const resolveIsFormSubject = product => typeof isFormSubject === 'function' ? Boolean(isFormSubject(product)) : Boolean(isFormSubject);
|
|
210
224
|
|
|
211
225
|
// 识别出来是不是在编辑的界面里又新增了商品
|
|
212
226
|
// 这种情况下,如果有可用的优惠券,也会自动勾选上
|
|
213
227
|
const isEditModeAddNewProduct = productList.find(n => n.booking_id) && productList.find(n => !n.booking_id);
|
|
214
|
-
const
|
|
228
|
+
const reapplyWeighedDiscountProductUids = new Set((options?.reapplyWeighedDiscountProductUids || []).map(String));
|
|
229
|
+
const reapplyBookingDiscountProductUids = new Set([...(options?.reapplyBookingDiscountProductUids || []), ...reapplyWeighedDiscountProductUids].filter(uid => uid !== undefined && uid !== null && uid !== '').map(String));
|
|
215
230
|
const getOriginProductUid = originProduct => {
|
|
216
231
|
const uid = originProduct?.metadata?.unique_identification_number ?? originProduct?.unique_identification_number;
|
|
217
232
|
if (uid === undefined || uid === null || uid === '') return undefined;
|
|
@@ -222,7 +237,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
222
237
|
const uid = getOriginProductUid(originProduct);
|
|
223
238
|
if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
|
|
224
239
|
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
225
|
-
return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ['good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
240
|
+
return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '');
|
|
226
241
|
};
|
|
227
242
|
const resolveReapplyBookingDiscountPercent = (originProduct, selectedDiscount) => {
|
|
228
243
|
if (!shouldReapplyBookingDiscount(originProduct, selectedDiscount)) return undefined;
|
|
@@ -239,7 +254,13 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
239
254
|
if (discount.isEditMode) {
|
|
240
255
|
editModeDiscount.push(discount);
|
|
241
256
|
} else {
|
|
242
|
-
|
|
257
|
+
// 试算不能把 config / 不可用原因写回原有选券;每次重新计算以清除旧原因。
|
|
258
|
+
addModeDiscount.push({
|
|
259
|
+
...discount,
|
|
260
|
+
usageLimitFailKey: (0, _availability.getDiscountUsageLimitFailKey)(discount),
|
|
261
|
+
reason: undefined,
|
|
262
|
+
reasonCode: (0, _availability.getDiscountUsageBlockReason)(discount)
|
|
263
|
+
});
|
|
243
264
|
}
|
|
244
265
|
});
|
|
245
266
|
|
|
@@ -268,7 +289,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
268
289
|
}
|
|
269
290
|
const canUseEditModeDiscountForReapply = discount => {
|
|
270
291
|
const discountType = discount.tag || discount.type;
|
|
271
|
-
return reapplyBookingDiscountProductUids.size > 0 && discount.isEditMode === true && discount.isSelected === true && ['good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
292
|
+
return reapplyBookingDiscountProductUids.size > 0 && discount.isEditMode === true && discount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '');
|
|
272
293
|
};
|
|
273
294
|
|
|
274
295
|
// 过滤掉手动取消false的优惠券,但仅用于应用优惠逻辑。
|
|
@@ -342,6 +363,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
342
363
|
|
|
343
364
|
// 辅助函数:判断是否是商品券
|
|
344
365
|
const isGoodPass = discount => discount.tag === 'good_pass';
|
|
366
|
+
const isEntitlement = discount => (0, _entitlementPass.isEntitlementPass)(discount);
|
|
345
367
|
|
|
346
368
|
// 辅助函数:判断是否是订单级折扣卡
|
|
347
369
|
const isOrderLevelDiscount = discount => discount.tag === 'product_discount_card' && discount.metadata?.discount_calculation_mode === 'order_level';
|
|
@@ -403,6 +425,14 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
403
425
|
}
|
|
404
426
|
return 0;
|
|
405
427
|
}
|
|
428
|
+
const entitlementA = isEntitlement(a);
|
|
429
|
+
const entitlementB = isEntitlement(b);
|
|
430
|
+
if (entitlementA || entitlementB) {
|
|
431
|
+
if (entitlementA && entitlementB) {
|
|
432
|
+
return (0, _entitlementPass.compareEntitlementPassPriority)(a, b);
|
|
433
|
+
}
|
|
434
|
+
return entitlementA ? -1 : 1;
|
|
435
|
+
}
|
|
406
436
|
|
|
407
437
|
// 1. 先按优先级排序
|
|
408
438
|
const priorityA = getPriority(a);
|
|
@@ -471,19 +501,34 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
471
501
|
|
|
472
502
|
// 🔥 为每个折扣卡/商品券执行策略检查并附加config
|
|
473
503
|
const evaluator = this.getWalletPassEvaluator();
|
|
504
|
+
addModeDiscount.forEach(discount => {
|
|
505
|
+
if (discount.reasonCode) discount.config = {
|
|
506
|
+
isAvailable: false
|
|
507
|
+
};
|
|
508
|
+
});
|
|
474
509
|
if (evaluator) {
|
|
475
510
|
addModeDiscount.forEach(discount => {
|
|
511
|
+
// 即使没有匹配的动态策略,服务端状态和次数限制也不能被覆盖为可用。
|
|
512
|
+
if (discount.reasonCode) return;
|
|
476
513
|
const discountType = discount.tag || discount.type;
|
|
477
|
-
//
|
|
478
|
-
if (['good_pass', 'discount_card', 'product_discount_card'].includes(discountType)) {
|
|
514
|
+
// 对两类全额抵扣卡和折扣卡执行附加策略检查。
|
|
515
|
+
if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '')) {
|
|
516
|
+
const entitlementQuota = (0, _entitlementPass.isEntitlementPass)(discount) ? (0, _entitlementPass.resolveEntitlementQuota)(discount) : null;
|
|
517
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount) && !entitlementQuota) {
|
|
518
|
+
discount.config = {
|
|
519
|
+
isAvailable: false
|
|
520
|
+
};
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
479
524
|
// 转换 discount 为 Voucher 格式
|
|
480
525
|
const voucher = {
|
|
481
526
|
id: discount.id,
|
|
482
527
|
amount: Number(discount.par_value || 0),
|
|
483
|
-
balance: Number(discount.balance || 0),
|
|
528
|
+
balance: entitlementQuota?.type === 'unlimited' ? Number.MAX_SAFE_INTEGER : entitlementQuota?.type === 'limited' ? entitlementQuota.balance : Number(discount.balance || 0),
|
|
484
529
|
type: discountType,
|
|
485
530
|
product_id: discount.product_id,
|
|
486
|
-
unified_available_status: 1,
|
|
531
|
+
unified_available_status: discount.unified_available_status ?? 1,
|
|
487
532
|
available_product_type: discount.limited_relation_product_data?.type,
|
|
488
533
|
available_product_ids: discount.limited_relation_product_data?.product_ids
|
|
489
534
|
};
|
|
@@ -514,6 +559,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
514
559
|
discount.config = {
|
|
515
560
|
isAvailable: false
|
|
516
561
|
};
|
|
562
|
+
discount.reason = result.reason;
|
|
563
|
+
discount.reasonCode = result.reasonCode || 'not_meet_the_required_conditions';
|
|
517
564
|
}
|
|
518
565
|
}
|
|
519
566
|
});
|
|
@@ -536,17 +583,126 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
536
583
|
});
|
|
537
584
|
*/
|
|
538
585
|
|
|
539
|
-
//
|
|
586
|
+
// good_pass / 折扣卡保持原有的一次性使用标记。
|
|
540
587
|
const usedDiscounts = new Map();
|
|
541
588
|
|
|
589
|
+
// Entitlement Pass 单独记录本次重算新增分配的次数。
|
|
590
|
+
const entitlementUsageCounts = new Map();
|
|
591
|
+
|
|
542
592
|
// 🔥 统计每个 product_id(商品券商品id)已使用的数量,用于 maxUsagePerOrder 限制
|
|
543
593
|
const usedProductIdCounts = new Map();
|
|
594
|
+
const markDiscountUsed = (discount, count = 1) => {
|
|
595
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
596
|
+
entitlementUsageCounts.set(discount.id, (entitlementUsageCounts.get(discount.id) || 0) + count);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
usedDiscounts.set(discount.id, true);
|
|
600
|
+
};
|
|
601
|
+
const isDiscountUsed = discount => {
|
|
602
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
603
|
+
return (entitlementUsageCounts.get(discount.id) || 0) > 0;
|
|
604
|
+
}
|
|
605
|
+
return usedDiscounts.get(discount.id) === true;
|
|
606
|
+
};
|
|
607
|
+
const buildFullCoveragePassUsageGroups = (discounts, requestedCount) => {
|
|
608
|
+
const groups = [];
|
|
609
|
+
const pendingUsedDiscounts = new Set(Array.from(usedDiscounts.entries()).filter(([, used]) => used).map(([discountId]) => discountId));
|
|
610
|
+
const pendingEntitlementCounts = new Map(entitlementUsageCounts);
|
|
611
|
+
const pendingProductCounts = new Map(usedProductIdCounts);
|
|
612
|
+
const firstGoodPass = discounts.find(discount => (0, _entitlementPass.isFullCoveragePass)(discount) && !(0, _entitlementPass.isEntitlementPass)(discount));
|
|
613
|
+
const legacyGoodPassPerLineLimit = firstGoodPass ? Number(firstGoodPass.config?.maxUsagePerOrder || 0) : 0;
|
|
614
|
+
let allocatedGoodPassCount = 0;
|
|
615
|
+
let remaining = Math.max(Math.floor(requestedCount), 0);
|
|
616
|
+
for (const discount of discounts) {
|
|
617
|
+
if (remaining <= 0) break;
|
|
618
|
+
if (!(0, _entitlementPass.isFullCoveragePass)(discount)) continue;
|
|
619
|
+
const currentEntitlementCount = pendingEntitlementCounts.get(discount.id) || 0;
|
|
620
|
+
let availableCount = (0, _entitlementPass.isEntitlementPass)(discount) ? (0, _entitlementPass.getRemainingEntitlementUses)(discount, currentEntitlementCount, remaining) : pendingUsedDiscounts.has(discount.id) ? 0 : 1;
|
|
621
|
+
if (!(0, _entitlementPass.isEntitlementPass)(discount) && legacyGoodPassPerLineLimit > 0) {
|
|
622
|
+
availableCount = Math.min(availableCount, Math.max(Math.floor(legacyGoodPassPerLineLimit) - allocatedGoodPassCount, 0));
|
|
623
|
+
}
|
|
624
|
+
const maxUsagePerOrder = Number(discount.config?.maxUsagePerOrder || 0);
|
|
625
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount) && maxUsagePerOrder > 0) {
|
|
626
|
+
const currentProductCount = pendingProductCounts.get(discount.product_id) || 0;
|
|
627
|
+
availableCount = Math.min(availableCount, Math.max(Math.floor(maxUsagePerOrder) - currentProductCount, 0));
|
|
628
|
+
}
|
|
629
|
+
const allocatedCount = Math.min(availableCount, remaining);
|
|
630
|
+
if (allocatedCount <= 0) continue;
|
|
631
|
+
const existingGroup = groups.find(group => String(group.discount.id) === String(discount.id));
|
|
632
|
+
if (existingGroup) {
|
|
633
|
+
existingGroup.count += allocatedCount;
|
|
634
|
+
} else {
|
|
635
|
+
groups.push({
|
|
636
|
+
discount,
|
|
637
|
+
count: allocatedCount
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
641
|
+
pendingEntitlementCounts.set(discount.id, currentEntitlementCount + allocatedCount);
|
|
642
|
+
} else {
|
|
643
|
+
pendingUsedDiscounts.add(discount.id);
|
|
644
|
+
allocatedGoodPassCount += allocatedCount;
|
|
645
|
+
}
|
|
646
|
+
pendingProductCounts.set(discount.product_id, (pendingProductCounts.get(discount.product_id) || 0) + allocatedCount);
|
|
647
|
+
remaining -= allocatedCount;
|
|
648
|
+
}
|
|
649
|
+
return groups;
|
|
650
|
+
};
|
|
651
|
+
const mergeEntitlementAppliedProductDetails = details => {
|
|
652
|
+
const mergedDetails = [];
|
|
653
|
+
const mergedIndexByKey = new Map();
|
|
654
|
+
const toComparableNumber = value => {
|
|
655
|
+
const numberValue = Number(value);
|
|
656
|
+
return Number.isFinite(numberValue) ? String(numberValue) : String(value ?? '');
|
|
657
|
+
};
|
|
658
|
+
for (const detail of details || []) {
|
|
659
|
+
if (!(0, _entitlementPass.isEntitlementPass)(detail)) {
|
|
660
|
+
mergedDetails.push(detail);
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
const resourceId = detail?.discount?.resource_id ?? detail?.resource_id;
|
|
664
|
+
if (resourceId === undefined || resourceId === null) {
|
|
665
|
+
mergedDetails.push(detail);
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
const key = [resourceId, detail?.discount?.product_id ?? '', detail?.metadata?.custom_product_bundle_map_id ?? '', toComparableNumber(detail?.amount), toComparableNumber(detail?.discount?.fixed_amount)].join('|');
|
|
669
|
+
const rawUsageCount = Number(detail?.metadata?.num ?? detail?._num ?? 1);
|
|
670
|
+
const usageCount = Number.isFinite(rawUsageCount) && rawUsageCount > 0 ? rawUsageCount : 1;
|
|
671
|
+
const mergedIndex = mergedIndexByKey.get(key);
|
|
672
|
+
if (mergedIndex === undefined) {
|
|
673
|
+
mergedIndexByKey.set(key, mergedDetails.length);
|
|
674
|
+
mergedDetails.push({
|
|
675
|
+
...detail,
|
|
676
|
+
metadata: {
|
|
677
|
+
...(detail?.metadata || {}),
|
|
678
|
+
num: usageCount
|
|
679
|
+
},
|
|
680
|
+
_num: usageCount
|
|
681
|
+
});
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
const existed = mergedDetails[mergedIndex];
|
|
685
|
+
const existedUsageCount = Number(existed?.metadata?.num ?? existed?._num ?? 1);
|
|
686
|
+
const totalUsageCount = existedUsageCount + usageCount;
|
|
687
|
+
mergedDetails[mergedIndex] = {
|
|
688
|
+
...existed,
|
|
689
|
+
metadata: {
|
|
690
|
+
...(existed?.metadata || {}),
|
|
691
|
+
num: totalUsageCount
|
|
692
|
+
},
|
|
693
|
+
_num: totalUsageCount
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
return mergedDetails;
|
|
697
|
+
};
|
|
544
698
|
|
|
545
699
|
// 记录每个优惠券适用的商品ID
|
|
546
700
|
const discountApplicability = new Map();
|
|
547
701
|
|
|
548
702
|
// 记录每个优惠券适用的商品详细信息
|
|
549
703
|
const discountApplicableProducts = new Map();
|
|
704
|
+
const discountTimeReasons = new Map();
|
|
705
|
+
const discountHolderMismatch = new Set();
|
|
550
706
|
|
|
551
707
|
// 初始化每个优惠券的适用性和可抵扣商品
|
|
552
708
|
addModeDiscount.forEach(discount => {
|
|
@@ -566,6 +722,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
566
722
|
|
|
567
723
|
// 辅助函数:检查商品是否对某个折扣卡可用
|
|
568
724
|
const checkItemApplicableForDiscount = (flatItem, discount) => {
|
|
725
|
+
if (discount.config?.isAvailable === false) return false;
|
|
569
726
|
let product;
|
|
570
727
|
if (flatItem.type === 'main') {
|
|
571
728
|
product = flatItem.product;
|
|
@@ -586,7 +743,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
586
743
|
}
|
|
587
744
|
|
|
588
745
|
// 编辑的商品使用了优惠券不可用
|
|
589
|
-
const isAvailableProduct = flatItem.type === 'main' ? !(product?.booking_id && product?.discount_list?.length && product?.discount_list?.every(d => d.id && ['good_pass', 'discount_card', 'product_discount_card'].includes(d.tag || d.type))) : !(flatItem?.booking_id && !!flatItem?.bundleItem?.discount_list?.length && flatItem?.bundleItem?.discount_list?.every(d => d.id));
|
|
746
|
+
const isAvailableProduct = flatItem.type === 'main' ? !(product?.booking_id && product?.discount_list?.length && product?.discount_list?.every(d => d.id && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(d.tag || d.type))) : !(flatItem?.booking_id && !!flatItem?.bundleItem?.discount_list?.length && flatItem?.bundleItem?.discount_list?.every(d => d.id));
|
|
590
747
|
if (!isAvailableProduct) {
|
|
591
748
|
return false;
|
|
592
749
|
}
|
|
@@ -770,30 +927,37 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
770
927
|
const isHolderMatch = this.checkHolderMatch(discount, {
|
|
771
928
|
isNeedHolder: resolveIsFormSubject(_tempVar) && !_tempVar?.isNormalProduct,
|
|
772
929
|
holder_id: _tempVar?.holder_id || product.holder_id
|
|
773
|
-
}, holders);
|
|
774
|
-
|
|
775
|
-
timeLimit =
|
|
930
|
+
}, flatItem.originProduct, holders);
|
|
931
|
+
const timeReason = (0, _utils.getDiscountTimeUnavailableReason)(discount, (product?.startDate || (0, _dayjs.default)()).format('YYYY-MM-DD HH:mm:ss'));
|
|
932
|
+
const timeLimit = !timeReason;
|
|
776
933
|
// 是符合折扣的商品
|
|
777
|
-
const
|
|
934
|
+
const isProductInScope = limitedData.type === 'product_all' && limitedData.filter !== 1 || limitedData.type === 'product_all' && limitedData.filter === 1 && !limitedData.exclude_product_ids.includes(product.id) || limitedData.product_ids && limitedData.product_ids.includes(product.id);
|
|
935
|
+
const isLimitedProduct = isProductInScope && isHolderMatch;
|
|
778
936
|
|
|
779
937
|
// 编辑的商品 使用了优惠券不可用
|
|
780
|
-
const isAvailableProduct = flatItem.type === 'main' ? !(product?.booking_id && product?.discount_list?.length && product?.discount_list?.every(discount => discount.id && ['good_pass', 'discount_card', 'product_discount_card'].includes(discount.tag || discount.type))) : !(flatItem?.booking_id && !!flatItem?.bundleItem?.discount_list?.length && flatItem?.bundleItem?.discount_list?.every(discount => discount.id));
|
|
938
|
+
const isAvailableProduct = flatItem.type === 'main' ? !(product?.booking_id && product?.discount_list?.length && product?.discount_list?.every(discount => discount.id && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discount.tag || discount.type))) : !(flatItem?.booking_id && !!flatItem?.bundleItem?.discount_list?.length && flatItem?.bundleItem?.discount_list?.every(discount => discount.id));
|
|
781
939
|
|
|
782
940
|
// 套餐是否可用判断
|
|
783
941
|
const isBundleAvailable = this.checkPackageSubItemUsageRules(discount, flatItem);
|
|
942
|
+
if (isProductInScope && isBundleAvailable && !isItemRewardProductDiscount) {
|
|
943
|
+
const timeReasons = discountTimeReasons.get(discount.id) || [];
|
|
944
|
+
timeReasons.push(timeReason);
|
|
945
|
+
discountTimeReasons.set(discount.id, timeReasons);
|
|
946
|
+
if (!isHolderMatch && timeLimit) discountHolderMismatch.add(discount.id);
|
|
947
|
+
}
|
|
784
948
|
|
|
785
949
|
// 判断优惠券是否适用于该商品
|
|
786
|
-
if (isAvailableProduct && !isItemRewardProductDiscount && isLimitedProduct && timeLimit && isBundleAvailable && discount.config?.isAvailable) {
|
|
950
|
+
if (isAvailableProduct && !isItemRewardProductDiscount && isLimitedProduct && timeLimit && isBundleAvailable && discount.config?.isAvailable && (!(0, _entitlementPass.isEntitlementPass)(discount) || (0, _entitlementPass.getRemainingEntitlementUses)(discount, 0, 1) > 0)) {
|
|
787
951
|
// 记录此优惠券适用的商品
|
|
788
952
|
discountApplicability.get(discount.id)?.push(product.id);
|
|
789
953
|
|
|
790
954
|
// 记录可抵扣的商品详情
|
|
791
955
|
const applicableProducts = discountApplicableProducts.get(discount.id) || [];
|
|
792
956
|
const discountType = discount.tag || discount.type;
|
|
793
|
-
const
|
|
957
|
+
const isFullCoverage = (0, _entitlementPass.isFullCoveragePass)(discount);
|
|
794
958
|
|
|
795
959
|
// 使用数量
|
|
796
|
-
const num =
|
|
960
|
+
const num = isFullCoverage || flatItem?.type === 'main' ? 1 : product.num;
|
|
797
961
|
const productData = {
|
|
798
962
|
amount: product.price * num,
|
|
799
963
|
type: discountType,
|
|
@@ -810,6 +974,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
810
974
|
discount_product_id: discount.product_id
|
|
811
975
|
},
|
|
812
976
|
metadata: {
|
|
977
|
+
encoded: discount.metadata?.encoded || discount.encoded,
|
|
813
978
|
num: num,
|
|
814
979
|
discount_rule_uncheck_flag: discount?.discount_rule_uncheck_flag
|
|
815
980
|
}
|
|
@@ -911,11 +1076,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
911
1076
|
const hasExistingWalletDiscount = product.discount_list?.length && product?.discount_list?.every(discount => {
|
|
912
1077
|
const discountIdentity = discount.id ?? discount.discount?.resource_id;
|
|
913
1078
|
const discountType = discount.tag || discount.type;
|
|
914
|
-
return Boolean(discountIdentity && ['good_pass', 'discount_card', 'product_discount_card'].includes(discountType));
|
|
1079
|
+
return Boolean(discountIdentity && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType));
|
|
915
1080
|
});
|
|
916
1081
|
|
|
917
1082
|
// 已持久化且已有钱包券/折扣卡的商品行跳过,保留后端历史折扣结果。
|
|
918
|
-
|
|
1083
|
+
const isReweighedProduct = reapplyWeighedDiscountProductUids.has(getOriginProductUid(originProduct) || '');
|
|
1084
|
+
if (isPersistedProduct && hasExistingWalletDiscount && !isReweighedProduct) {
|
|
919
1085
|
if (flatItem.type === 'main') {
|
|
920
1086
|
processedProductsMap.set(product._id, [originProduct]);
|
|
921
1087
|
}
|
|
@@ -928,7 +1094,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
928
1094
|
// 找到适用于此商品的所有优惠券,仅考虑isSelected不为false的优惠券
|
|
929
1095
|
const applicableDiscounts = sortedDiscountList.filter(discount => {
|
|
930
1096
|
if (this.isItemRewardProductDiscount(product)) return false;
|
|
931
|
-
// 🔥
|
|
1097
|
+
// 🔥 检查策略可用性(针对全额抵扣卡和折扣卡)
|
|
932
1098
|
const discountType = discount.tag || discount.type;
|
|
933
1099
|
const currentOriginUid = getOriginProductUid(originProduct);
|
|
934
1100
|
const isReapplyEditModeDiscountForProduct = discount.isEditMode && reapplyBookingDiscountProductUids.size > 0 && currentOriginUid && reapplyBookingDiscountProductUids.has(String(currentOriginUid)) && discount.isSelected === true;
|
|
@@ -937,7 +1103,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
937
1103
|
if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
|
|
938
1104
|
if (!discount.isSelected) return false;
|
|
939
1105
|
}
|
|
940
|
-
if (['good_pass', 'discount_card', 'product_discount_card'].includes(discountType) && !isReapplyEditModeDiscountForProduct) {
|
|
1106
|
+
if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '') && !isReapplyEditModeDiscountForProduct) {
|
|
941
1107
|
// 如果策略检查后没有config,说明不可用
|
|
942
1108
|
if (discount.config === undefined || !discount?.config?.isAvailable) {
|
|
943
1109
|
return false;
|
|
@@ -945,17 +1111,22 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
945
1111
|
}
|
|
946
1112
|
|
|
947
1113
|
// 如果商品价格为 0,其实不需要使用任何优惠券,直接 return true
|
|
948
|
-
//
|
|
949
|
-
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.length && (
|
|
1114
|
+
// 全额抵扣卡在主商品价格为 0 时不可用。
|
|
1115
|
+
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.length && (0, _entitlementPass.isFullCoveragePass)(discount)) return false;
|
|
950
1116
|
|
|
951
1117
|
// 折扣卡商品价格为0时不可用
|
|
952
|
-
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.find(n => n.discount?.resource_id === discount.id) && (
|
|
953
|
-
//
|
|
954
|
-
|
|
955
|
-
|
|
1118
|
+
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.find(n => n.discount?.resource_id === discount.id) && !(0, _entitlementPass.isFullCoveragePass)(discount)) return false;
|
|
1119
|
+
// good_pass 保持一张实例只使用一次。
|
|
1120
|
+
if ((discount.tag || discount.type) === 'good_pass' && usedDiscounts.get(discount.id)) {
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
// 有限次数卡按后端实时 balance 约束本次新增分配;无限卡不看余额。
|
|
1124
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount) && (0, _entitlementPass.getRemainingEntitlementUses)(discount, entitlementUsageCounts.get(discount.id) || 0, 1) <= 0) {
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
956
1127
|
|
|
957
1128
|
// 🔥 检查 maxUsagePerOrder 限制:同一 product_id 的商品券使用数量不能超过配置值
|
|
958
|
-
if ((
|
|
1129
|
+
if ((0, _entitlementPass.isFullCoveragePass)(discount)) {
|
|
959
1130
|
const maxUsagePerOrder = discount.config?.maxUsagePerOrder;
|
|
960
1131
|
if (maxUsagePerOrder && maxUsagePerOrder > 0) {
|
|
961
1132
|
const currentUsedCount = usedProductIdCounts.get(discount.product_id) || 0;
|
|
@@ -971,7 +1142,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
971
1142
|
const isHolderMatch = this.checkHolderMatch(discount, {
|
|
972
1143
|
isNeedHolder: resolveIsFormSubject(_tempVar) && !_tempVar?.isNormalProduct,
|
|
973
1144
|
holder_id: _tempVar?.holder_id || product.holder_id
|
|
974
|
-
}, holders);
|
|
1145
|
+
}, flatItem.originProduct, holders);
|
|
975
1146
|
// 如果 holder 不匹配,则不适用
|
|
976
1147
|
if (!isHolderMatch) return false;
|
|
977
1148
|
let timeLimit = true;
|
|
@@ -1000,31 +1171,34 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1000
1171
|
return false;
|
|
1001
1172
|
});
|
|
1002
1173
|
|
|
1003
|
-
//
|
|
1174
|
+
// 识别用户本次显式选择的卡券,供次卡和折扣卡各自的优先级使用。
|
|
1004
1175
|
// 目标仍必须在当前商品的 applicableDiscounts 中,因此不会绕过 Holder、时间或商品范围校验。
|
|
1005
1176
|
const explicitlySelectedDiscount = options?.isSelected === true && options.discountId !== undefined ? applicableDiscounts.find(discount => {
|
|
1006
1177
|
const discountType = discount.tag || discount.type;
|
|
1007
|
-
return String(discount.id) === String(options.discountId) && ['good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
1178
|
+
return String(discount.id) === String(options.discountId) && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType ?? '');
|
|
1008
1179
|
}) : undefined;
|
|
1009
1180
|
// 折扣卡会复用于整条商品行;商品券必须继续按单张拆分,不能复用该分支。
|
|
1010
|
-
const explicitlySelectedDiscountCard = explicitlySelectedDiscount && (
|
|
1181
|
+
const explicitlySelectedDiscountCard = explicitlySelectedDiscount && !(0, _entitlementPass.isFullCoveragePass)(explicitlySelectedDiscount) ? explicitlySelectedDiscount : undefined;
|
|
1011
1182
|
|
|
1012
|
-
// 商品数量等变化会触发无 discountId
|
|
1183
|
+
// 商品数量等变化会触发无 discountId 的重算。只要当前行已应用的卡仍在
|
|
1013
1184
|
// applicableDiscounts 中,就应保留该行选择,避免被自动排序中力度更大的卡替换。
|
|
1014
|
-
const
|
|
1185
|
+
const appliedWalletDiscountIds = new Set((product.discount_list || []).filter(item => ['entitlement_pass', 'discount_card', 'product_discount_card'].includes(item?.tag || item?.type)).map(item => item?.discount?.resource_id ?? item?.resource_id ?? item?.id).filter(id => id !== undefined && id !== null).map(String));
|
|
1015
1186
|
const appliedDiscountCard = options?.scan ? undefined : applicableDiscounts.find(discount => {
|
|
1016
1187
|
const discountType = discount.tag || discount.type;
|
|
1017
|
-
return discount.isSelected === true && ['discount_card', 'product_discount_card'].includes(discountType) &&
|
|
1188
|
+
return discount.isSelected === true && ['discount_card', 'product_discount_card'].includes(discountType ?? '') && appliedWalletDiscountIds.has(String(discount.id));
|
|
1018
1189
|
});
|
|
1190
|
+
const appliedEntitlementPass = options?.scan || options?.preserveEntitlementSelection === false ? undefined : applicableDiscounts.find(discount => (0, _entitlementPass.isEntitlementPass)(discount) && discount.isSelected === true && appliedWalletDiscountIds.has(String(discount.id)));
|
|
1019
1191
|
|
|
1020
|
-
//
|
|
1021
|
-
const scannedSelectedDiscountCard = applicableDiscounts.find(n => n.isScan && n.isSelected && (
|
|
1192
|
+
// 次卡内部按本次手选/扫码、当前行已应用、默认排序选择。
|
|
1193
|
+
const scannedSelectedDiscountCard = applicableDiscounts.find(n => n.isScan && n.isSelected && !(0, _entitlementPass.isFullCoveragePass)(n));
|
|
1022
1194
|
const selectedDiscountCard = explicitlySelectedDiscountCard || appliedDiscountCard || scannedSelectedDiscountCard;
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1195
|
+
const selectedEntitlementPass = (explicitlySelectedDiscount && (0, _entitlementPass.isEntitlementPass)(explicitlySelectedDiscount) ? explicitlySelectedDiscount : undefined) || applicableDiscounts.find(discount => (0, _entitlementPass.isEntitlementPass)(discount) && preferredEntitlementPassIds.has(String(discount.id))) || appliedEntitlementPass || applicableDiscounts.find(discount => (0, _entitlementPass.isEntitlementPass)(discount));
|
|
1196
|
+
// 手选优先;普通重算保留当前折扣卡,扫码和最优选择仍使用原有优先级。
|
|
1197
|
+
const selectedDiscount = explicitlySelectedDiscount || (options?.preserveEntitlementSelection !== false && preferredEntitlementPassIds.size === 0 ? appliedDiscountCard : undefined) || selectedEntitlementPass || selectedDiscountCard || applicableDiscounts[0];
|
|
1198
|
+
// 实际次数分配必须使用同一顺序,避免选中状态切换后仍将次数分配给默认卡。
|
|
1199
|
+
const prioritizedApplicableDiscounts = selectedDiscount && (0, _entitlementPass.isFullCoveragePass)(selectedDiscount) ? [selectedDiscount, ...applicableDiscounts.filter(discount => {
|
|
1200
|
+
return (0, _entitlementPass.isFullCoveragePass)(discount) && String(discount.id) !== String(selectedDiscount.id);
|
|
1201
|
+
}), ...applicableDiscounts.filter(discount => !(0, _entitlementPass.isFullCoveragePass)(discount))] : applicableDiscounts;
|
|
1028
1202
|
|
|
1029
1203
|
// 如果是手动折扣,则不适用优惠券
|
|
1030
1204
|
let isManualDiscount = false;
|
|
@@ -1140,19 +1314,13 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1140
1314
|
return;
|
|
1141
1315
|
}
|
|
1142
1316
|
|
|
1143
|
-
//
|
|
1144
|
-
const isNeedSplit = (
|
|
1317
|
+
// good_pass 与 Entitlement Pass 都沿用全额抵扣卡的商品行拆分机制。
|
|
1318
|
+
const isNeedSplit = (0, _entitlementPass.isFullCoveragePass)(selectedDiscount);
|
|
1145
1319
|
|
|
1146
1320
|
// 需要拆分出来的数量
|
|
1147
1321
|
const totalQuantity = product.quantity || product.num || 1;
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
// 🔥 如果selectedDiscount有config配置,则使用config.maxUsagePerOrder限制使用次数
|
|
1151
|
-
let maxUsageLimit = availableGoodPassCount;
|
|
1152
|
-
if (selectedDiscount.config && selectedDiscount.config.maxUsagePerOrder) {
|
|
1153
|
-
maxUsageLimit = Math.min(availableGoodPassCount, selectedDiscount.config.maxUsagePerOrder);
|
|
1154
|
-
}
|
|
1155
|
-
const splitCount = isNeedSplit ? Math.min(product.quantity || product.num || 1, maxUsageLimit) : 1;
|
|
1322
|
+
const fullCoveragePassUsageGroups = isNeedSplit ? buildFullCoveragePassUsageGroups(prioritizedApplicableDiscounts, totalQuantity) : [];
|
|
1323
|
+
const splitCount = isNeedSplit ? fullCoveragePassUsageGroups.reduce((total, group) => total + group.count, 0) : 1;
|
|
1156
1324
|
const arr = [];
|
|
1157
1325
|
|
|
1158
1326
|
// 🔥 主商品和bundle子商品分别处理
|
|
@@ -1160,22 +1328,22 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1160
1328
|
// 主商品:保持原有逻辑
|
|
1161
1329
|
if (splitCount < totalQuantity && isNeedSplit) {
|
|
1162
1330
|
const remainingQuantity = totalQuantity - splitCount;
|
|
1163
|
-
const
|
|
1331
|
+
const applicableNonFullCoveragePassById = new Map(applicableDiscounts.filter(discount => !(0, _entitlementPass.isFullCoveragePass)(discount)).map(discount => [String(discount.id), discount]));
|
|
1164
1332
|
const remainderDiscountList = (product.discount_list || []).filter(discount => {
|
|
1165
1333
|
const discountType = discount.tag || discount.type;
|
|
1166
1334
|
if (discountType === 'promotion') return true;
|
|
1167
|
-
if (
|
|
1335
|
+
if ((0, _entitlementPass.isFullCoveragePass)(discount)) return false;
|
|
1168
1336
|
const resourceId = discount.discount?.resource_id ?? discount.id;
|
|
1169
|
-
return resourceId !== undefined && resourceId !== null &&
|
|
1337
|
+
return resourceId !== undefined && resourceId !== null && applicableNonFullCoveragePassById.has(String(resourceId));
|
|
1170
1338
|
}).map(discount => {
|
|
1171
1339
|
if ((discount.tag || discount.type) === 'promotion') return discount;
|
|
1172
1340
|
const resourceId = discount.discount?.resource_id ?? discount.id;
|
|
1173
|
-
const matchedDiscount =
|
|
1341
|
+
const matchedDiscount = applicableNonFullCoveragePassById.get(String(resourceId));
|
|
1174
1342
|
if (matchedDiscount) {
|
|
1175
1343
|
// 实际场景:3 件折扣卡商品中拆出 1 件改用商品券后,
|
|
1176
1344
|
// 剩余 2 件仍在使用原折扣卡;必须同步登记折扣卡的使用状态,
|
|
1177
1345
|
// 否则 updatedDiscountList 会把它改成未选中并清空 savedAmount。
|
|
1178
|
-
|
|
1346
|
+
markDiscountUsed(matchedDiscount);
|
|
1179
1347
|
const appliedProducts = appliedDiscountProducts.get(matchedDiscount.id) || [];
|
|
1180
1348
|
appliedProducts.push({
|
|
1181
1349
|
...discount,
|
|
@@ -1189,14 +1357,14 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1189
1357
|
_num: remainingQuantity
|
|
1190
1358
|
};
|
|
1191
1359
|
});
|
|
1192
|
-
const hasRetainedWalletDiscount = remainderDiscountList.some(discount =>
|
|
1360
|
+
const hasRetainedWalletDiscount = remainderDiscountList.some(discount => (discount.tag || discount.type) !== 'promotion' && !(0, _entitlementPass.isFullCoveragePass)(discount));
|
|
1193
1361
|
let total = product.origin_total ?? product.total;
|
|
1194
1362
|
if ((product.discount_list || []).some(item => item.type === 'promotion')) {
|
|
1195
1363
|
total = product.total ?? product.origin_total;
|
|
1196
1364
|
}
|
|
1197
1365
|
arr.push(this.hooks.setProduct(originProduct, {
|
|
1198
1366
|
// 商品券只替换被拆出的 splitCount 件。余量行原本仍有效的折扣卡
|
|
1199
|
-
//
|
|
1367
|
+
// 不能随全额抵扣卡拆分一起清除;已取消或已失效的折扣不会进入此列表。
|
|
1200
1368
|
discount_list: remainderDiscountList,
|
|
1201
1369
|
quantity: remainingQuantity,
|
|
1202
1370
|
_id: product._id.split('___')[0],
|
|
@@ -1208,14 +1376,21 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1208
1376
|
} : {})
|
|
1209
1377
|
}));
|
|
1210
1378
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1379
|
+
const discountUsageGroups = isNeedSplit ? fullCoveragePassUsageGroups : [{
|
|
1380
|
+
discount: selectedDiscount,
|
|
1381
|
+
count: 1
|
|
1382
|
+
}];
|
|
1383
|
+
for (let i = 0; i < discountUsageGroups.length; i++) {
|
|
1384
|
+
const {
|
|
1385
|
+
discount: selectedDiscount,
|
|
1386
|
+
count: appliedUseCount
|
|
1387
|
+
} = discountUsageGroups[i];
|
|
1213
1388
|
// 标记优惠券为已使用
|
|
1214
|
-
|
|
1389
|
+
markDiscountUsed(selectedDiscount, appliedUseCount);
|
|
1215
1390
|
// 🔥 更新 product_id 使用计数
|
|
1216
|
-
if ((
|
|
1391
|
+
if ((0, _entitlementPass.isFullCoveragePass)(selectedDiscount)) {
|
|
1217
1392
|
const currentCount = usedProductIdCounts.get(selectedDiscount.product_id) || 0;
|
|
1218
|
-
usedProductIdCounts.set(selectedDiscount.product_id, currentCount +
|
|
1393
|
+
usedProductIdCounts.set(selectedDiscount.product_id, currentCount + appliedUseCount);
|
|
1219
1394
|
}
|
|
1220
1395
|
|
|
1221
1396
|
// 记录实际应用了优惠券的商品信息
|
|
@@ -1277,7 +1452,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1277
1452
|
const mainProductDiscountAmount = amount;
|
|
1278
1453
|
const lineDiscountAmount = new _decimal.default(mainProductDiscountAmount).plus(optionDiscountAmount).toNumber();
|
|
1279
1454
|
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
1280
|
-
const
|
|
1455
|
+
const isFullCoverage = (0, _entitlementPass.isFullCoveragePass)(selectedDiscount);
|
|
1281
1456
|
const discountDetail = {
|
|
1282
1457
|
amount: lineDiscountAmount,
|
|
1283
1458
|
type: selectedDiscount.tag === 'product_discount_card' ? 'discount_card' : discountType,
|
|
@@ -1293,10 +1468,11 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1293
1468
|
discount_product_id: selectedDiscount.product_id
|
|
1294
1469
|
},
|
|
1295
1470
|
// 前端使用的num数量,为了计算优惠金额
|
|
1296
|
-
_num:
|
|
1471
|
+
_num: isFullCoverage ? appliedUseCount : product.num,
|
|
1297
1472
|
config: selectedDiscount?.config,
|
|
1298
1473
|
metadata: {
|
|
1299
|
-
|
|
1474
|
+
encoded: selectedDiscount.metadata?.encoded || selectedDiscount.encoded,
|
|
1475
|
+
num: isFullCoverage ? appliedUseCount : 1,
|
|
1300
1476
|
discount_rule_uncheck_flag: selectedDiscount?.discount_rule_uncheck_flag,
|
|
1301
1477
|
// 🔥 order_level 分摊差值
|
|
1302
1478
|
...(productDiscountDifference !== undefined && {
|
|
@@ -1315,9 +1491,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1315
1491
|
if (product.isClient) {
|
|
1316
1492
|
arr.push(this.hooks.setProduct(originProduct, {
|
|
1317
1493
|
discount_list: [discountDetail],
|
|
1318
|
-
//
|
|
1319
|
-
price:
|
|
1320
|
-
quantity: isNeedSplit ?
|
|
1494
|
+
// 全额抵扣卡:主商品价以折后价为准;勿用 product.price - amount(amount 含 option 优惠时会算错)
|
|
1495
|
+
price: isFullCoverage ? mainProductSellingPrice : product.price,
|
|
1496
|
+
quantity: isNeedSplit ? appliedUseCount : product.quantity,
|
|
1321
1497
|
origin_total: (0, _utils2.getProductOriginTotalPrice)({
|
|
1322
1498
|
product: {
|
|
1323
1499
|
original_price: product.original_price
|
|
@@ -1334,8 +1510,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1334
1510
|
arr.push(this.hooks.setProduct(originProduct, {
|
|
1335
1511
|
discount_list: this.filterDiscountListByType(product.discount_list, 'promotion').concat([discountDetail]),
|
|
1336
1512
|
_id: product._id.split('___')[0] + '___' + selectedDiscount.id + index,
|
|
1337
|
-
price:
|
|
1338
|
-
quantity: isNeedSplit ?
|
|
1513
|
+
price: isFullCoverage ? 0 : product.price,
|
|
1514
|
+
quantity: isNeedSplit ? appliedUseCount : product.quantity,
|
|
1339
1515
|
total: total,
|
|
1340
1516
|
origin_total: productOriginTotal,
|
|
1341
1517
|
main_product_selling_price: mainProductSellingPrice,
|
|
@@ -1352,14 +1528,17 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1352
1528
|
const discountNum = splitCount;
|
|
1353
1529
|
const normalNum = totalQuantity - discountNum;
|
|
1354
1530
|
|
|
1355
|
-
//
|
|
1356
|
-
for (let i = 0; i <
|
|
1357
|
-
const
|
|
1358
|
-
|
|
1531
|
+
// 同一卡实例的本次新增使用合并为一条,metadata.num 记录实际次数。
|
|
1532
|
+
for (let i = 0; i < fullCoveragePassUsageGroups.length; i++) {
|
|
1533
|
+
const {
|
|
1534
|
+
discount: selectedDiscount,
|
|
1535
|
+
count: appliedUseCount
|
|
1536
|
+
} = fullCoveragePassUsageGroups[i];
|
|
1537
|
+
markDiscountUsed(selectedDiscount, appliedUseCount);
|
|
1359
1538
|
// 🔥 更新 product_id 使用计数
|
|
1360
|
-
if ((
|
|
1539
|
+
if ((0, _entitlementPass.isFullCoveragePass)(selectedDiscount)) {
|
|
1361
1540
|
const currentCount = usedProductIdCounts.get(selectedDiscount.product_id) || 0;
|
|
1362
|
-
usedProductIdCounts.set(selectedDiscount.product_id, currentCount +
|
|
1541
|
+
usedProductIdCounts.set(selectedDiscount.product_id, currentCount + appliedUseCount);
|
|
1363
1542
|
}
|
|
1364
1543
|
|
|
1365
1544
|
// 🔥 生成唯一的 _id
|
|
@@ -1367,11 +1546,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1367
1546
|
|
|
1368
1547
|
// 计算折扣后的价格(使用 getDiscountAmount,内部已处理 maxDeductionAmount)
|
|
1369
1548
|
const discountedPrice = (0, _utils.getDiscountAmount)(selectedDiscount, product.origin_total, product.origin_total);
|
|
1370
|
-
const
|
|
1549
|
+
const bundleDiscountAmountPerItem = new _decimal.default(product.origin_total || 0).minus(discountedPrice).toNumber();
|
|
1550
|
+
const bundleDiscountAmount = new _decimal.default(bundleDiscountAmountPerItem).mul(appliedUseCount).toNumber();
|
|
1371
1551
|
debugger;
|
|
1372
1552
|
const discountDetail = {
|
|
1373
1553
|
amount: bundleDiscountAmount,
|
|
1374
|
-
type:
|
|
1554
|
+
type: selectedDiscount.tag || selectedDiscount.type,
|
|
1375
1555
|
discount: {
|
|
1376
1556
|
fixed_amount: product.origin_total,
|
|
1377
1557
|
discount_calculation_mode: selectedDiscount?.metadata?.discount_calculation_mode,
|
|
@@ -1382,12 +1562,13 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1382
1562
|
discount_product_id: selectedDiscount.product_id
|
|
1383
1563
|
},
|
|
1384
1564
|
metadata: {
|
|
1565
|
+
encoded: selectedDiscount.metadata?.encoded || selectedDiscount.encoded,
|
|
1385
1566
|
// 🔥 使用拆分后的唯一 _id
|
|
1386
1567
|
custom_product_bundle_map_id: uniqueId,
|
|
1387
1568
|
discount_rule_uncheck_flag: selectedDiscount?.discount_rule_uncheck_flag,
|
|
1388
|
-
num:
|
|
1569
|
+
num: appliedUseCount
|
|
1389
1570
|
},
|
|
1390
|
-
_num:
|
|
1571
|
+
_num: appliedUseCount,
|
|
1391
1572
|
config: selectedDiscount?.config
|
|
1392
1573
|
};
|
|
1393
1574
|
|
|
@@ -1399,11 +1580,11 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1399
1580
|
...flatItem,
|
|
1400
1581
|
// 🔥 使用唯一的 _id
|
|
1401
1582
|
_id: uniqueId,
|
|
1402
|
-
num:
|
|
1403
|
-
quantity:
|
|
1583
|
+
num: appliedUseCount,
|
|
1584
|
+
quantity: appliedUseCount,
|
|
1404
1585
|
price: discountedPrice,
|
|
1405
1586
|
// 折扣后的价格
|
|
1406
|
-
total: discountedPrice,
|
|
1587
|
+
total: new _decimal.default(discountedPrice).mul(appliedUseCount).toNumber(),
|
|
1407
1588
|
discount_list: [discountDetail],
|
|
1408
1589
|
processed: true,
|
|
1409
1590
|
_discountId: selectedDiscount.id
|
|
@@ -1425,9 +1606,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1425
1606
|
} else {
|
|
1426
1607
|
// 折扣卡:不拆分数量,直接应用
|
|
1427
1608
|
const selectedDiscount = selectedDiscountCard || applicableDiscounts[0];
|
|
1428
|
-
|
|
1609
|
+
markDiscountUsed(selectedDiscount);
|
|
1429
1610
|
// 🔥 更新 product_id 使用计数(虽然此处主要是折扣卡,但保持一致性)
|
|
1430
|
-
if ((
|
|
1611
|
+
if ((0, _entitlementPass.isFullCoveragePass)(selectedDiscount)) {
|
|
1431
1612
|
const currentCount = usedProductIdCounts.get(selectedDiscount.product_id) || 0;
|
|
1432
1613
|
usedProductIdCounts.set(selectedDiscount.product_id, currentCount + 1);
|
|
1433
1614
|
}
|
|
@@ -1477,6 +1658,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1477
1658
|
discount_product_id: selectedDiscount.product_id
|
|
1478
1659
|
},
|
|
1479
1660
|
metadata: {
|
|
1661
|
+
encoded: selectedDiscount.metadata?.encoded || selectedDiscount.encoded,
|
|
1480
1662
|
// 🔥 使用唯一的 _id
|
|
1481
1663
|
custom_product_bundle_map_id: uniqueId,
|
|
1482
1664
|
discount_rule_uncheck_flag: selectedDiscount?.discount_rule_uncheck_flag,
|
|
@@ -1560,11 +1742,10 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1560
1742
|
// 没有bundle,直接使用主商品处理结果
|
|
1561
1743
|
result.push(...mainProductArr);
|
|
1562
1744
|
} else {
|
|
1563
|
-
// 🔥 有bundle
|
|
1745
|
+
// 🔥 有 bundle,需要检查子商品是否应用了全额抵扣卡
|
|
1564
1746
|
// 1. 先收集所有bundle子商品的处理结果
|
|
1565
1747
|
const bundleProcessingInfo = new Map();
|
|
1566
|
-
let
|
|
1567
|
-
|
|
1748
|
+
let hasFullCoveragePassApplied = false;
|
|
1568
1749
|
if (product.bundle && Array.isArray(product.bundle)) {
|
|
1569
1750
|
product.bundle.forEach((bundleItem, bundleIndex) => {
|
|
1570
1751
|
const bundleItemId = `${product._id}_bundle_${bundleIndex}`;
|
|
@@ -1575,10 +1756,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1575
1756
|
} else {
|
|
1576
1757
|
// 处理过的bundle item
|
|
1577
1758
|
bundleProcessingInfo.set(bundleIndex, processedBundleItems);
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
hasGoodPassApplied = true;
|
|
1759
|
+
const hasFullCoveragePass = processedBundleItems.some(item => item.discount_list?.some(discount => (0, _entitlementPass.isFullCoveragePass)(discount)));
|
|
1760
|
+
if (hasFullCoveragePass) {
|
|
1761
|
+
hasFullCoveragePassApplied = true;
|
|
1582
1762
|
}
|
|
1583
1763
|
}
|
|
1584
1764
|
});
|
|
@@ -1586,7 +1766,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1586
1766
|
|
|
1587
1767
|
// 🔥 2. 如果有子商品应用了商品券,且主商品数量大于1,需要拆分主商品
|
|
1588
1768
|
const mainProductQuantity = mainProductArr[0] ? this.hooks.getProduct(mainProductArr[0]).quantity : 1;
|
|
1589
|
-
if (
|
|
1769
|
+
if (hasFullCoveragePassApplied && mainProductArr.length === 1 && mainProductQuantity > 1) {
|
|
1590
1770
|
const mainProduct = mainProductArr[0];
|
|
1591
1771
|
const mainProductData = this.hooks.getProduct(mainProduct);
|
|
1592
1772
|
|
|
@@ -1890,27 +2070,39 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1890
2070
|
|
|
1891
2071
|
// 获取此优惠券实际应用的商品详情
|
|
1892
2072
|
const appliedProductDetails = appliedDiscountProducts.get(discount.id) || [];
|
|
2073
|
+
const committedAppliedProductDetails = (0, _entitlementPass.isEntitlementPass)(discount) ? mergeEntitlementAppliedProductDetails(discount.committedAppliedProductDetails || []) : [];
|
|
2074
|
+
const allAppliedProductDetails = (0, _entitlementPass.isEntitlementPass)(discount) ? mergeEntitlementAppliedProductDetails([...committedAppliedProductDetails, ...appliedProductDetails]) : appliedProductDetails;
|
|
1893
2075
|
|
|
1894
2076
|
// 根据是否有适用商品决定isAvailable
|
|
1895
2077
|
const isAvailable = applicableProducts.length > 0;
|
|
2078
|
+
const timeReasons = discountTimeReasons.get(discount.id) || [];
|
|
2079
|
+
const timeReason = timeReasons.length ? timeReasons.every(Boolean) ? timeReasons[0] : undefined : (0, _utils.getDiscountTimeUnavailableReason)(discount, (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss'));
|
|
2080
|
+
const reasonCode = isAvailable ? undefined : (0, _availability.getDiscountUsageBlockReason)(discount) || timeReason || discount.reasonCode || (discountHolderMismatch.has(discount.id) ? 'holder_not_matched' : 'product_not_applicable');
|
|
2081
|
+
const availabilityReason = {
|
|
2082
|
+
reasonCode,
|
|
2083
|
+
reason: reasonCode && reasonCode === discount.reasonCode ? discount.reason : undefined
|
|
2084
|
+
};
|
|
1896
2085
|
|
|
1897
2086
|
// 如果此优惠券被明确设置为不选中,保持其状态,但正确设置isAvailable
|
|
1898
2087
|
if (typeof discount.isSelected === 'boolean' && discount.isSelected === false && discount.isManualSelect) {
|
|
1899
2088
|
return {
|
|
1900
2089
|
...discount,
|
|
2090
|
+
...availabilityReason,
|
|
1901
2091
|
isSelected: false,
|
|
1902
2092
|
isAvailable: isAvailable,
|
|
1903
2093
|
// 正确设置可用性
|
|
1904
2094
|
applicableProductIds: applicableProducts,
|
|
1905
2095
|
// 添加可抵扣的商品详情
|
|
1906
2096
|
applicableProductDetails: applicableProductDetails,
|
|
1907
|
-
//
|
|
1908
|
-
appliedProductDetails:
|
|
2097
|
+
// 已提交的历史使用不可撤销;只取消本次新增分配。
|
|
2098
|
+
appliedProductDetails: committedAppliedProductDetails
|
|
1909
2099
|
};
|
|
1910
2100
|
}
|
|
1911
|
-
if (
|
|
2101
|
+
if (isDiscountUsed(discount)) {
|
|
1912
2102
|
return {
|
|
1913
2103
|
...discount,
|
|
2104
|
+
reason: undefined,
|
|
2105
|
+
reasonCode: undefined,
|
|
1914
2106
|
isSelected: true,
|
|
1915
2107
|
isManualSelect: options?.scan ? false : discount.isManualSelect,
|
|
1916
2108
|
// 标记为可用,因为它已被应用
|
|
@@ -1920,7 +2112,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1920
2112
|
// 添加可抵扣的商品详情
|
|
1921
2113
|
applicableProductDetails: applicableProductDetails,
|
|
1922
2114
|
// 添加实际抵扣的商品详情
|
|
1923
|
-
appliedProductDetails:
|
|
2115
|
+
appliedProductDetails: allAppliedProductDetails
|
|
1924
2116
|
};
|
|
1925
2117
|
}
|
|
1926
2118
|
let isSelected = typeof discount.isSelected === 'boolean' ? discount.isSelected : undefined;
|
|
@@ -1928,11 +2120,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1928
2120
|
// 如果是可用的 并且没有手动标记 则设置为选中
|
|
1929
2121
|
if (isAvailable && appliedProductDetails.length && !discount.isManualSelect) {
|
|
1930
2122
|
isSelected = true;
|
|
1931
|
-
} else if (!appliedProductDetails.length) {
|
|
2123
|
+
} else if (!appliedProductDetails.length && !committedAppliedProductDetails.length) {
|
|
1932
2124
|
isSelected = false;
|
|
1933
2125
|
}
|
|
1934
2126
|
return {
|
|
1935
2127
|
...discount,
|
|
2128
|
+
...availabilityReason,
|
|
1936
2129
|
// 如果有适用的商品,标记为可用
|
|
1937
2130
|
isAvailable: isAvailable,
|
|
1938
2131
|
isSelected,
|
|
@@ -1942,13 +2135,69 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1942
2135
|
applicableProductIds: applicableProducts,
|
|
1943
2136
|
// 添加可抵扣的商品详情
|
|
1944
2137
|
applicableProductDetails: applicableProductDetails,
|
|
1945
|
-
//
|
|
1946
|
-
appliedProductDetails:
|
|
2138
|
+
// 编辑订单的历史使用继续保留,本次没有新增使用。
|
|
2139
|
+
appliedProductDetails: committedAppliedProductDetails
|
|
2140
|
+
};
|
|
2141
|
+
});
|
|
2142
|
+
|
|
2143
|
+
// 整单分配完成后统一记录卡余额;历史使用已包含在服务端余额中,只扣本次新增次数。
|
|
2144
|
+
const entitlementBalances = new Map();
|
|
2145
|
+
addModeDiscount.forEach(discount => {
|
|
2146
|
+
const quota = (0, _entitlementPass.resolveEntitlementQuota)(discount);
|
|
2147
|
+
if (!quota) return;
|
|
2148
|
+
entitlementBalances.set(String(discount.id), quota.type === 'unlimited' ? null : Math.max(quota.balance - (entitlementUsageCounts.get(discount.id) || 0), 0));
|
|
2149
|
+
});
|
|
2150
|
+
const getEntitlementBalance = detail => {
|
|
2151
|
+
if (!(0, _entitlementPass.isEntitlementPass)(detail)) return undefined;
|
|
2152
|
+
const resourceId = detail?.discount?.resource_id ?? detail?.resource_id;
|
|
2153
|
+
return entitlementBalances.get(String(resourceId));
|
|
2154
|
+
};
|
|
2155
|
+
const needsEntitlementBalance = detail => {
|
|
2156
|
+
const balance = getEntitlementBalance(detail);
|
|
2157
|
+
return balance !== undefined && detail?.metadata?.balance !== balance;
|
|
2158
|
+
};
|
|
2159
|
+
const withEntitlementBalance = detail => {
|
|
2160
|
+
if (!needsEntitlementBalance(detail)) return detail;
|
|
2161
|
+
return {
|
|
2162
|
+
...detail,
|
|
2163
|
+
metadata: {
|
|
2164
|
+
...(detail.metadata || {}),
|
|
2165
|
+
balance: getEntitlementBalance(detail)
|
|
2166
|
+
}
|
|
2167
|
+
};
|
|
2168
|
+
};
|
|
2169
|
+
const getProductDiscountDetails = originProduct => {
|
|
2170
|
+
const product = this.hooks.getProduct(originProduct);
|
|
2171
|
+
return [...(product.discount_list || []), ...(product.bundle || []).flatMap(bundle => bundle.discount_list || [])];
|
|
2172
|
+
};
|
|
2173
|
+
const productsWithEntitlementBalances = entitlementBalances.size ? processedProductList.map(product => {
|
|
2174
|
+
if (!getProductDiscountDetails(product).some(needsEntitlementBalance)) {
|
|
2175
|
+
return product;
|
|
2176
|
+
}
|
|
2177
|
+
// 历史商品及套餐可能仍引用输入快照;仅复制有变化的行,并直接更新 metadata。
|
|
2178
|
+
// 不再次调用价格换算钩子,避免只补余额时改变商品价格。
|
|
2179
|
+
const nextProduct = (0, _lodashEs.cloneDeep)(product);
|
|
2180
|
+
getProductDiscountDetails(nextProduct).forEach(detail => {
|
|
2181
|
+
if (!needsEntitlementBalance(detail)) return;
|
|
2182
|
+
detail.metadata = withEntitlementBalance(detail).metadata;
|
|
2183
|
+
});
|
|
2184
|
+
return nextProduct;
|
|
2185
|
+
}) : processedProductList;
|
|
2186
|
+
const discountsWithEntitlementBalances = [...editModeDiscount, ...updatedDiscountList].map(discount => {
|
|
2187
|
+
if (!(0, _entitlementPass.isEntitlementPass)(discount) || !entitlementBalances.has(String(discount.id))) {
|
|
2188
|
+
return discount;
|
|
2189
|
+
}
|
|
2190
|
+
return {
|
|
2191
|
+
...discount,
|
|
2192
|
+
appliedProductDetails: (discount.appliedProductDetails || []).map(withEntitlementBalance),
|
|
2193
|
+
...(discount.committedAppliedProductDetails ? {
|
|
2194
|
+
committedAppliedProductDetails: discount.committedAppliedProductDetails.map(withEntitlementBalance)
|
|
2195
|
+
} : {})
|
|
1947
2196
|
};
|
|
1948
2197
|
});
|
|
1949
2198
|
return {
|
|
1950
|
-
productList:
|
|
1951
|
-
discountList:
|
|
2199
|
+
productList: productsWithEntitlementBalances,
|
|
2200
|
+
discountList: discountsWithEntitlementBalances
|
|
1952
2201
|
};
|
|
1953
2202
|
}
|
|
1954
2203
|
|