@pisell/pisellos 2.2.309 → 2.2.311
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 +0 -9
- 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 +32 -4
- package/dist/modules/Order/index.js +733 -373
- package/dist/modules/Order/types.d.ts +17 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
- package/dist/modules/Order/utils.d.ts +19 -5
- package/dist/modules/Order/utils.js +306 -86
- 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 +480 -186
- package/dist/modules/Rules/types.d.ts +4 -0
- package/dist/server/index.js +8 -5
- package/dist/server/modules/floor-plan/index.js +34 -3
- 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/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/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 +32 -4
- package/lib/modules/Order/index.js +341 -71
- package/lib/modules/Order/types.d.ts +17 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
- package/lib/modules/Order/utils.d.ts +19 -5
- package/lib/modules/Order/utils.js +215 -13
- 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 +357 -110
- package/lib/modules/Rules/types.d.ts +4 -0
- package/lib/server/index.js +8 -5
- package/lib/server/modules/floor-plan/index.js +29 -1
- 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/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/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,6 +219,7 @@ 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
|
// 识别出来是不是在编辑的界面里又新增了商品
|
|
@@ -223,7 +237,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
223
237
|
const uid = getOriginProductUid(originProduct);
|
|
224
238
|
if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
|
|
225
239
|
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
226
|
-
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 ?? '');
|
|
227
241
|
};
|
|
228
242
|
const resolveReapplyBookingDiscountPercent = (originProduct, selectedDiscount) => {
|
|
229
243
|
if (!shouldReapplyBookingDiscount(originProduct, selectedDiscount)) return undefined;
|
|
@@ -240,7 +254,13 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
240
254
|
if (discount.isEditMode) {
|
|
241
255
|
editModeDiscount.push(discount);
|
|
242
256
|
} else {
|
|
243
|
-
|
|
257
|
+
// 试算不能把 config / 不可用原因写回原有选券;每次重新计算以清除旧原因。
|
|
258
|
+
addModeDiscount.push({
|
|
259
|
+
...discount,
|
|
260
|
+
usageLimitFailKey: (0, _availability.getDiscountUsageLimitFailKey)(discount),
|
|
261
|
+
reason: undefined,
|
|
262
|
+
reasonCode: (0, _availability.getDiscountUsageBlockReason)(discount)
|
|
263
|
+
});
|
|
244
264
|
}
|
|
245
265
|
});
|
|
246
266
|
|
|
@@ -269,7 +289,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
269
289
|
}
|
|
270
290
|
const canUseEditModeDiscountForReapply = discount => {
|
|
271
291
|
const discountType = discount.tag || discount.type;
|
|
272
|
-
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 ?? '');
|
|
273
293
|
};
|
|
274
294
|
|
|
275
295
|
// 过滤掉手动取消false的优惠券,但仅用于应用优惠逻辑。
|
|
@@ -343,6 +363,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
343
363
|
|
|
344
364
|
// 辅助函数:判断是否是商品券
|
|
345
365
|
const isGoodPass = discount => discount.tag === 'good_pass';
|
|
366
|
+
const isEntitlement = discount => (0, _entitlementPass.isEntitlementPass)(discount);
|
|
346
367
|
|
|
347
368
|
// 辅助函数:判断是否是订单级折扣卡
|
|
348
369
|
const isOrderLevelDiscount = discount => discount.tag === 'product_discount_card' && discount.metadata?.discount_calculation_mode === 'order_level';
|
|
@@ -404,6 +425,14 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
404
425
|
}
|
|
405
426
|
return 0;
|
|
406
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
|
+
}
|
|
407
436
|
|
|
408
437
|
// 1. 先按优先级排序
|
|
409
438
|
const priorityA = getPriority(a);
|
|
@@ -472,19 +501,34 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
472
501
|
|
|
473
502
|
// 🔥 为每个折扣卡/商品券执行策略检查并附加config
|
|
474
503
|
const evaluator = this.getWalletPassEvaluator();
|
|
504
|
+
addModeDiscount.forEach(discount => {
|
|
505
|
+
if (discount.reasonCode) discount.config = {
|
|
506
|
+
isAvailable: false
|
|
507
|
+
};
|
|
508
|
+
});
|
|
475
509
|
if (evaluator) {
|
|
476
510
|
addModeDiscount.forEach(discount => {
|
|
511
|
+
// 即使没有匹配的动态策略,服务端状态和次数限制也不能被覆盖为可用。
|
|
512
|
+
if (discount.reasonCode) return;
|
|
477
513
|
const discountType = discount.tag || discount.type;
|
|
478
|
-
//
|
|
479
|
-
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
|
+
|
|
480
524
|
// 转换 discount 为 Voucher 格式
|
|
481
525
|
const voucher = {
|
|
482
526
|
id: discount.id,
|
|
483
527
|
amount: Number(discount.par_value || 0),
|
|
484
|
-
balance: Number(discount.balance || 0),
|
|
528
|
+
balance: entitlementQuota?.type === 'unlimited' ? Number.MAX_SAFE_INTEGER : entitlementQuota?.type === 'limited' ? entitlementQuota.balance : Number(discount.balance || 0),
|
|
485
529
|
type: discountType,
|
|
486
530
|
product_id: discount.product_id,
|
|
487
|
-
unified_available_status: 1,
|
|
531
|
+
unified_available_status: discount.unified_available_status ?? 1,
|
|
488
532
|
available_product_type: discount.limited_relation_product_data?.type,
|
|
489
533
|
available_product_ids: discount.limited_relation_product_data?.product_ids
|
|
490
534
|
};
|
|
@@ -515,6 +559,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
515
559
|
discount.config = {
|
|
516
560
|
isAvailable: false
|
|
517
561
|
};
|
|
562
|
+
discount.reason = result.reason;
|
|
563
|
+
discount.reasonCode = result.reasonCode || 'not_meet_the_required_conditions';
|
|
518
564
|
}
|
|
519
565
|
}
|
|
520
566
|
});
|
|
@@ -537,17 +583,126 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
537
583
|
});
|
|
538
584
|
*/
|
|
539
585
|
|
|
540
|
-
//
|
|
586
|
+
// good_pass / 折扣卡保持原有的一次性使用标记。
|
|
541
587
|
const usedDiscounts = new Map();
|
|
542
588
|
|
|
589
|
+
// Entitlement Pass 单独记录本次重算新增分配的次数。
|
|
590
|
+
const entitlementUsageCounts = new Map();
|
|
591
|
+
|
|
543
592
|
// 🔥 统计每个 product_id(商品券商品id)已使用的数量,用于 maxUsagePerOrder 限制
|
|
544
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
|
+
};
|
|
545
698
|
|
|
546
699
|
// 记录每个优惠券适用的商品ID
|
|
547
700
|
const discountApplicability = new Map();
|
|
548
701
|
|
|
549
702
|
// 记录每个优惠券适用的商品详细信息
|
|
550
703
|
const discountApplicableProducts = new Map();
|
|
704
|
+
const discountTimeReasons = new Map();
|
|
705
|
+
const discountHolderMismatch = new Set();
|
|
551
706
|
|
|
552
707
|
// 初始化每个优惠券的适用性和可抵扣商品
|
|
553
708
|
addModeDiscount.forEach(discount => {
|
|
@@ -567,6 +722,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
567
722
|
|
|
568
723
|
// 辅助函数:检查商品是否对某个折扣卡可用
|
|
569
724
|
const checkItemApplicableForDiscount = (flatItem, discount) => {
|
|
725
|
+
if (discount.config?.isAvailable === false) return false;
|
|
570
726
|
let product;
|
|
571
727
|
if (flatItem.type === 'main') {
|
|
572
728
|
product = flatItem.product;
|
|
@@ -587,7 +743,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
587
743
|
}
|
|
588
744
|
|
|
589
745
|
// 编辑的商品使用了优惠券不可用
|
|
590
|
-
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));
|
|
591
747
|
if (!isAvailableProduct) {
|
|
592
748
|
return false;
|
|
593
749
|
}
|
|
@@ -771,30 +927,37 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
771
927
|
const isHolderMatch = this.checkHolderMatch(discount, {
|
|
772
928
|
isNeedHolder: resolveIsFormSubject(_tempVar) && !_tempVar?.isNormalProduct,
|
|
773
929
|
holder_id: _tempVar?.holder_id || product.holder_id
|
|
774
|
-
}, holders);
|
|
775
|
-
|
|
776
|
-
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;
|
|
777
933
|
// 是符合折扣的商品
|
|
778
|
-
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;
|
|
779
936
|
|
|
780
937
|
// 编辑的商品 使用了优惠券不可用
|
|
781
|
-
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));
|
|
782
939
|
|
|
783
940
|
// 套餐是否可用判断
|
|
784
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
|
+
}
|
|
785
948
|
|
|
786
949
|
// 判断优惠券是否适用于该商品
|
|
787
|
-
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)) {
|
|
788
951
|
// 记录此优惠券适用的商品
|
|
789
952
|
discountApplicability.get(discount.id)?.push(product.id);
|
|
790
953
|
|
|
791
954
|
// 记录可抵扣的商品详情
|
|
792
955
|
const applicableProducts = discountApplicableProducts.get(discount.id) || [];
|
|
793
956
|
const discountType = discount.tag || discount.type;
|
|
794
|
-
const
|
|
957
|
+
const isFullCoverage = (0, _entitlementPass.isFullCoveragePass)(discount);
|
|
795
958
|
|
|
796
959
|
// 使用数量
|
|
797
|
-
const num =
|
|
960
|
+
const num = isFullCoverage || flatItem?.type === 'main' ? 1 : product.num;
|
|
798
961
|
const productData = {
|
|
799
962
|
amount: product.price * num,
|
|
800
963
|
type: discountType,
|
|
@@ -811,6 +974,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
811
974
|
discount_product_id: discount.product_id
|
|
812
975
|
},
|
|
813
976
|
metadata: {
|
|
977
|
+
encoded: discount.metadata?.encoded || discount.encoded,
|
|
814
978
|
num: num,
|
|
815
979
|
discount_rule_uncheck_flag: discount?.discount_rule_uncheck_flag
|
|
816
980
|
}
|
|
@@ -912,7 +1076,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
912
1076
|
const hasExistingWalletDiscount = product.discount_list?.length && product?.discount_list?.every(discount => {
|
|
913
1077
|
const discountIdentity = discount.id ?? discount.discount?.resource_id;
|
|
914
1078
|
const discountType = discount.tag || discount.type;
|
|
915
|
-
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));
|
|
916
1080
|
});
|
|
917
1081
|
|
|
918
1082
|
// 已持久化且已有钱包券/折扣卡的商品行跳过,保留后端历史折扣结果。
|
|
@@ -930,7 +1094,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
930
1094
|
// 找到适用于此商品的所有优惠券,仅考虑isSelected不为false的优惠券
|
|
931
1095
|
const applicableDiscounts = sortedDiscountList.filter(discount => {
|
|
932
1096
|
if (this.isItemRewardProductDiscount(product)) return false;
|
|
933
|
-
// 🔥
|
|
1097
|
+
// 🔥 检查策略可用性(针对全额抵扣卡和折扣卡)
|
|
934
1098
|
const discountType = discount.tag || discount.type;
|
|
935
1099
|
const currentOriginUid = getOriginProductUid(originProduct);
|
|
936
1100
|
const isReapplyEditModeDiscountForProduct = discount.isEditMode && reapplyBookingDiscountProductUids.size > 0 && currentOriginUid && reapplyBookingDiscountProductUids.has(String(currentOriginUid)) && discount.isSelected === true;
|
|
@@ -939,7 +1103,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
939
1103
|
if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
|
|
940
1104
|
if (!discount.isSelected) return false;
|
|
941
1105
|
}
|
|
942
|
-
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) {
|
|
943
1107
|
// 如果策略检查后没有config,说明不可用
|
|
944
1108
|
if (discount.config === undefined || !discount?.config?.isAvailable) {
|
|
945
1109
|
return false;
|
|
@@ -947,17 +1111,22 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
947
1111
|
}
|
|
948
1112
|
|
|
949
1113
|
// 如果商品价格为 0,其实不需要使用任何优惠券,直接 return true
|
|
950
|
-
//
|
|
951
|
-
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;
|
|
952
1116
|
|
|
953
1117
|
// 折扣卡商品价格为0时不可用
|
|
954
|
-
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.find(n => n.discount?.resource_id === discount.id) && (
|
|
955
|
-
//
|
|
956
|
-
|
|
957
|
-
|
|
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
|
+
}
|
|
958
1127
|
|
|
959
1128
|
// 🔥 检查 maxUsagePerOrder 限制:同一 product_id 的商品券使用数量不能超过配置值
|
|
960
|
-
if ((
|
|
1129
|
+
if ((0, _entitlementPass.isFullCoveragePass)(discount)) {
|
|
961
1130
|
const maxUsagePerOrder = discount.config?.maxUsagePerOrder;
|
|
962
1131
|
if (maxUsagePerOrder && maxUsagePerOrder > 0) {
|
|
963
1132
|
const currentUsedCount = usedProductIdCounts.get(discount.product_id) || 0;
|
|
@@ -973,7 +1142,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
973
1142
|
const isHolderMatch = this.checkHolderMatch(discount, {
|
|
974
1143
|
isNeedHolder: resolveIsFormSubject(_tempVar) && !_tempVar?.isNormalProduct,
|
|
975
1144
|
holder_id: _tempVar?.holder_id || product.holder_id
|
|
976
|
-
}, holders);
|
|
1145
|
+
}, flatItem.originProduct, holders);
|
|
977
1146
|
// 如果 holder 不匹配,则不适用
|
|
978
1147
|
if (!isHolderMatch) return false;
|
|
979
1148
|
let timeLimit = true;
|
|
@@ -1002,31 +1171,34 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1002
1171
|
return false;
|
|
1003
1172
|
});
|
|
1004
1173
|
|
|
1005
|
-
//
|
|
1174
|
+
// 识别用户本次显式选择的卡券,供次卡和折扣卡各自的优先级使用。
|
|
1006
1175
|
// 目标仍必须在当前商品的 applicableDiscounts 中,因此不会绕过 Holder、时间或商品范围校验。
|
|
1007
1176
|
const explicitlySelectedDiscount = options?.isSelected === true && options.discountId !== undefined ? applicableDiscounts.find(discount => {
|
|
1008
1177
|
const discountType = discount.tag || discount.type;
|
|
1009
|
-
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 ?? '');
|
|
1010
1179
|
}) : undefined;
|
|
1011
1180
|
// 折扣卡会复用于整条商品行;商品券必须继续按单张拆分,不能复用该分支。
|
|
1012
|
-
const explicitlySelectedDiscountCard = explicitlySelectedDiscount && (
|
|
1181
|
+
const explicitlySelectedDiscountCard = explicitlySelectedDiscount && !(0, _entitlementPass.isFullCoveragePass)(explicitlySelectedDiscount) ? explicitlySelectedDiscount : undefined;
|
|
1013
1182
|
|
|
1014
|
-
// 商品数量等变化会触发无 discountId
|
|
1183
|
+
// 商品数量等变化会触发无 discountId 的重算。只要当前行已应用的卡仍在
|
|
1015
1184
|
// applicableDiscounts 中,就应保留该行选择,避免被自动排序中力度更大的卡替换。
|
|
1016
|
-
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));
|
|
1017
1186
|
const appliedDiscountCard = options?.scan ? undefined : applicableDiscounts.find(discount => {
|
|
1018
1187
|
const discountType = discount.tag || discount.type;
|
|
1019
|
-
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));
|
|
1020
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)));
|
|
1021
1191
|
|
|
1022
|
-
//
|
|
1023
|
-
const scannedSelectedDiscountCard = applicableDiscounts.find(n => n.isScan && n.isSelected && (
|
|
1192
|
+
// 次卡内部按本次手选/扫码、当前行已应用、默认排序选择。
|
|
1193
|
+
const scannedSelectedDiscountCard = applicableDiscounts.find(n => n.isScan && n.isSelected && !(0, _entitlementPass.isFullCoveragePass)(n));
|
|
1024
1194
|
const selectedDiscountCard = explicitlySelectedDiscountCard || appliedDiscountCard || scannedSelectedDiscountCard;
|
|
1025
|
-
const
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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;
|
|
1030
1202
|
|
|
1031
1203
|
// 如果是手动折扣,则不适用优惠券
|
|
1032
1204
|
let isManualDiscount = false;
|
|
@@ -1142,19 +1314,13 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1142
1314
|
return;
|
|
1143
1315
|
}
|
|
1144
1316
|
|
|
1145
|
-
//
|
|
1146
|
-
const isNeedSplit = (
|
|
1317
|
+
// good_pass 与 Entitlement Pass 都沿用全额抵扣卡的商品行拆分机制。
|
|
1318
|
+
const isNeedSplit = (0, _entitlementPass.isFullCoveragePass)(selectedDiscount);
|
|
1147
1319
|
|
|
1148
1320
|
// 需要拆分出来的数量
|
|
1149
1321
|
const totalQuantity = product.quantity || product.num || 1;
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
// 🔥 如果selectedDiscount有config配置,则使用config.maxUsagePerOrder限制使用次数
|
|
1153
|
-
let maxUsageLimit = availableGoodPassCount;
|
|
1154
|
-
if (selectedDiscount.config && selectedDiscount.config.maxUsagePerOrder) {
|
|
1155
|
-
maxUsageLimit = Math.min(availableGoodPassCount, selectedDiscount.config.maxUsagePerOrder);
|
|
1156
|
-
}
|
|
1157
|
-
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;
|
|
1158
1324
|
const arr = [];
|
|
1159
1325
|
|
|
1160
1326
|
// 🔥 主商品和bundle子商品分别处理
|
|
@@ -1162,22 +1328,22 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1162
1328
|
// 主商品:保持原有逻辑
|
|
1163
1329
|
if (splitCount < totalQuantity && isNeedSplit) {
|
|
1164
1330
|
const remainingQuantity = totalQuantity - splitCount;
|
|
1165
|
-
const
|
|
1331
|
+
const applicableNonFullCoveragePassById = new Map(applicableDiscounts.filter(discount => !(0, _entitlementPass.isFullCoveragePass)(discount)).map(discount => [String(discount.id), discount]));
|
|
1166
1332
|
const remainderDiscountList = (product.discount_list || []).filter(discount => {
|
|
1167
1333
|
const discountType = discount.tag || discount.type;
|
|
1168
1334
|
if (discountType === 'promotion') return true;
|
|
1169
|
-
if (
|
|
1335
|
+
if ((0, _entitlementPass.isFullCoveragePass)(discount)) return false;
|
|
1170
1336
|
const resourceId = discount.discount?.resource_id ?? discount.id;
|
|
1171
|
-
return resourceId !== undefined && resourceId !== null &&
|
|
1337
|
+
return resourceId !== undefined && resourceId !== null && applicableNonFullCoveragePassById.has(String(resourceId));
|
|
1172
1338
|
}).map(discount => {
|
|
1173
1339
|
if ((discount.tag || discount.type) === 'promotion') return discount;
|
|
1174
1340
|
const resourceId = discount.discount?.resource_id ?? discount.id;
|
|
1175
|
-
const matchedDiscount =
|
|
1341
|
+
const matchedDiscount = applicableNonFullCoveragePassById.get(String(resourceId));
|
|
1176
1342
|
if (matchedDiscount) {
|
|
1177
1343
|
// 实际场景:3 件折扣卡商品中拆出 1 件改用商品券后,
|
|
1178
1344
|
// 剩余 2 件仍在使用原折扣卡;必须同步登记折扣卡的使用状态,
|
|
1179
1345
|
// 否则 updatedDiscountList 会把它改成未选中并清空 savedAmount。
|
|
1180
|
-
|
|
1346
|
+
markDiscountUsed(matchedDiscount);
|
|
1181
1347
|
const appliedProducts = appliedDiscountProducts.get(matchedDiscount.id) || [];
|
|
1182
1348
|
appliedProducts.push({
|
|
1183
1349
|
...discount,
|
|
@@ -1191,14 +1357,14 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1191
1357
|
_num: remainingQuantity
|
|
1192
1358
|
};
|
|
1193
1359
|
});
|
|
1194
|
-
const hasRetainedWalletDiscount = remainderDiscountList.some(discount =>
|
|
1360
|
+
const hasRetainedWalletDiscount = remainderDiscountList.some(discount => (discount.tag || discount.type) !== 'promotion' && !(0, _entitlementPass.isFullCoveragePass)(discount));
|
|
1195
1361
|
let total = product.origin_total ?? product.total;
|
|
1196
1362
|
if ((product.discount_list || []).some(item => item.type === 'promotion')) {
|
|
1197
1363
|
total = product.total ?? product.origin_total;
|
|
1198
1364
|
}
|
|
1199
1365
|
arr.push(this.hooks.setProduct(originProduct, {
|
|
1200
1366
|
// 商品券只替换被拆出的 splitCount 件。余量行原本仍有效的折扣卡
|
|
1201
|
-
//
|
|
1367
|
+
// 不能随全额抵扣卡拆分一起清除;已取消或已失效的折扣不会进入此列表。
|
|
1202
1368
|
discount_list: remainderDiscountList,
|
|
1203
1369
|
quantity: remainingQuantity,
|
|
1204
1370
|
_id: product._id.split('___')[0],
|
|
@@ -1210,14 +1376,21 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1210
1376
|
} : {})
|
|
1211
1377
|
}));
|
|
1212
1378
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
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];
|
|
1215
1388
|
// 标记优惠券为已使用
|
|
1216
|
-
|
|
1389
|
+
markDiscountUsed(selectedDiscount, appliedUseCount);
|
|
1217
1390
|
// 🔥 更新 product_id 使用计数
|
|
1218
|
-
if ((
|
|
1391
|
+
if ((0, _entitlementPass.isFullCoveragePass)(selectedDiscount)) {
|
|
1219
1392
|
const currentCount = usedProductIdCounts.get(selectedDiscount.product_id) || 0;
|
|
1220
|
-
usedProductIdCounts.set(selectedDiscount.product_id, currentCount +
|
|
1393
|
+
usedProductIdCounts.set(selectedDiscount.product_id, currentCount + appliedUseCount);
|
|
1221
1394
|
}
|
|
1222
1395
|
|
|
1223
1396
|
// 记录实际应用了优惠券的商品信息
|
|
@@ -1279,7 +1452,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1279
1452
|
const mainProductDiscountAmount = amount;
|
|
1280
1453
|
const lineDiscountAmount = new _decimal.default(mainProductDiscountAmount).plus(optionDiscountAmount).toNumber();
|
|
1281
1454
|
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
1282
|
-
const
|
|
1455
|
+
const isFullCoverage = (0, _entitlementPass.isFullCoveragePass)(selectedDiscount);
|
|
1283
1456
|
const discountDetail = {
|
|
1284
1457
|
amount: lineDiscountAmount,
|
|
1285
1458
|
type: selectedDiscount.tag === 'product_discount_card' ? 'discount_card' : discountType,
|
|
@@ -1295,10 +1468,11 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1295
1468
|
discount_product_id: selectedDiscount.product_id
|
|
1296
1469
|
},
|
|
1297
1470
|
// 前端使用的num数量,为了计算优惠金额
|
|
1298
|
-
_num:
|
|
1471
|
+
_num: isFullCoverage ? appliedUseCount : product.num,
|
|
1299
1472
|
config: selectedDiscount?.config,
|
|
1300
1473
|
metadata: {
|
|
1301
|
-
|
|
1474
|
+
encoded: selectedDiscount.metadata?.encoded || selectedDiscount.encoded,
|
|
1475
|
+
num: isFullCoverage ? appliedUseCount : 1,
|
|
1302
1476
|
discount_rule_uncheck_flag: selectedDiscount?.discount_rule_uncheck_flag,
|
|
1303
1477
|
// 🔥 order_level 分摊差值
|
|
1304
1478
|
...(productDiscountDifference !== undefined && {
|
|
@@ -1317,9 +1491,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1317
1491
|
if (product.isClient) {
|
|
1318
1492
|
arr.push(this.hooks.setProduct(originProduct, {
|
|
1319
1493
|
discount_list: [discountDetail],
|
|
1320
|
-
//
|
|
1321
|
-
price:
|
|
1322
|
-
quantity: isNeedSplit ?
|
|
1494
|
+
// 全额抵扣卡:主商品价以折后价为准;勿用 product.price - amount(amount 含 option 优惠时会算错)
|
|
1495
|
+
price: isFullCoverage ? mainProductSellingPrice : product.price,
|
|
1496
|
+
quantity: isNeedSplit ? appliedUseCount : product.quantity,
|
|
1323
1497
|
origin_total: (0, _utils2.getProductOriginTotalPrice)({
|
|
1324
1498
|
product: {
|
|
1325
1499
|
original_price: product.original_price
|
|
@@ -1336,8 +1510,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1336
1510
|
arr.push(this.hooks.setProduct(originProduct, {
|
|
1337
1511
|
discount_list: this.filterDiscountListByType(product.discount_list, 'promotion').concat([discountDetail]),
|
|
1338
1512
|
_id: product._id.split('___')[0] + '___' + selectedDiscount.id + index,
|
|
1339
|
-
price:
|
|
1340
|
-
quantity: isNeedSplit ?
|
|
1513
|
+
price: isFullCoverage ? 0 : product.price,
|
|
1514
|
+
quantity: isNeedSplit ? appliedUseCount : product.quantity,
|
|
1341
1515
|
total: total,
|
|
1342
1516
|
origin_total: productOriginTotal,
|
|
1343
1517
|
main_product_selling_price: mainProductSellingPrice,
|
|
@@ -1354,14 +1528,17 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1354
1528
|
const discountNum = splitCount;
|
|
1355
1529
|
const normalNum = totalQuantity - discountNum;
|
|
1356
1530
|
|
|
1357
|
-
//
|
|
1358
|
-
for (let i = 0; i <
|
|
1359
|
-
const
|
|
1360
|
-
|
|
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);
|
|
1361
1538
|
// 🔥 更新 product_id 使用计数
|
|
1362
|
-
if ((
|
|
1539
|
+
if ((0, _entitlementPass.isFullCoveragePass)(selectedDiscount)) {
|
|
1363
1540
|
const currentCount = usedProductIdCounts.get(selectedDiscount.product_id) || 0;
|
|
1364
|
-
usedProductIdCounts.set(selectedDiscount.product_id, currentCount +
|
|
1541
|
+
usedProductIdCounts.set(selectedDiscount.product_id, currentCount + appliedUseCount);
|
|
1365
1542
|
}
|
|
1366
1543
|
|
|
1367
1544
|
// 🔥 生成唯一的 _id
|
|
@@ -1369,11 +1546,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1369
1546
|
|
|
1370
1547
|
// 计算折扣后的价格(使用 getDiscountAmount,内部已处理 maxDeductionAmount)
|
|
1371
1548
|
const discountedPrice = (0, _utils.getDiscountAmount)(selectedDiscount, product.origin_total, product.origin_total);
|
|
1372
|
-
const
|
|
1549
|
+
const bundleDiscountAmountPerItem = new _decimal.default(product.origin_total || 0).minus(discountedPrice).toNumber();
|
|
1550
|
+
const bundleDiscountAmount = new _decimal.default(bundleDiscountAmountPerItem).mul(appliedUseCount).toNumber();
|
|
1373
1551
|
debugger;
|
|
1374
1552
|
const discountDetail = {
|
|
1375
1553
|
amount: bundleDiscountAmount,
|
|
1376
|
-
type:
|
|
1554
|
+
type: selectedDiscount.tag || selectedDiscount.type,
|
|
1377
1555
|
discount: {
|
|
1378
1556
|
fixed_amount: product.origin_total,
|
|
1379
1557
|
discount_calculation_mode: selectedDiscount?.metadata?.discount_calculation_mode,
|
|
@@ -1384,12 +1562,13 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1384
1562
|
discount_product_id: selectedDiscount.product_id
|
|
1385
1563
|
},
|
|
1386
1564
|
metadata: {
|
|
1565
|
+
encoded: selectedDiscount.metadata?.encoded || selectedDiscount.encoded,
|
|
1387
1566
|
// 🔥 使用拆分后的唯一 _id
|
|
1388
1567
|
custom_product_bundle_map_id: uniqueId,
|
|
1389
1568
|
discount_rule_uncheck_flag: selectedDiscount?.discount_rule_uncheck_flag,
|
|
1390
|
-
num:
|
|
1569
|
+
num: appliedUseCount
|
|
1391
1570
|
},
|
|
1392
|
-
_num:
|
|
1571
|
+
_num: appliedUseCount,
|
|
1393
1572
|
config: selectedDiscount?.config
|
|
1394
1573
|
};
|
|
1395
1574
|
|
|
@@ -1401,11 +1580,11 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1401
1580
|
...flatItem,
|
|
1402
1581
|
// 🔥 使用唯一的 _id
|
|
1403
1582
|
_id: uniqueId,
|
|
1404
|
-
num:
|
|
1405
|
-
quantity:
|
|
1583
|
+
num: appliedUseCount,
|
|
1584
|
+
quantity: appliedUseCount,
|
|
1406
1585
|
price: discountedPrice,
|
|
1407
1586
|
// 折扣后的价格
|
|
1408
|
-
total: discountedPrice,
|
|
1587
|
+
total: new _decimal.default(discountedPrice).mul(appliedUseCount).toNumber(),
|
|
1409
1588
|
discount_list: [discountDetail],
|
|
1410
1589
|
processed: true,
|
|
1411
1590
|
_discountId: selectedDiscount.id
|
|
@@ -1427,9 +1606,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1427
1606
|
} else {
|
|
1428
1607
|
// 折扣卡:不拆分数量,直接应用
|
|
1429
1608
|
const selectedDiscount = selectedDiscountCard || applicableDiscounts[0];
|
|
1430
|
-
|
|
1609
|
+
markDiscountUsed(selectedDiscount);
|
|
1431
1610
|
// 🔥 更新 product_id 使用计数(虽然此处主要是折扣卡,但保持一致性)
|
|
1432
|
-
if ((
|
|
1611
|
+
if ((0, _entitlementPass.isFullCoveragePass)(selectedDiscount)) {
|
|
1433
1612
|
const currentCount = usedProductIdCounts.get(selectedDiscount.product_id) || 0;
|
|
1434
1613
|
usedProductIdCounts.set(selectedDiscount.product_id, currentCount + 1);
|
|
1435
1614
|
}
|
|
@@ -1479,6 +1658,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1479
1658
|
discount_product_id: selectedDiscount.product_id
|
|
1480
1659
|
},
|
|
1481
1660
|
metadata: {
|
|
1661
|
+
encoded: selectedDiscount.metadata?.encoded || selectedDiscount.encoded,
|
|
1482
1662
|
// 🔥 使用唯一的 _id
|
|
1483
1663
|
custom_product_bundle_map_id: uniqueId,
|
|
1484
1664
|
discount_rule_uncheck_flag: selectedDiscount?.discount_rule_uncheck_flag,
|
|
@@ -1562,11 +1742,10 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1562
1742
|
// 没有bundle,直接使用主商品处理结果
|
|
1563
1743
|
result.push(...mainProductArr);
|
|
1564
1744
|
} else {
|
|
1565
|
-
// 🔥 有bundle
|
|
1745
|
+
// 🔥 有 bundle,需要检查子商品是否应用了全额抵扣卡
|
|
1566
1746
|
// 1. 先收集所有bundle子商品的处理结果
|
|
1567
1747
|
const bundleProcessingInfo = new Map();
|
|
1568
|
-
let
|
|
1569
|
-
|
|
1748
|
+
let hasFullCoveragePassApplied = false;
|
|
1570
1749
|
if (product.bundle && Array.isArray(product.bundle)) {
|
|
1571
1750
|
product.bundle.forEach((bundleItem, bundleIndex) => {
|
|
1572
1751
|
const bundleItemId = `${product._id}_bundle_${bundleIndex}`;
|
|
@@ -1577,10 +1756,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1577
1756
|
} else {
|
|
1578
1757
|
// 处理过的bundle item
|
|
1579
1758
|
bundleProcessingInfo.set(bundleIndex, processedBundleItems);
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
hasGoodPassApplied = true;
|
|
1759
|
+
const hasFullCoveragePass = processedBundleItems.some(item => item.discount_list?.some(discount => (0, _entitlementPass.isFullCoveragePass)(discount)));
|
|
1760
|
+
if (hasFullCoveragePass) {
|
|
1761
|
+
hasFullCoveragePassApplied = true;
|
|
1584
1762
|
}
|
|
1585
1763
|
}
|
|
1586
1764
|
});
|
|
@@ -1588,7 +1766,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1588
1766
|
|
|
1589
1767
|
// 🔥 2. 如果有子商品应用了商品券,且主商品数量大于1,需要拆分主商品
|
|
1590
1768
|
const mainProductQuantity = mainProductArr[0] ? this.hooks.getProduct(mainProductArr[0]).quantity : 1;
|
|
1591
|
-
if (
|
|
1769
|
+
if (hasFullCoveragePassApplied && mainProductArr.length === 1 && mainProductQuantity > 1) {
|
|
1592
1770
|
const mainProduct = mainProductArr[0];
|
|
1593
1771
|
const mainProductData = this.hooks.getProduct(mainProduct);
|
|
1594
1772
|
|
|
@@ -1892,27 +2070,39 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1892
2070
|
|
|
1893
2071
|
// 获取此优惠券实际应用的商品详情
|
|
1894
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;
|
|
1895
2075
|
|
|
1896
2076
|
// 根据是否有适用商品决定isAvailable
|
|
1897
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
|
+
};
|
|
1898
2085
|
|
|
1899
2086
|
// 如果此优惠券被明确设置为不选中,保持其状态,但正确设置isAvailable
|
|
1900
2087
|
if (typeof discount.isSelected === 'boolean' && discount.isSelected === false && discount.isManualSelect) {
|
|
1901
2088
|
return {
|
|
1902
2089
|
...discount,
|
|
2090
|
+
...availabilityReason,
|
|
1903
2091
|
isSelected: false,
|
|
1904
2092
|
isAvailable: isAvailable,
|
|
1905
2093
|
// 正确设置可用性
|
|
1906
2094
|
applicableProductIds: applicableProducts,
|
|
1907
2095
|
// 添加可抵扣的商品详情
|
|
1908
2096
|
applicableProductDetails: applicableProductDetails,
|
|
1909
|
-
//
|
|
1910
|
-
appliedProductDetails:
|
|
2097
|
+
// 已提交的历史使用不可撤销;只取消本次新增分配。
|
|
2098
|
+
appliedProductDetails: committedAppliedProductDetails
|
|
1911
2099
|
};
|
|
1912
2100
|
}
|
|
1913
|
-
if (
|
|
2101
|
+
if (isDiscountUsed(discount)) {
|
|
1914
2102
|
return {
|
|
1915
2103
|
...discount,
|
|
2104
|
+
reason: undefined,
|
|
2105
|
+
reasonCode: undefined,
|
|
1916
2106
|
isSelected: true,
|
|
1917
2107
|
isManualSelect: options?.scan ? false : discount.isManualSelect,
|
|
1918
2108
|
// 标记为可用,因为它已被应用
|
|
@@ -1922,7 +2112,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1922
2112
|
// 添加可抵扣的商品详情
|
|
1923
2113
|
applicableProductDetails: applicableProductDetails,
|
|
1924
2114
|
// 添加实际抵扣的商品详情
|
|
1925
|
-
appliedProductDetails:
|
|
2115
|
+
appliedProductDetails: allAppliedProductDetails
|
|
1926
2116
|
};
|
|
1927
2117
|
}
|
|
1928
2118
|
let isSelected = typeof discount.isSelected === 'boolean' ? discount.isSelected : undefined;
|
|
@@ -1930,11 +2120,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1930
2120
|
// 如果是可用的 并且没有手动标记 则设置为选中
|
|
1931
2121
|
if (isAvailable && appliedProductDetails.length && !discount.isManualSelect) {
|
|
1932
2122
|
isSelected = true;
|
|
1933
|
-
} else if (!appliedProductDetails.length) {
|
|
2123
|
+
} else if (!appliedProductDetails.length && !committedAppliedProductDetails.length) {
|
|
1934
2124
|
isSelected = false;
|
|
1935
2125
|
}
|
|
1936
2126
|
return {
|
|
1937
2127
|
...discount,
|
|
2128
|
+
...availabilityReason,
|
|
1938
2129
|
// 如果有适用的商品,标记为可用
|
|
1939
2130
|
isAvailable: isAvailable,
|
|
1940
2131
|
isSelected,
|
|
@@ -1944,13 +2135,69 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1944
2135
|
applicableProductIds: applicableProducts,
|
|
1945
2136
|
// 添加可抵扣的商品详情
|
|
1946
2137
|
applicableProductDetails: applicableProductDetails,
|
|
1947
|
-
//
|
|
1948
|
-
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
|
+
} : {})
|
|
1949
2196
|
};
|
|
1950
2197
|
});
|
|
1951
2198
|
return {
|
|
1952
|
-
productList:
|
|
1953
|
-
discountList:
|
|
2199
|
+
productList: productsWithEntitlementBalances,
|
|
2200
|
+
discountList: discountsWithEntitlementBalances
|
|
1954
2201
|
};
|
|
1955
2202
|
}
|
|
1956
2203
|
|