@pisell/pisellos 0.10.41 → 0.10.43
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/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
- 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 -0
- package/dist/modules/Discount/index.js +39 -13
- package/dist/modules/Discount/types.d.ts +26 -5
- package/dist/modules/Order/index.d.ts +5 -0
- package/dist/modules/Order/index.js +283 -118
- package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
- package/dist/modules/Order/utils.js +17 -1
- package/dist/modules/ResourcePlanner/planner.d.ts +2 -2
- package/dist/modules/ResourcePlanner/planner.js +163 -46
- package/dist/modules/ResourcePlanner/types.d.ts +17 -0
- package/dist/modules/Rules/index.js +312 -135
- package/dist/solution/BaseSales/index.d.ts +2 -0
- package/dist/solution/BaseSales/index.js +9 -1
- package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
- package/dist/solution/BookingTicket/types.d.ts +5 -1
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +171 -96
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +3 -1
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +2 -0
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +12 -3
- package/dist/solution/ShopDiscount/index.d.ts +1 -0
- package/dist/solution/ShopDiscount/index.js +41 -21
- package/dist/solution/ShopDiscount/utils.js +3 -2
- package/dist/solution/UnifiedBookingSales/index.js +94 -21
- package/lib/model/strategy/adapter/promotion/index.js +1 -46
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
- 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 -0
- package/lib/modules/Discount/index.js +25 -9
- package/lib/modules/Discount/types.d.ts +26 -5
- package/lib/modules/Order/index.d.ts +5 -0
- package/lib/modules/Order/index.js +172 -28
- package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
- package/lib/modules/Order/utils.js +19 -1
- package/lib/modules/ResourcePlanner/planner.d.ts +2 -2
- package/lib/modules/ResourcePlanner/planner.js +137 -35
- package/lib/modules/ResourcePlanner/types.d.ts +17 -0
- package/lib/modules/Rules/index.js +232 -97
- package/lib/solution/BaseSales/index.d.ts +2 -0
- package/lib/solution/BaseSales/index.js +7 -1
- package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
- package/lib/solution/BookingTicket/types.d.ts +5 -1
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +65 -18
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +3 -1
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +2 -0
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +5 -1
- package/lib/solution/ShopDiscount/index.d.ts +1 -0
- package/lib/solution/ShopDiscount/index.js +26 -6
- package/lib/solution/ShopDiscount/utils.js +3 -2
- package/lib/solution/UnifiedBookingSales/index.js +86 -22
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ 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");
|
|
14
15
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
15
16
|
class RulesModule extends _BaseModule.BaseModule {
|
|
16
17
|
defaultName = 'rules';
|
|
@@ -97,7 +98,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
97
98
|
|
|
98
99
|
// 合并新旧折扣列表,并计算折扣结果,注意,如果旧折扣里有 isEditMode 为 true 的优惠券,则不合并
|
|
99
100
|
// 保留之前扫码得到的商品券(isScan: true)
|
|
100
|
-
const filteredOldDiscountList = oldDiscountList.filter(discount => !discount.isEditMode && (discount
|
|
101
|
+
const filteredOldDiscountList = oldDiscountList.filter(discount => !discount.isEditMode && (!(0, _entitlementPass.isFullCoveragePass)(discount) || discount.isScan));
|
|
101
102
|
const mergedDiscountList = (0, _utils.uniqueById)([...filteredOldDiscountList, ...newDiscountList]);
|
|
102
103
|
const result = this.calcDiscount({
|
|
103
104
|
discountList: mergedDiscountList,
|
|
@@ -221,8 +222,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
221
222
|
if (!selectedDiscount) return false;
|
|
222
223
|
const uid = getOriginProductUid(originProduct);
|
|
223
224
|
if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
|
|
224
|
-
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
225
|
-
return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ['good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
225
|
+
const discountType = selectedDiscount.tag || selectedDiscount.type || '';
|
|
226
|
+
return selectedDiscount.isEditMode === true && selectedDiscount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
226
227
|
};
|
|
227
228
|
const resolveReapplyBookingDiscountPercent = (originProduct, selectedDiscount) => {
|
|
228
229
|
if (!shouldReapplyBookingDiscount(originProduct, selectedDiscount)) return undefined;
|
|
@@ -267,8 +268,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
267
268
|
};
|
|
268
269
|
}
|
|
269
270
|
const canUseEditModeDiscountForReapply = discount => {
|
|
270
|
-
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);
|
|
271
|
+
const discountType = discount.tag || discount.type || '';
|
|
272
|
+
return reapplyBookingDiscountProductUids.size > 0 && discount.isEditMode === true && discount.isSelected === true && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
272
273
|
};
|
|
273
274
|
|
|
274
275
|
// 过滤掉手动取消false的优惠券,但仅用于应用优惠逻辑。
|
|
@@ -342,6 +343,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
342
343
|
|
|
343
344
|
// 辅助函数:判断是否是商品券
|
|
344
345
|
const isGoodPass = discount => discount.tag === 'good_pass';
|
|
346
|
+
const isEntitlement = discount => (0, _entitlementPass.isEntitlementPass)(discount);
|
|
345
347
|
|
|
346
348
|
// 辅助函数:判断是否是订单级折扣卡
|
|
347
349
|
const isOrderLevelDiscount = discount => discount.tag === 'product_discount_card' && discount.metadata?.discount_calculation_mode === 'order_level';
|
|
@@ -403,6 +405,14 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
403
405
|
}
|
|
404
406
|
return 0;
|
|
405
407
|
}
|
|
408
|
+
const entitlementA = isEntitlement(a);
|
|
409
|
+
const entitlementB = isEntitlement(b);
|
|
410
|
+
if (entitlementA || entitlementB) {
|
|
411
|
+
if (entitlementA && entitlementB) {
|
|
412
|
+
return (0, _entitlementPass.compareEntitlementPassPriority)(a, b);
|
|
413
|
+
}
|
|
414
|
+
return entitlementA ? -1 : 1;
|
|
415
|
+
}
|
|
406
416
|
|
|
407
417
|
// 1. 先按优先级排序
|
|
408
418
|
const priorityA = getPriority(a);
|
|
@@ -473,14 +483,22 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
473
483
|
const evaluator = this.getWalletPassEvaluator();
|
|
474
484
|
if (evaluator) {
|
|
475
485
|
addModeDiscount.forEach(discount => {
|
|
476
|
-
const discountType = discount.tag || discount.type;
|
|
477
|
-
//
|
|
478
|
-
if (['good_pass', 'discount_card', 'product_discount_card'].includes(discountType)) {
|
|
486
|
+
const discountType = discount.tag || discount.type || '';
|
|
487
|
+
// 对两类全额抵扣卡和折扣卡执行附加策略检查。
|
|
488
|
+
if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType)) {
|
|
489
|
+
const entitlementQuota = (0, _entitlementPass.isEntitlementPass)(discount) ? (0, _entitlementPass.resolveEntitlementQuota)(discount) : null;
|
|
490
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount) && !entitlementQuota) {
|
|
491
|
+
discount.config = {
|
|
492
|
+
isAvailable: false
|
|
493
|
+
};
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
479
497
|
// 转换 discount 为 Voucher 格式
|
|
480
498
|
const voucher = {
|
|
481
499
|
id: discount.id,
|
|
482
500
|
amount: Number(discount.par_value || 0),
|
|
483
|
-
balance: Number(discount.balance || 0),
|
|
501
|
+
balance: entitlementQuota?.type === 'unlimited' ? Number.MAX_SAFE_INTEGER : entitlementQuota?.type === 'limited' ? entitlementQuota.balance : Number(discount.balance || 0),
|
|
484
502
|
type: discountType,
|
|
485
503
|
product_id: discount.product_id,
|
|
486
504
|
unified_available_status: 1,
|
|
@@ -536,11 +554,118 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
536
554
|
});
|
|
537
555
|
*/
|
|
538
556
|
|
|
539
|
-
//
|
|
557
|
+
// good_pass / 折扣卡保持原有的一次性使用标记。
|
|
540
558
|
const usedDiscounts = new Map();
|
|
541
559
|
|
|
560
|
+
// Entitlement Pass 单独记录本次重算新增分配的次数。
|
|
561
|
+
const entitlementUsageCounts = new Map();
|
|
562
|
+
|
|
542
563
|
// 🔥 统计每个 product_id(商品券商品id)已使用的数量,用于 maxUsagePerOrder 限制
|
|
543
564
|
const usedProductIdCounts = new Map();
|
|
565
|
+
const markDiscountUsed = (discount, count = 1) => {
|
|
566
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
567
|
+
entitlementUsageCounts.set(discount.id, (entitlementUsageCounts.get(discount.id) || 0) + count);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
usedDiscounts.set(discount.id, true);
|
|
571
|
+
};
|
|
572
|
+
const isDiscountUsed = discount => {
|
|
573
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
574
|
+
return (entitlementUsageCounts.get(discount.id) || 0) > 0;
|
|
575
|
+
}
|
|
576
|
+
return usedDiscounts.get(discount.id) === true;
|
|
577
|
+
};
|
|
578
|
+
const buildFullCoveragePassUsageGroups = (discounts, requestedCount) => {
|
|
579
|
+
const groups = [];
|
|
580
|
+
const pendingUsedDiscounts = new Set(Array.from(usedDiscounts.entries()).filter(([, used]) => used).map(([discountId]) => discountId));
|
|
581
|
+
const pendingEntitlementCounts = new Map(entitlementUsageCounts);
|
|
582
|
+
const pendingProductCounts = new Map(usedProductIdCounts);
|
|
583
|
+
const firstGoodPass = discounts.find(discount => (0, _entitlementPass.isFullCoveragePass)(discount) && !(0, _entitlementPass.isEntitlementPass)(discount));
|
|
584
|
+
const legacyGoodPassPerLineLimit = firstGoodPass ? Number(firstGoodPass.config?.maxUsagePerOrder || 0) : 0;
|
|
585
|
+
let allocatedGoodPassCount = 0;
|
|
586
|
+
let remaining = Math.max(Math.floor(requestedCount), 0);
|
|
587
|
+
for (const discount of discounts) {
|
|
588
|
+
if (remaining <= 0) break;
|
|
589
|
+
if (!(0, _entitlementPass.isFullCoveragePass)(discount)) continue;
|
|
590
|
+
const currentEntitlementCount = pendingEntitlementCounts.get(discount.id) || 0;
|
|
591
|
+
let availableCount = (0, _entitlementPass.isEntitlementPass)(discount) ? (0, _entitlementPass.getRemainingEntitlementUses)(discount, currentEntitlementCount, remaining) : pendingUsedDiscounts.has(discount.id) ? 0 : 1;
|
|
592
|
+
if (!(0, _entitlementPass.isEntitlementPass)(discount) && legacyGoodPassPerLineLimit > 0) {
|
|
593
|
+
availableCount = Math.min(availableCount, Math.max(Math.floor(legacyGoodPassPerLineLimit) - allocatedGoodPassCount, 0));
|
|
594
|
+
}
|
|
595
|
+
const maxUsagePerOrder = Number(discount.config?.maxUsagePerOrder || 0);
|
|
596
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount) && maxUsagePerOrder > 0) {
|
|
597
|
+
const currentProductCount = pendingProductCounts.get(discount.product_id) || 0;
|
|
598
|
+
availableCount = Math.min(availableCount, Math.max(Math.floor(maxUsagePerOrder) - currentProductCount, 0));
|
|
599
|
+
}
|
|
600
|
+
const allocatedCount = Math.min(availableCount, remaining);
|
|
601
|
+
if (allocatedCount <= 0) continue;
|
|
602
|
+
const existingGroup = groups.find(group => String(group.discount.id) === String(discount.id));
|
|
603
|
+
if (existingGroup) {
|
|
604
|
+
existingGroup.count += allocatedCount;
|
|
605
|
+
} else {
|
|
606
|
+
groups.push({
|
|
607
|
+
discount,
|
|
608
|
+
count: allocatedCount
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
612
|
+
pendingEntitlementCounts.set(discount.id, currentEntitlementCount + allocatedCount);
|
|
613
|
+
} else {
|
|
614
|
+
pendingUsedDiscounts.add(discount.id);
|
|
615
|
+
allocatedGoodPassCount += allocatedCount;
|
|
616
|
+
}
|
|
617
|
+
pendingProductCounts.set(discount.product_id, (pendingProductCounts.get(discount.product_id) || 0) + allocatedCount);
|
|
618
|
+
remaining -= allocatedCount;
|
|
619
|
+
}
|
|
620
|
+
return groups;
|
|
621
|
+
};
|
|
622
|
+
const mergeEntitlementAppliedProductDetails = details => {
|
|
623
|
+
const mergedDetails = [];
|
|
624
|
+
const mergedIndexByKey = new Map();
|
|
625
|
+
const toComparableNumber = value => {
|
|
626
|
+
const numberValue = Number(value);
|
|
627
|
+
return Number.isFinite(numberValue) ? String(numberValue) : String(value ?? '');
|
|
628
|
+
};
|
|
629
|
+
for (const detail of details || []) {
|
|
630
|
+
if (!(0, _entitlementPass.isEntitlementPass)(detail)) {
|
|
631
|
+
mergedDetails.push(detail);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const resourceId = detail?.discount?.resource_id ?? detail?.resource_id;
|
|
635
|
+
if (resourceId === undefined || resourceId === null) {
|
|
636
|
+
mergedDetails.push(detail);
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const key = [resourceId, detail?.discount?.product_id ?? '', detail?.metadata?.custom_product_bundle_map_id ?? '', toComparableNumber(detail?.amount), toComparableNumber(detail?.discount?.fixed_amount)].join('|');
|
|
640
|
+
const rawUsageCount = Number(detail?.metadata?.num ?? detail?._num ?? 1);
|
|
641
|
+
const usageCount = Number.isFinite(rawUsageCount) && rawUsageCount > 0 ? rawUsageCount : 1;
|
|
642
|
+
const mergedIndex = mergedIndexByKey.get(key);
|
|
643
|
+
if (mergedIndex === undefined) {
|
|
644
|
+
mergedIndexByKey.set(key, mergedDetails.length);
|
|
645
|
+
mergedDetails.push({
|
|
646
|
+
...detail,
|
|
647
|
+
metadata: {
|
|
648
|
+
...(detail?.metadata || {}),
|
|
649
|
+
num: usageCount
|
|
650
|
+
},
|
|
651
|
+
_num: usageCount
|
|
652
|
+
});
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
const existed = mergedDetails[mergedIndex];
|
|
656
|
+
const existedUsageCount = Number(existed?.metadata?.num ?? existed?._num ?? 1);
|
|
657
|
+
const totalUsageCount = existedUsageCount + usageCount;
|
|
658
|
+
mergedDetails[mergedIndex] = {
|
|
659
|
+
...existed,
|
|
660
|
+
metadata: {
|
|
661
|
+
...(existed?.metadata || {}),
|
|
662
|
+
num: totalUsageCount
|
|
663
|
+
},
|
|
664
|
+
_num: totalUsageCount
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
return mergedDetails;
|
|
668
|
+
};
|
|
544
669
|
|
|
545
670
|
// 记录每个优惠券适用的商品ID
|
|
546
671
|
const discountApplicability = new Map();
|
|
@@ -586,7 +711,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
586
711
|
}
|
|
587
712
|
|
|
588
713
|
// 编辑的商品使用了优惠券不可用
|
|
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));
|
|
714
|
+
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
715
|
if (!isAvailableProduct) {
|
|
591
716
|
return false;
|
|
592
717
|
}
|
|
@@ -777,23 +902,23 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
777
902
|
const isLimitedProduct = (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)) && isHolderMatch;
|
|
778
903
|
|
|
779
904
|
// 编辑的商品 使用了优惠券不可用
|
|
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));
|
|
905
|
+
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
906
|
|
|
782
907
|
// 套餐是否可用判断
|
|
783
908
|
const isBundleAvailable = this.checkPackageSubItemUsageRules(discount, flatItem);
|
|
784
909
|
|
|
785
910
|
// 判断优惠券是否适用于该商品
|
|
786
|
-
if (isAvailableProduct && !isItemRewardProductDiscount && isLimitedProduct && timeLimit && isBundleAvailable && discount.config?.isAvailable) {
|
|
911
|
+
if (isAvailableProduct && !isItemRewardProductDiscount && isLimitedProduct && timeLimit && isBundleAvailable && discount.config?.isAvailable && (!(0, _entitlementPass.isEntitlementPass)(discount) || (0, _entitlementPass.getRemainingEntitlementUses)(discount, 0, 1) > 0)) {
|
|
787
912
|
// 记录此优惠券适用的商品
|
|
788
913
|
discountApplicability.get(discount.id)?.push(product.id);
|
|
789
914
|
|
|
790
915
|
// 记录可抵扣的商品详情
|
|
791
916
|
const applicableProducts = discountApplicableProducts.get(discount.id) || [];
|
|
792
917
|
const discountType = discount.tag || discount.type;
|
|
793
|
-
const
|
|
918
|
+
const isFullCoverage = (0, _entitlementPass.isFullCoveragePass)(discount);
|
|
794
919
|
|
|
795
920
|
// 使用数量
|
|
796
|
-
const num =
|
|
921
|
+
const num = isFullCoverage || flatItem?.type === 'main' ? 1 : product.num;
|
|
797
922
|
const productData = {
|
|
798
923
|
amount: product.price * num,
|
|
799
924
|
type: discountType,
|
|
@@ -911,7 +1036,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
911
1036
|
const hasExistingWalletDiscount = product.discount_list?.length && product?.discount_list?.every(discount => {
|
|
912
1037
|
const discountIdentity = discount.id ?? discount.discount?.resource_id;
|
|
913
1038
|
const discountType = discount.tag || discount.type;
|
|
914
|
-
return Boolean(discountIdentity && ['good_pass', 'discount_card', 'product_discount_card'].includes(discountType));
|
|
1039
|
+
return Boolean(discountIdentity && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType));
|
|
915
1040
|
});
|
|
916
1041
|
|
|
917
1042
|
// 已持久化且已有钱包券/折扣卡的商品行跳过,保留后端历史折扣结果。
|
|
@@ -928,8 +1053,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
928
1053
|
// 找到适用于此商品的所有优惠券,仅考虑isSelected不为false的优惠券
|
|
929
1054
|
const applicableDiscounts = sortedDiscountList.filter(discount => {
|
|
930
1055
|
if (this.isItemRewardProductDiscount(product)) return false;
|
|
931
|
-
// 🔥
|
|
932
|
-
const discountType = discount.tag || discount.type;
|
|
1056
|
+
// 🔥 检查策略可用性(针对全额抵扣卡和折扣卡)
|
|
1057
|
+
const discountType = discount.tag || discount.type || '';
|
|
933
1058
|
const currentOriginUid = getOriginProductUid(originProduct);
|
|
934
1059
|
const isReapplyEditModeDiscountForProduct = discount.isEditMode && reapplyBookingDiscountProductUids.size > 0 && currentOriginUid && reapplyBookingDiscountProductUids.has(String(currentOriginUid)) && discount.isSelected === true;
|
|
935
1060
|
if (discount.isEditMode && reapplyBookingDiscountProductUids.size > 0) {
|
|
@@ -937,7 +1062,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
937
1062
|
if (!uid || !reapplyBookingDiscountProductUids.has(uid)) return false;
|
|
938
1063
|
if (!discount.isSelected) return false;
|
|
939
1064
|
}
|
|
940
|
-
if (['good_pass', 'discount_card', 'product_discount_card'].includes(discountType) && !isReapplyEditModeDiscountForProduct) {
|
|
1065
|
+
if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType) && !isReapplyEditModeDiscountForProduct) {
|
|
941
1066
|
// 如果策略检查后没有config,说明不可用
|
|
942
1067
|
if (discount.config === undefined || !discount?.config?.isAvailable) {
|
|
943
1068
|
return false;
|
|
@@ -945,17 +1070,22 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
945
1070
|
}
|
|
946
1071
|
|
|
947
1072
|
// 如果商品价格为 0,其实不需要使用任何优惠券,直接 return true
|
|
948
|
-
//
|
|
949
|
-
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.length && (
|
|
1073
|
+
// 全额抵扣卡在主商品价格为 0 时不可用。
|
|
1074
|
+
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.length && (0, _entitlementPass.isFullCoveragePass)(discount)) return false;
|
|
950
1075
|
|
|
951
1076
|
// 折扣卡商品价格为0时不可用
|
|
952
|
-
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.find(n => n.discount?.resource_id === discount.id) && (
|
|
953
|
-
//
|
|
954
|
-
|
|
955
|
-
|
|
1077
|
+
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.find(n => n.discount?.resource_id === discount.id) && !(0, _entitlementPass.isFullCoveragePass)(discount)) return false;
|
|
1078
|
+
// good_pass 保持一张实例只使用一次。
|
|
1079
|
+
if ((discount.tag || discount.type) === 'good_pass' && usedDiscounts.get(discount.id)) {
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
// 有限次数卡按后端实时 balance 约束本次新增分配;无限卡不看余额。
|
|
1083
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount) && (0, _entitlementPass.getRemainingEntitlementUses)(discount, entitlementUsageCounts.get(discount.id) || 0, 1) <= 0) {
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
956
1086
|
|
|
957
1087
|
// 🔥 检查 maxUsagePerOrder 限制:同一 product_id 的商品券使用数量不能超过配置值
|
|
958
|
-
if ((
|
|
1088
|
+
if ((0, _entitlementPass.isFullCoveragePass)(discount)) {
|
|
959
1089
|
const maxUsagePerOrder = discount.config?.maxUsagePerOrder;
|
|
960
1090
|
if (maxUsagePerOrder && maxUsagePerOrder > 0) {
|
|
961
1091
|
const currentUsedCount = usedProductIdCounts.get(discount.product_id) || 0;
|
|
@@ -1003,28 +1133,28 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1003
1133
|
// 用户显式点击某张卡券时,本次选择优先于自动排序和扫码默认选择。
|
|
1004
1134
|
// 目标仍必须在当前商品的 applicableDiscounts 中,因此不会绕过 Holder、时间或商品范围校验。
|
|
1005
1135
|
const explicitlySelectedDiscount = options?.isSelected === true && options.discountId !== undefined ? applicableDiscounts.find(discount => {
|
|
1006
|
-
const discountType = discount.tag || discount.type;
|
|
1007
|
-
return String(discount.id) === String(options.discountId) && ['good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
1136
|
+
const discountType = discount.tag || discount.type || '';
|
|
1137
|
+
return String(discount.id) === String(options.discountId) && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType);
|
|
1008
1138
|
}) : undefined;
|
|
1009
1139
|
// 折扣卡会复用于整条商品行;商品券必须继续按单张拆分,不能复用该分支。
|
|
1010
|
-
const explicitlySelectedDiscountCard = explicitlySelectedDiscount && (
|
|
1140
|
+
const explicitlySelectedDiscountCard = explicitlySelectedDiscount && !(0, _entitlementPass.isFullCoveragePass)(explicitlySelectedDiscount) ? explicitlySelectedDiscount : undefined;
|
|
1011
1141
|
|
|
1012
1142
|
// 商品数量等变化会触发无 discountId 的重算。只要当前行已应用的折扣卡仍在
|
|
1013
1143
|
// applicableDiscounts 中,就应保留该行选择,避免被自动排序中力度更大的卡替换。
|
|
1014
1144
|
const appliedDiscountCardIds = new Set((product.discount_list || []).filter(item => ['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
1145
|
const appliedDiscountCard = options?.scan ? undefined : applicableDiscounts.find(discount => {
|
|
1016
|
-
const discountType = discount.tag || discount.type;
|
|
1146
|
+
const discountType = discount.tag || discount.type || '';
|
|
1017
1147
|
return discount.isSelected === true && ['discount_card', 'product_discount_card'].includes(discountType) && appliedDiscountCardIds.has(String(discount.id));
|
|
1018
1148
|
});
|
|
1019
1149
|
|
|
1020
|
-
//
|
|
1021
|
-
const scannedSelectedDiscountCard = applicableDiscounts.find(n => n.isScan && n.isSelected && (
|
|
1150
|
+
// Entitlement Pass 始终按协议优先级使用;没有新卡时沿用原选择优先级。
|
|
1151
|
+
const scannedSelectedDiscountCard = applicableDiscounts.find(n => n.isScan && n.isSelected && !(0, _entitlementPass.isFullCoveragePass)(n));
|
|
1022
1152
|
const selectedDiscountCard = explicitlySelectedDiscountCard || appliedDiscountCard || scannedSelectedDiscountCard;
|
|
1023
|
-
const
|
|
1024
|
-
const
|
|
1025
|
-
|
|
1026
|
-
return
|
|
1027
|
-
}), ...applicableDiscounts.filter(discount => (
|
|
1153
|
+
const highestPriorityEntitlementPass = applicableDiscounts.find(discount => (0, _entitlementPass.isEntitlementPass)(discount));
|
|
1154
|
+
const selectedDiscount = highestPriorityEntitlementPass || explicitlySelectedDiscount || selectedDiscountCard || applicableDiscounts[0];
|
|
1155
|
+
const prioritizedApplicableDiscounts = !highestPriorityEntitlementPass && explicitlySelectedDiscount && (0, _entitlementPass.isFullCoveragePass)(explicitlySelectedDiscount) ? [explicitlySelectedDiscount, ...applicableDiscounts.filter(discount => {
|
|
1156
|
+
return (0, _entitlementPass.isFullCoveragePass)(discount) && String(discount.id) !== String(explicitlySelectedDiscount.id);
|
|
1157
|
+
}), ...applicableDiscounts.filter(discount => !(0, _entitlementPass.isFullCoveragePass)(discount))] : applicableDiscounts;
|
|
1028
1158
|
|
|
1029
1159
|
// 如果是手动折扣,则不适用优惠券
|
|
1030
1160
|
let isManualDiscount = false;
|
|
@@ -1140,19 +1270,13 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1140
1270
|
return;
|
|
1141
1271
|
}
|
|
1142
1272
|
|
|
1143
|
-
//
|
|
1144
|
-
const isNeedSplit = (
|
|
1273
|
+
// good_pass 与 Entitlement Pass 都沿用全额抵扣卡的商品行拆分机制。
|
|
1274
|
+
const isNeedSplit = (0, _entitlementPass.isFullCoveragePass)(selectedDiscount);
|
|
1145
1275
|
|
|
1146
1276
|
// 需要拆分出来的数量
|
|
1147
1277
|
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;
|
|
1278
|
+
const fullCoveragePassUsageGroups = isNeedSplit ? buildFullCoveragePassUsageGroups(prioritizedApplicableDiscounts, totalQuantity) : [];
|
|
1279
|
+
const splitCount = isNeedSplit ? fullCoveragePassUsageGroups.reduce((total, group) => total + group.count, 0) : 1;
|
|
1156
1280
|
const arr = [];
|
|
1157
1281
|
|
|
1158
1282
|
// 🔥 主商品和bundle子商品分别处理
|
|
@@ -1160,22 +1284,22 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1160
1284
|
// 主商品:保持原有逻辑
|
|
1161
1285
|
if (splitCount < totalQuantity && isNeedSplit) {
|
|
1162
1286
|
const remainingQuantity = totalQuantity - splitCount;
|
|
1163
|
-
const
|
|
1287
|
+
const applicableNonFullCoveragePassById = new Map(applicableDiscounts.filter(discount => !(0, _entitlementPass.isFullCoveragePass)(discount)).map(discount => [String(discount.id), discount]));
|
|
1164
1288
|
const remainderDiscountList = (product.discount_list || []).filter(discount => {
|
|
1165
1289
|
const discountType = discount.tag || discount.type;
|
|
1166
1290
|
if (discountType === 'promotion') return true;
|
|
1167
|
-
if (
|
|
1291
|
+
if ((0, _entitlementPass.isFullCoveragePass)(discount)) return false;
|
|
1168
1292
|
const resourceId = discount.discount?.resource_id ?? discount.id;
|
|
1169
|
-
return resourceId !== undefined && resourceId !== null &&
|
|
1293
|
+
return resourceId !== undefined && resourceId !== null && applicableNonFullCoveragePassById.has(String(resourceId));
|
|
1170
1294
|
}).map(discount => {
|
|
1171
1295
|
if ((discount.tag || discount.type) === 'promotion') return discount;
|
|
1172
1296
|
const resourceId = discount.discount?.resource_id ?? discount.id;
|
|
1173
|
-
const matchedDiscount =
|
|
1297
|
+
const matchedDiscount = applicableNonFullCoveragePassById.get(String(resourceId));
|
|
1174
1298
|
if (matchedDiscount) {
|
|
1175
1299
|
// 实际场景:3 件折扣卡商品中拆出 1 件改用商品券后,
|
|
1176
1300
|
// 剩余 2 件仍在使用原折扣卡;必须同步登记折扣卡的使用状态,
|
|
1177
1301
|
// 否则 updatedDiscountList 会把它改成未选中并清空 savedAmount。
|
|
1178
|
-
|
|
1302
|
+
markDiscountUsed(matchedDiscount);
|
|
1179
1303
|
const appliedProducts = appliedDiscountProducts.get(matchedDiscount.id) || [];
|
|
1180
1304
|
appliedProducts.push({
|
|
1181
1305
|
...discount,
|
|
@@ -1189,14 +1313,14 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1189
1313
|
_num: remainingQuantity
|
|
1190
1314
|
};
|
|
1191
1315
|
});
|
|
1192
|
-
const hasRetainedWalletDiscount = remainderDiscountList.some(discount =>
|
|
1316
|
+
const hasRetainedWalletDiscount = remainderDiscountList.some(discount => (discount.tag || discount.type) !== 'promotion' && !(0, _entitlementPass.isFullCoveragePass)(discount));
|
|
1193
1317
|
let total = product.origin_total ?? product.total;
|
|
1194
1318
|
if ((product.discount_list || []).some(item => item.type === 'promotion')) {
|
|
1195
1319
|
total = product.total ?? product.origin_total;
|
|
1196
1320
|
}
|
|
1197
1321
|
arr.push(this.hooks.setProduct(originProduct, {
|
|
1198
1322
|
// 商品券只替换被拆出的 splitCount 件。余量行原本仍有效的折扣卡
|
|
1199
|
-
//
|
|
1323
|
+
// 不能随全额抵扣卡拆分一起清除;已取消或已失效的折扣不会进入此列表。
|
|
1200
1324
|
discount_list: remainderDiscountList,
|
|
1201
1325
|
quantity: remainingQuantity,
|
|
1202
1326
|
_id: product._id.split('___')[0],
|
|
@@ -1208,14 +1332,21 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1208
1332
|
} : {})
|
|
1209
1333
|
}));
|
|
1210
1334
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1335
|
+
const discountUsageGroups = isNeedSplit ? fullCoveragePassUsageGroups : [{
|
|
1336
|
+
discount: selectedDiscount,
|
|
1337
|
+
count: 1
|
|
1338
|
+
}];
|
|
1339
|
+
for (let i = 0; i < discountUsageGroups.length; i++) {
|
|
1340
|
+
const {
|
|
1341
|
+
discount: selectedDiscount,
|
|
1342
|
+
count: appliedUseCount
|
|
1343
|
+
} = discountUsageGroups[i];
|
|
1213
1344
|
// 标记优惠券为已使用
|
|
1214
|
-
|
|
1345
|
+
markDiscountUsed(selectedDiscount, appliedUseCount);
|
|
1215
1346
|
// 🔥 更新 product_id 使用计数
|
|
1216
|
-
if ((
|
|
1347
|
+
if ((0, _entitlementPass.isFullCoveragePass)(selectedDiscount)) {
|
|
1217
1348
|
const currentCount = usedProductIdCounts.get(selectedDiscount.product_id) || 0;
|
|
1218
|
-
usedProductIdCounts.set(selectedDiscount.product_id, currentCount +
|
|
1349
|
+
usedProductIdCounts.set(selectedDiscount.product_id, currentCount + appliedUseCount);
|
|
1219
1350
|
}
|
|
1220
1351
|
|
|
1221
1352
|
// 记录实际应用了优惠券的商品信息
|
|
@@ -1277,7 +1408,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1277
1408
|
const mainProductDiscountAmount = amount;
|
|
1278
1409
|
const lineDiscountAmount = new _decimal.default(mainProductDiscountAmount).plus(optionDiscountAmount).toNumber();
|
|
1279
1410
|
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
1280
|
-
const
|
|
1411
|
+
const isFullCoverage = (0, _entitlementPass.isFullCoveragePass)(selectedDiscount);
|
|
1281
1412
|
const discountDetail = {
|
|
1282
1413
|
amount: lineDiscountAmount,
|
|
1283
1414
|
type: selectedDiscount.tag === 'product_discount_card' ? 'discount_card' : discountType,
|
|
@@ -1293,10 +1424,10 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1293
1424
|
discount_product_id: selectedDiscount.product_id
|
|
1294
1425
|
},
|
|
1295
1426
|
// 前端使用的num数量,为了计算优惠金额
|
|
1296
|
-
_num:
|
|
1427
|
+
_num: isFullCoverage ? appliedUseCount : product.num,
|
|
1297
1428
|
config: selectedDiscount?.config,
|
|
1298
1429
|
metadata: {
|
|
1299
|
-
num: 1,
|
|
1430
|
+
num: isFullCoverage ? appliedUseCount : 1,
|
|
1300
1431
|
discount_rule_uncheck_flag: selectedDiscount?.discount_rule_uncheck_flag,
|
|
1301
1432
|
// 🔥 order_level 分摊差值
|
|
1302
1433
|
...(productDiscountDifference !== undefined && {
|
|
@@ -1315,9 +1446,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1315
1446
|
if (product.isClient) {
|
|
1316
1447
|
arr.push(this.hooks.setProduct(originProduct, {
|
|
1317
1448
|
discount_list: [discountDetail],
|
|
1318
|
-
//
|
|
1319
|
-
price:
|
|
1320
|
-
quantity: isNeedSplit ?
|
|
1449
|
+
// 全额抵扣卡:主商品价以折后价为准;勿用 product.price - amount(amount 含 option 优惠时会算错)
|
|
1450
|
+
price: isFullCoverage ? mainProductSellingPrice : product.price,
|
|
1451
|
+
quantity: isNeedSplit ? appliedUseCount : product.quantity,
|
|
1321
1452
|
origin_total: (0, _utils2.getProductOriginTotalPrice)({
|
|
1322
1453
|
product: {
|
|
1323
1454
|
original_price: product.original_price
|
|
@@ -1334,8 +1465,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1334
1465
|
arr.push(this.hooks.setProduct(originProduct, {
|
|
1335
1466
|
discount_list: this.filterDiscountListByType(product.discount_list, 'promotion').concat([discountDetail]),
|
|
1336
1467
|
_id: product._id.split('___')[0] + '___' + selectedDiscount.id + index,
|
|
1337
|
-
price:
|
|
1338
|
-
quantity: isNeedSplit ?
|
|
1468
|
+
price: isFullCoverage ? 0 : product.price,
|
|
1469
|
+
quantity: isNeedSplit ? appliedUseCount : product.quantity,
|
|
1339
1470
|
total: total,
|
|
1340
1471
|
origin_total: productOriginTotal,
|
|
1341
1472
|
main_product_selling_price: mainProductSellingPrice,
|
|
@@ -1352,14 +1483,17 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1352
1483
|
const discountNum = splitCount;
|
|
1353
1484
|
const normalNum = totalQuantity - discountNum;
|
|
1354
1485
|
|
|
1355
|
-
//
|
|
1356
|
-
for (let i = 0; i <
|
|
1357
|
-
const
|
|
1358
|
-
|
|
1486
|
+
// 同一卡实例的本次新增使用合并为一条,metadata.num 记录实际次数。
|
|
1487
|
+
for (let i = 0; i < fullCoveragePassUsageGroups.length; i++) {
|
|
1488
|
+
const {
|
|
1489
|
+
discount: selectedDiscount,
|
|
1490
|
+
count: appliedUseCount
|
|
1491
|
+
} = fullCoveragePassUsageGroups[i];
|
|
1492
|
+
markDiscountUsed(selectedDiscount, appliedUseCount);
|
|
1359
1493
|
// 🔥 更新 product_id 使用计数
|
|
1360
|
-
if ((
|
|
1494
|
+
if ((0, _entitlementPass.isFullCoveragePass)(selectedDiscount)) {
|
|
1361
1495
|
const currentCount = usedProductIdCounts.get(selectedDiscount.product_id) || 0;
|
|
1362
|
-
usedProductIdCounts.set(selectedDiscount.product_id, currentCount +
|
|
1496
|
+
usedProductIdCounts.set(selectedDiscount.product_id, currentCount + appliedUseCount);
|
|
1363
1497
|
}
|
|
1364
1498
|
|
|
1365
1499
|
// 🔥 生成唯一的 _id
|
|
@@ -1367,11 +1501,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1367
1501
|
|
|
1368
1502
|
// 计算折扣后的价格(使用 getDiscountAmount,内部已处理 maxDeductionAmount)
|
|
1369
1503
|
const discountedPrice = (0, _utils.getDiscountAmount)(selectedDiscount, product.origin_total, product.origin_total);
|
|
1370
|
-
const
|
|
1504
|
+
const bundleDiscountAmountPerItem = new _decimal.default(product.origin_total || 0).minus(discountedPrice).toNumber();
|
|
1505
|
+
const bundleDiscountAmount = new _decimal.default(bundleDiscountAmountPerItem).mul(appliedUseCount).toNumber();
|
|
1371
1506
|
debugger;
|
|
1372
1507
|
const discountDetail = {
|
|
1373
1508
|
amount: bundleDiscountAmount,
|
|
1374
|
-
type:
|
|
1509
|
+
type: selectedDiscount.tag || selectedDiscount.type,
|
|
1375
1510
|
discount: {
|
|
1376
1511
|
fixed_amount: product.origin_total,
|
|
1377
1512
|
discount_calculation_mode: selectedDiscount?.metadata?.discount_calculation_mode,
|
|
@@ -1385,9 +1520,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1385
1520
|
// 🔥 使用拆分后的唯一 _id
|
|
1386
1521
|
custom_product_bundle_map_id: uniqueId,
|
|
1387
1522
|
discount_rule_uncheck_flag: selectedDiscount?.discount_rule_uncheck_flag,
|
|
1388
|
-
num:
|
|
1523
|
+
num: appliedUseCount
|
|
1389
1524
|
},
|
|
1390
|
-
_num:
|
|
1525
|
+
_num: appliedUseCount,
|
|
1391
1526
|
config: selectedDiscount?.config
|
|
1392
1527
|
};
|
|
1393
1528
|
|
|
@@ -1399,11 +1534,11 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1399
1534
|
...flatItem,
|
|
1400
1535
|
// 🔥 使用唯一的 _id
|
|
1401
1536
|
_id: uniqueId,
|
|
1402
|
-
num:
|
|
1403
|
-
quantity:
|
|
1537
|
+
num: appliedUseCount,
|
|
1538
|
+
quantity: appliedUseCount,
|
|
1404
1539
|
price: discountedPrice,
|
|
1405
1540
|
// 折扣后的价格
|
|
1406
|
-
total: discountedPrice,
|
|
1541
|
+
total: new _decimal.default(discountedPrice).mul(appliedUseCount).toNumber(),
|
|
1407
1542
|
discount_list: [discountDetail],
|
|
1408
1543
|
processed: true,
|
|
1409
1544
|
_discountId: selectedDiscount.id
|
|
@@ -1425,9 +1560,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1425
1560
|
} else {
|
|
1426
1561
|
// 折扣卡:不拆分数量,直接应用
|
|
1427
1562
|
const selectedDiscount = selectedDiscountCard || applicableDiscounts[0];
|
|
1428
|
-
|
|
1563
|
+
markDiscountUsed(selectedDiscount);
|
|
1429
1564
|
// 🔥 更新 product_id 使用计数(虽然此处主要是折扣卡,但保持一致性)
|
|
1430
|
-
if ((
|
|
1565
|
+
if ((0, _entitlementPass.isFullCoveragePass)(selectedDiscount)) {
|
|
1431
1566
|
const currentCount = usedProductIdCounts.get(selectedDiscount.product_id) || 0;
|
|
1432
1567
|
usedProductIdCounts.set(selectedDiscount.product_id, currentCount + 1);
|
|
1433
1568
|
}
|
|
@@ -1560,11 +1695,10 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1560
1695
|
// 没有bundle,直接使用主商品处理结果
|
|
1561
1696
|
result.push(...mainProductArr);
|
|
1562
1697
|
} else {
|
|
1563
|
-
// 🔥 有bundle
|
|
1698
|
+
// 🔥 有 bundle,需要检查子商品是否应用了全额抵扣卡
|
|
1564
1699
|
// 1. 先收集所有bundle子商品的处理结果
|
|
1565
1700
|
const bundleProcessingInfo = new Map();
|
|
1566
|
-
let
|
|
1567
|
-
|
|
1701
|
+
let hasFullCoveragePassApplied = false;
|
|
1568
1702
|
if (product.bundle && Array.isArray(product.bundle)) {
|
|
1569
1703
|
product.bundle.forEach((bundleItem, bundleIndex) => {
|
|
1570
1704
|
const bundleItemId = `${product._id}_bundle_${bundleIndex}`;
|
|
@@ -1575,10 +1709,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1575
1709
|
} else {
|
|
1576
1710
|
// 处理过的bundle item
|
|
1577
1711
|
bundleProcessingInfo.set(bundleIndex, processedBundleItems);
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
hasGoodPassApplied = true;
|
|
1712
|
+
const hasFullCoveragePass = processedBundleItems.some(item => item.discount_list?.some(discount => (0, _entitlementPass.isFullCoveragePass)(discount)));
|
|
1713
|
+
if (hasFullCoveragePass) {
|
|
1714
|
+
hasFullCoveragePassApplied = true;
|
|
1582
1715
|
}
|
|
1583
1716
|
}
|
|
1584
1717
|
});
|
|
@@ -1586,7 +1719,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1586
1719
|
|
|
1587
1720
|
// 🔥 2. 如果有子商品应用了商品券,且主商品数量大于1,需要拆分主商品
|
|
1588
1721
|
const mainProductQuantity = mainProductArr[0] ? this.hooks.getProduct(mainProductArr[0]).quantity : 1;
|
|
1589
|
-
if (
|
|
1722
|
+
if (hasFullCoveragePassApplied && mainProductArr.length === 1 && mainProductQuantity > 1) {
|
|
1590
1723
|
const mainProduct = mainProductArr[0];
|
|
1591
1724
|
const mainProductData = this.hooks.getProduct(mainProduct);
|
|
1592
1725
|
|
|
@@ -1890,6 +2023,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1890
2023
|
|
|
1891
2024
|
// 获取此优惠券实际应用的商品详情
|
|
1892
2025
|
const appliedProductDetails = appliedDiscountProducts.get(discount.id) || [];
|
|
2026
|
+
const committedAppliedProductDetails = (0, _entitlementPass.isEntitlementPass)(discount) ? mergeEntitlementAppliedProductDetails(discount.committedAppliedProductDetails || []) : [];
|
|
2027
|
+
const allAppliedProductDetails = (0, _entitlementPass.isEntitlementPass)(discount) ? mergeEntitlementAppliedProductDetails([...committedAppliedProductDetails, ...appliedProductDetails]) : appliedProductDetails;
|
|
1893
2028
|
|
|
1894
2029
|
// 根据是否有适用商品决定isAvailable
|
|
1895
2030
|
const isAvailable = applicableProducts.length > 0;
|
|
@@ -1904,11 +2039,11 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1904
2039
|
applicableProductIds: applicableProducts,
|
|
1905
2040
|
// 添加可抵扣的商品详情
|
|
1906
2041
|
applicableProductDetails: applicableProductDetails,
|
|
1907
|
-
//
|
|
1908
|
-
appliedProductDetails:
|
|
2042
|
+
// 已提交的历史使用不可撤销;只取消本次新增分配。
|
|
2043
|
+
appliedProductDetails: committedAppliedProductDetails
|
|
1909
2044
|
};
|
|
1910
2045
|
}
|
|
1911
|
-
if (
|
|
2046
|
+
if (isDiscountUsed(discount)) {
|
|
1912
2047
|
return {
|
|
1913
2048
|
...discount,
|
|
1914
2049
|
isSelected: true,
|
|
@@ -1920,7 +2055,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1920
2055
|
// 添加可抵扣的商品详情
|
|
1921
2056
|
applicableProductDetails: applicableProductDetails,
|
|
1922
2057
|
// 添加实际抵扣的商品详情
|
|
1923
|
-
appliedProductDetails:
|
|
2058
|
+
appliedProductDetails: allAppliedProductDetails
|
|
1924
2059
|
};
|
|
1925
2060
|
}
|
|
1926
2061
|
let isSelected = typeof discount.isSelected === 'boolean' ? discount.isSelected : undefined;
|
|
@@ -1928,7 +2063,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1928
2063
|
// 如果是可用的 并且没有手动标记 则设置为选中
|
|
1929
2064
|
if (isAvailable && appliedProductDetails.length && !discount.isManualSelect) {
|
|
1930
2065
|
isSelected = true;
|
|
1931
|
-
} else if (!appliedProductDetails.length) {
|
|
2066
|
+
} else if (!appliedProductDetails.length && !committedAppliedProductDetails.length) {
|
|
1932
2067
|
isSelected = false;
|
|
1933
2068
|
}
|
|
1934
2069
|
return {
|
|
@@ -1942,8 +2077,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1942
2077
|
applicableProductIds: applicableProducts,
|
|
1943
2078
|
// 添加可抵扣的商品详情
|
|
1944
2079
|
applicableProductDetails: applicableProductDetails,
|
|
1945
|
-
//
|
|
1946
|
-
appliedProductDetails:
|
|
2080
|
+
// 编辑订单的历史使用继续保留,本次没有新增使用。
|
|
2081
|
+
appliedProductDetails: committedAppliedProductDetails
|
|
1947
2082
|
};
|
|
1948
2083
|
});
|
|
1949
2084
|
return {
|