@pisell/pisellos 0.0.571 → 0.0.572
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/effects/index.d.ts +2 -2
- package/dist/model/strategy/adapter/dataVariant/type.d.ts +3 -3
- package/dist/model/strategy/adapter/itemRule/type.d.ts +6 -6
- package/dist/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/dist/model/strategy/adapter/promotion/type.d.ts +5 -5
- package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
- package/dist/model/strategy/type.d.ts +1 -1
- package/dist/modules/BookingContext/types.d.ts +4 -4
- package/dist/modules/BookingContext/utils/resourceErrors.d.ts +1 -1
- package/dist/modules/Customer/types.d.ts +1 -1
- package/dist/modules/Discount/availability.d.ts +7 -0
- package/dist/modules/Discount/availability.js +52 -0
- package/dist/modules/Discount/entitlementPass.d.ts +34 -0
- package/dist/modules/Discount/entitlementPass.js +147 -0
- package/dist/modules/Discount/entitlementState.d.ts +8 -0
- package/dist/modules/Discount/entitlementState.js +205 -0
- package/dist/modules/Discount/index.d.ts +5 -2
- package/dist/modules/Discount/index.js +124 -25
- package/dist/modules/Discount/types.d.ts +29 -6
- package/dist/modules/Holder/types.d.ts +1 -1
- package/dist/modules/OpenData/types.d.ts +1 -1
- package/dist/modules/Order/types.d.ts +5 -5
- package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +1 -1
- package/dist/modules/Order/utils/orderCollectionIdentity.d.ts +1 -1
- package/dist/modules/Order/utils.d.ts +1 -1
- package/dist/modules/Payment/types.d.ts +7 -7
- package/dist/modules/Product/index.d.ts +1 -1
- package/dist/modules/ResourcePlanner/types.d.ts +4 -4
- package/dist/modules/Rules/entitlementLines.d.ts +8 -0
- package/dist/modules/Rules/entitlementLines.js +159 -0
- package/dist/modules/Rules/index.d.ts +13 -3
- package/dist/modules/Rules/index.js +617 -342
- package/dist/modules/Rules/types.d.ts +4 -4
- package/dist/modules/ScanOrderLogger/types.d.ts +2 -2
- package/dist/modules/Schedule/getDateIsInSchedule.d.ts +1 -1
- package/dist/modules/Schedule/type.d.ts +7 -7
- package/dist/modules/Schedule/types.d.ts +9 -9
- package/dist/plugins/app.d.ts +1 -1
- package/dist/plugins/request.d.ts +2 -2
- package/dist/plugins/window.d.ts +2 -2
- package/dist/server/modules/floor-plan/types.d.ts +1 -1
- package/dist/server/modules/menu/types.d.ts +1 -1
- package/dist/server/modules/order/types.d.ts +16 -16
- package/dist/server/modules/products/types.d.ts +1 -1
- package/dist/server/modules/resource/types.d.ts +1 -1
- package/dist/server/modules/schedule/utils.d.ts +1 -1
- package/dist/server/types.d.ts +4 -4
- package/dist/server/utils/small-ticket.d.ts +1 -1
- package/dist/solution/BaseSales/index.d.ts +1 -1
- package/dist/solution/BaseSales/types.d.ts +7 -7
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -1
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingByStep/utils/resources.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +13 -1
- package/dist/solution/BookingTicket/types.d.ts +3 -3
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +1 -1
- package/dist/solution/BookingTicket/utils/bookingStatus.d.ts +1 -1
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +1 -1
- package/dist/solution/Sales/types.d.ts +4 -4
- package/dist/solution/ScanOrder/types.d.ts +5 -5
- package/dist/solution/ShopDiscount/index.d.ts +3 -0
- package/dist/solution/ShopDiscount/index.js +161 -78
- package/dist/solution/ShopDiscount/types.d.ts +1 -1
- package/dist/solution/ShopDiscount/utils.d.ts +3 -2
- package/dist/solution/ShopDiscount/utils.js +40 -1
- package/dist/solution/UnifiedBookingSales/types.d.ts +1 -1
- package/dist/solution/VenueBooking/types.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/task.d.ts +2 -2
- package/dist/utils/watch.d.ts +2 -2
- package/lib/effects/index.d.ts +2 -2
- package/lib/model/strategy/adapter/dataVariant/type.d.ts +3 -3
- package/lib/model/strategy/adapter/itemRule/type.d.ts +6 -6
- package/lib/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/lib/model/strategy/adapter/promotion/type.d.ts +5 -5
- package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
- package/lib/model/strategy/type.d.ts +1 -1
- package/lib/modules/BookingContext/types.d.ts +4 -4
- package/lib/modules/BookingContext/utils/resourceErrors.d.ts +1 -1
- package/lib/modules/Customer/types.d.ts +1 -1
- package/lib/modules/Discount/availability.d.ts +7 -0
- package/lib/modules/Discount/availability.js +46 -0
- package/lib/modules/Discount/entitlementPass.d.ts +34 -0
- package/lib/modules/Discount/entitlementPass.js +141 -0
- package/lib/modules/Discount/entitlementState.d.ts +8 -0
- package/lib/modules/Discount/entitlementState.js +150 -0
- package/lib/modules/Discount/index.d.ts +5 -2
- package/lib/modules/Discount/index.js +59 -14
- package/lib/modules/Discount/types.d.ts +29 -6
- package/lib/modules/Holder/types.d.ts +1 -1
- package/lib/modules/OpenData/types.d.ts +1 -1
- package/lib/modules/Order/types.d.ts +5 -5
- package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +1 -1
- package/lib/modules/Order/utils/orderCollectionIdentity.d.ts +1 -1
- package/lib/modules/Order/utils.d.ts +1 -1
- package/lib/modules/Payment/types.d.ts +7 -7
- package/lib/modules/Product/index.d.ts +1 -1
- package/lib/modules/ResourcePlanner/types.d.ts +4 -4
- package/lib/modules/Rules/entitlementLines.d.ts +8 -0
- package/lib/modules/Rules/entitlementLines.js +128 -0
- package/lib/modules/Rules/index.d.ts +13 -3
- package/lib/modules/Rules/index.js +284 -61
- package/lib/modules/Rules/types.d.ts +4 -4
- package/lib/modules/ScanOrderLogger/types.d.ts +2 -2
- package/lib/modules/Schedule/getDateIsInSchedule.d.ts +1 -1
- package/lib/modules/Schedule/type.d.ts +7 -7
- package/lib/modules/Schedule/types.d.ts +9 -9
- package/lib/plugins/app.d.ts +1 -1
- package/lib/plugins/request.d.ts +2 -2
- package/lib/plugins/window.d.ts +2 -2
- package/lib/server/modules/floor-plan/types.d.ts +1 -1
- package/lib/server/modules/menu/types.d.ts +1 -1
- package/lib/server/modules/order/types.d.ts +16 -16
- package/lib/server/modules/products/types.d.ts +1 -1
- package/lib/server/modules/resource/types.d.ts +1 -1
- package/lib/server/modules/schedule/utils.d.ts +1 -1
- package/lib/server/types.d.ts +4 -4
- package/lib/server/utils/small-ticket.d.ts +1 -1
- package/lib/solution/BaseSales/index.d.ts +1 -1
- package/lib/solution/BaseSales/types.d.ts +7 -7
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -1
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingByStep/utils/resources.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +13 -1
- package/lib/solution/BookingTicket/types.d.ts +3 -3
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +1 -1
- package/lib/solution/BookingTicket/utils/bookingStatus.d.ts +1 -1
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +1 -1
- package/lib/solution/Sales/types.d.ts +4 -4
- package/lib/solution/ScanOrder/types.d.ts +5 -5
- package/lib/solution/ShopDiscount/index.d.ts +3 -0
- package/lib/solution/ShopDiscount/index.js +55 -18
- package/lib/solution/ShopDiscount/types.d.ts +1 -1
- package/lib/solution/ShopDiscount/utils.d.ts +3 -2
- package/lib/solution/ShopDiscount/utils.js +39 -3
- package/lib/solution/UnifiedBookingSales/types.d.ts +1 -1
- package/lib/solution/VenueBooking/types.d.ts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/utils/task.d.ts +2 -2
- package/lib/utils/watch.d.ts +2 -2
- package/package.json +19 -18
|
@@ -11,6 +11,11 @@ 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 _evaluator = require("../../model/strategy/adapter/walletPass/evaluator");
|
|
15
|
+
var _entitlementPass = require("../Discount/entitlementPass");
|
|
16
|
+
var _availability = require("../Discount/availability");
|
|
17
|
+
var _entitlementState = require("../Discount/entitlementState");
|
|
18
|
+
var _entitlementLines = require("./entitlementLines");
|
|
14
19
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
15
20
|
class RulesModule extends _BaseModule.BaseModule {
|
|
16
21
|
defaultName = 'rules';
|
|
@@ -18,6 +23,11 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
18
23
|
store;
|
|
19
24
|
window; // WindowPlugin 实例
|
|
20
25
|
hooks;
|
|
26
|
+
entitlementPassEnabled = false;
|
|
27
|
+
defaultWalletPassEvaluator;
|
|
28
|
+
setEntitlementPassEnabled(enabled) {
|
|
29
|
+
this.entitlementPassEnabled = enabled;
|
|
30
|
+
}
|
|
21
31
|
constructor(name, version) {
|
|
22
32
|
super(name, version);
|
|
23
33
|
this.hooks = {};
|
|
@@ -38,21 +48,48 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
38
48
|
getWalletPassEvaluator() {
|
|
39
49
|
return this.window.getWalletPassEvaluator?.();
|
|
40
50
|
}
|
|
51
|
+
normalizeHolderRecordIds(value) {
|
|
52
|
+
const records = Array.isArray(value) ? value : [value];
|
|
53
|
+
return Array.from(new Set(records.map(record => {
|
|
54
|
+
if (record && typeof record === 'object') {
|
|
55
|
+
return record.form_record_id;
|
|
56
|
+
}
|
|
57
|
+
return record;
|
|
58
|
+
}).filter(record => record !== undefined && record !== null && record !== '' && record !== 0 && record !== '0').map(String)));
|
|
59
|
+
}
|
|
41
60
|
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
/**
|
|
62
|
+
* 商品未开启 Holder 选择时跳过匹配;已开启时,特定 form Holder 卡
|
|
63
|
+
* 只适用于同一 Holder Record,是否必填不影响匹配规则。
|
|
64
|
+
*/
|
|
65
|
+
checkHolderMatch(discount, product, holderSource, holders) {
|
|
44
66
|
// 非表单类型 holder 视为匹配
|
|
45
67
|
if (discount.holder?.holder_type !== 'form') return true;
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
|
|
69
|
+
// 旧券沿用预约主体和单个 Holder 的匹配口径。
|
|
70
|
+
if (!(0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
71
|
+
if (!product.isNeedHolder) return true;
|
|
72
|
+
const holder = holders?.[0];
|
|
73
|
+
const orderHolderId = holder && typeof holder === 'object' ? holder.form_record_id : holder;
|
|
74
|
+
const productHolderId = Array.isArray(product.holder_id) ? product.holder_id[0] : product.holder_id;
|
|
75
|
+
if (!orderHolderId && !productHolderId) return false;
|
|
76
|
+
return (productHolderId || orderHolderId) === discount.holder.holder_id;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// getProduct 会裁剪商品字段,从原始商品读取配置,套餐子项沿用父商品。
|
|
80
|
+
const holderConfig = holderSource?.holder_config ?? holderSource?.metadata?.holder_config ?? holderSource?.origin?.holder_config ?? holderSource?.metadata?.origin?.holder_config ?? holderSource?._origin?.holder_config ?? holderSource?._origin?.product?.holder_config ?? holderSource?._originData?.product?.holder_config ?? holderSource?._productInit?.holder_config ?? holderSource?._productOrigin?.holder_config ?? holderSource?._extend?.holder_config ?? holderSource?._extend?.product?.holder_config ?? holderSource?._extend?.product?.metadata?.holder_config;
|
|
81
|
+
const holderStatus = holderConfig?.status;
|
|
82
|
+
const holderEnabled = holderStatus == null || holderStatus === '' || holderStatus === true || ['enable', '1'].includes(String(holderStatus).trim().toLowerCase());
|
|
83
|
+
if (!holderEnabled || !holderConfig?.resource_id) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
const discountHolderIds = this.normalizeHolderRecordIds(discount.holder.holder_id);
|
|
87
|
+
if (!discountHolderIds.length) return false;
|
|
88
|
+
const productHolderIds = this.normalizeHolderRecordIds(product.holder_id);
|
|
89
|
+
const candidateHolderIds = productHolderIds.length || Array.isArray(product.holder_id) ? productHolderIds : this.normalizeHolderRecordIds(holders);
|
|
90
|
+
if (!candidateHolderIds.length) return false;
|
|
91
|
+
const discountHolderIdSet = new Set(discountHolderIds);
|
|
92
|
+
return candidateHolderIds.every(holderId => discountHolderIdSet.has(holderId));
|
|
56
93
|
}
|
|
57
94
|
|
|
58
95
|
// 判断discountList 是否可以对当前productList生效
|
|
@@ -74,7 +111,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
74
111
|
}
|
|
75
112
|
|
|
76
113
|
// 编辑商品已有商品券
|
|
77
|
-
if (productList.every(item => {
|
|
114
|
+
if (!newDiscountList.some(_entitlementPass.isEntitlementPass) && productList.length > 0 && productList.every(item => {
|
|
78
115
|
const product = this.hooks.getProduct(item);
|
|
79
116
|
return product.booking_id && (product.discount_list?.length || product.total == 0);
|
|
80
117
|
})) {
|
|
@@ -89,10 +126,10 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
89
126
|
// 合并新旧折扣列表,并计算折扣结果,注意,如果旧折扣里有 isEditMode 为 true 的优惠券,则不合并
|
|
90
127
|
// 保留之前扫码得到的商品券(isScan: true)
|
|
91
128
|
const filteredOldDiscountList = oldDiscountList.filter(discount => !discount.isEditMode && (discount.tag !== 'good_pass' || discount.isScan));
|
|
92
|
-
const mergedDiscountList = (0, _utils.uniqueById)([...filteredOldDiscountList, ...newDiscountList]);
|
|
129
|
+
const mergedDiscountList = (0, _utils.uniqueById)([...newDiscountList.filter(_entitlementPass.isEntitlementPass), ...filteredOldDiscountList, ...newDiscountList]);
|
|
93
130
|
const result = this.calcDiscount({
|
|
94
131
|
discountList: mergedDiscountList,
|
|
95
|
-
productList: [...productList],
|
|
132
|
+
productList: this.entitlementPassEnabled ? (0, _lodashEs.cloneDeep)(productList) : [...productList],
|
|
96
133
|
orderTotalAmount,
|
|
97
134
|
holders,
|
|
98
135
|
isFormSubject
|
|
@@ -102,21 +139,23 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
102
139
|
|
|
103
140
|
// 检查是否有产品使用了新折扣
|
|
104
141
|
let hasApplicableDiscount = false;
|
|
105
|
-
const newDiscountIds = newDiscountList.map(discount => discount.id);
|
|
142
|
+
const newDiscountIds = new Set(newDiscountList.map(discount => String(discount.id)));
|
|
106
143
|
|
|
107
144
|
// 遍历处理后的产品列表,检查是否有产品使用了新折扣
|
|
108
145
|
result.productList.forEach(product => {
|
|
146
|
+
const mappedProduct = this.hooks.getProduct(product);
|
|
147
|
+
if ((0, _entitlementState.isPersistedEntitlementLine)(product, mappedProduct)) return;
|
|
109
148
|
const {
|
|
110
149
|
discount_list,
|
|
111
150
|
bundle
|
|
112
|
-
} =
|
|
151
|
+
} = mappedProduct;
|
|
113
152
|
const allDiscountList = [...(discount_list || [])];
|
|
114
153
|
(bundle || []).forEach(item => {
|
|
115
154
|
allDiscountList.push(...(item?.discount_list || []));
|
|
116
155
|
});
|
|
117
156
|
if (allDiscountList && allDiscountList.length > 0) {
|
|
118
157
|
// 检查是否有使用新折扣的情况
|
|
119
|
-
const usedNewDiscount = allDiscountList.some(discount => newDiscountIds.
|
|
158
|
+
const usedNewDiscount = allDiscountList.some(discount => newDiscountIds.has(String(discount?.discount?.resource_id)));
|
|
120
159
|
if (usedNewDiscount) {
|
|
121
160
|
hasApplicableDiscount = true;
|
|
122
161
|
}
|
|
@@ -130,6 +169,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
130
169
|
}
|
|
131
170
|
return {
|
|
132
171
|
isAvailable: hasApplicableDiscount,
|
|
172
|
+
evaluatedDiscountList: result.discountList,
|
|
133
173
|
discountList: hasApplicableDiscount ? result.discountList : oldDiscountList,
|
|
134
174
|
productList: hasApplicableDiscount ? result.productList : productList,
|
|
135
175
|
unavailableReason
|
|
@@ -154,7 +194,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
154
194
|
for (const discount of discountList) {
|
|
155
195
|
for (const item of productList) {
|
|
156
196
|
const product = this.hooks.getProduct(item);
|
|
157
|
-
const bookingTime = (
|
|
197
|
+
const bookingTime = (0, _dayjs.default)(product?.startDate || undefined).format('YYYY-MM-DD HH:mm:ss');
|
|
158
198
|
const filteredList = (0, _utils.filterDiscountListByBookingTime)([discount], bookingTime);
|
|
159
199
|
if (filteredList.length === 0) {
|
|
160
200
|
return _types.UnavailableReason.TimeLimit;
|
|
@@ -165,6 +205,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
165
205
|
// 检查商品适用性
|
|
166
206
|
for (const discount of discountList) {
|
|
167
207
|
const limitedData = discount.limited_relation_product_data;
|
|
208
|
+
if (!limitedData) return _types.UnavailableReason.ProductNotApplicable;
|
|
168
209
|
let hasApplicableProduct = false;
|
|
169
210
|
for (const item of productList) {
|
|
170
211
|
const product = this.hooks.getProduct(item);
|
|
@@ -190,6 +231,37 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
190
231
|
orderTotalAmount
|
|
191
232
|
}, options) {
|
|
192
233
|
const resolveIsFormSubject = product => typeof isFormSubject === 'function' ? Boolean(isFormSubject(product)) : Boolean(isFormSubject);
|
|
234
|
+
discountList = this.entitlementPassEnabled ? (0, _entitlementState.withCommittedEntitlementDetails)(discountList, productList, this.hooks.getProduct) : discountList.filter(item => !(0, _entitlementPass.isEntitlementPass)(item));
|
|
235
|
+
const usableEntitlements = discountList.filter(discount => (0, _entitlementPass.isEntitlementPass)(discount) && !discount.isEditMode && !discount.isManualSelect && !(0, _availability.getDiscountUsageBlockReason)(discount));
|
|
236
|
+
const canUseEntitlement = originProduct => {
|
|
237
|
+
const product = this.hooks.getProduct(originProduct);
|
|
238
|
+
if (product.vouchersApplicable === false) return false;
|
|
239
|
+
// 预判任一份可用就按份拆开,实际抵扣仍逐份核对 Holder。
|
|
240
|
+
const holderProducts = Array.isArray(product.holder_id) && product.holder_id.length > 1 ? product.holder_id.map(holder_id => ({
|
|
241
|
+
...product,
|
|
242
|
+
holder_id
|
|
243
|
+
})) : [product];
|
|
244
|
+
const items = [{
|
|
245
|
+
type: 'main',
|
|
246
|
+
id: product.id,
|
|
247
|
+
price: product.original_price ?? product.price
|
|
248
|
+
}, ...(product.bundle || []).map(bundleItem => ({
|
|
249
|
+
type: 'bundle',
|
|
250
|
+
id: bundleItem._bundle_product_id ?? bundleItem.bundle_product_id ?? bundleItem.product_id,
|
|
251
|
+
price: bundleItem.original_price ?? bundleItem.price,
|
|
252
|
+
bundleItem
|
|
253
|
+
}))];
|
|
254
|
+
return usableEntitlements.some(discount => {
|
|
255
|
+
const scope = discount.limited_relation_product_data;
|
|
256
|
+
if (!scope || !holderProducts.some(item => this.checkHolderMatch(discount, item, originProduct, holders)) || !(0, _utils.filterDiscountListByBookingTime)([discount], (0, _dayjs.default)(product.startDate || undefined).format('YYYY-MM-DD HH:mm:ss')).length) return false;
|
|
257
|
+
return items.some(item => Number(item.price) > 0 && (scope.type === 'product_all' ? !(scope.filter === 1 && scope.exclude_product_ids?.some(id => String(id) === String(item.id))) : !!scope.product_ids?.includes(item.id)) && this.checkPackageSubItemUsageRules(discount, {
|
|
258
|
+
...item,
|
|
259
|
+
originProduct,
|
|
260
|
+
parentProduct: product
|
|
261
|
+
}));
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
if (this.entitlementPassEnabled) productList = (0, _entitlementLines.prepareEntitlementLines)(productList, this.hooks, canUseEntitlement);
|
|
193
265
|
|
|
194
266
|
// 识别出来是不是在编辑的界面里又新增了商品
|
|
195
267
|
// 这种情况下,如果有可用的优惠券,也会自动勾选上
|
|
@@ -222,7 +294,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
222
294
|
if (discount.isEditMode) {
|
|
223
295
|
editModeDiscount.push(discount);
|
|
224
296
|
} else {
|
|
225
|
-
addModeDiscount.push(discount)
|
|
297
|
+
addModeDiscount.push((0, _entitlementPass.isEntitlementPass)(discount) ? {
|
|
298
|
+
...discount,
|
|
299
|
+
config: undefined,
|
|
300
|
+
reason: undefined,
|
|
301
|
+
reasonCode: undefined
|
|
302
|
+
} : discount);
|
|
226
303
|
}
|
|
227
304
|
});
|
|
228
305
|
|
|
@@ -289,8 +366,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
289
366
|
bundleIndex,
|
|
290
367
|
// 虚拟商品属性
|
|
291
368
|
price: Number(bundleItem.price || 0),
|
|
292
|
-
id: bundleItem._bundle_product_id,
|
|
293
|
-
// 🔥 使用 _bundle_product_id
|
|
369
|
+
id: bundleItem._bundle_product_id ?? bundleItem.bundle_product_id ?? bundleItem.product_id,
|
|
294
370
|
_id: `${product._id}_bundle_${bundleIndex}`,
|
|
295
371
|
parentId: product._id,
|
|
296
372
|
num: bundleItem.num || 1,
|
|
@@ -384,6 +460,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
384
460
|
}
|
|
385
461
|
return 0;
|
|
386
462
|
}
|
|
463
|
+
if ((0, _entitlementPass.isEntitlementPass)(a) || (0, _entitlementPass.isEntitlementPass)(b)) {
|
|
464
|
+
return (0, _entitlementPass.isEntitlementPass)(a) && (0, _entitlementPass.isEntitlementPass)(b) ? (0, _entitlementPass.compareEntitlementPassPriority)(a, b) : (0, _entitlementPass.isEntitlementPass)(a) ? -1 : 1;
|
|
465
|
+
}
|
|
387
466
|
|
|
388
467
|
// 1. 先按优先级排序
|
|
389
468
|
const priorityA = getPriority(a);
|
|
@@ -540,6 +619,78 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
540
619
|
usedDiscountCardLimitCounts.set(discount.id, currentCount + (discount.metadata?.num || 1));
|
|
541
620
|
}
|
|
542
621
|
});
|
|
622
|
+
const entitlementUsageCounts = new Map();
|
|
623
|
+
const markDiscountUsed = discount => {
|
|
624
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
625
|
+
entitlementUsageCounts.set(discount.id, (entitlementUsageCounts.get(discount.id) || 0) + 1);
|
|
626
|
+
usedProductIdCounts.set(discount.product_id, (usedProductIdCounts.get(discount.product_id) || 0) + 1);
|
|
627
|
+
} else {
|
|
628
|
+
usedDiscounts.set(discount.id, true);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// 沿用商品券逐份拆分,同一多次卡可重复分配,数量受本单剩余额度限制。
|
|
633
|
+
const getFullCoveragePasses = (discounts, quantity) => {
|
|
634
|
+
const allocated = [];
|
|
635
|
+
const productCounts = new Map(usedProductIdCounts);
|
|
636
|
+
for (const discount of discounts) {
|
|
637
|
+
if (!(0, _entitlementPass.isFullCoveragePass)(discount) || allocated.length >= quantity) continue;
|
|
638
|
+
let count = (0, _entitlementPass.isEntitlementPass)(discount) ? (0, _entitlementPass.getRemainingEntitlementUses)(discount, entitlementUsageCounts.get(discount.id) || 0, quantity - allocated.length) : usedDiscounts.get(discount.id) ? 0 : 1;
|
|
639
|
+
const limit = Number(discount.config?.maxUsagePerOrder || 0);
|
|
640
|
+
if (limit > 0) {
|
|
641
|
+
count = Math.min(count, Math.max(0, Math.floor(limit) - (productCounts.get(discount.product_id) || 0)));
|
|
642
|
+
}
|
|
643
|
+
for (let i = 0; i < count; i++) allocated.push(discount);
|
|
644
|
+
productCounts.set(discount.product_id, (productCounts.get(discount.product_id) || 0) + count);
|
|
645
|
+
}
|
|
646
|
+
return allocated;
|
|
647
|
+
};
|
|
648
|
+
addModeDiscount.filter(_entitlementPass.isEntitlementPass).forEach(discount => {
|
|
649
|
+
discount.usageLimitFailKey = (0, _availability.getDiscountUsageLimitFailKey)(discount);
|
|
650
|
+
discount.reasonCode = (0, _availability.getDiscountUsageBlockReason)(discount);
|
|
651
|
+
const quota = (0, _entitlementPass.resolveEntitlementQuota)(discount);
|
|
652
|
+
if (discount.reasonCode || !quota) {
|
|
653
|
+
discount.config = {
|
|
654
|
+
isAvailable: false
|
|
655
|
+
};
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const entitlementEvaluator = evaluator || (this.defaultWalletPassEvaluator ?? (this.defaultWalletPassEvaluator = new _evaluator.WalletPassEvaluator()));
|
|
659
|
+
const result = entitlementEvaluator.checkVoucherAvailability({
|
|
660
|
+
// 兼容尚未传入订单总额的预约组件,保留多次卡原有的金额计算。
|
|
661
|
+
orderTotalAmount: orderTotalAmount || productList.reduce((sum, item) => {
|
|
662
|
+
const product = this.hooks.getProduct(item);
|
|
663
|
+
return sum.plus(new _decimal.default(product.origin_total || 0).times(product.quantity || product.num || 1));
|
|
664
|
+
}, new _decimal.default(0)).toNumber(),
|
|
665
|
+
// 策略按统一商品结构读主价与数量,套餐次数还需乘父商品份数。
|
|
666
|
+
products: sortedFlattenedList.map(item => ({
|
|
667
|
+
product_id: item.id,
|
|
668
|
+
price: item.price || 0,
|
|
669
|
+
main_product_selling_price: item.price || 0,
|
|
670
|
+
quantity: (item.quantity || item.num || 1) * (item.type === 'bundle' ? item.parentProduct?.quantity || item.parentProduct?.num || 1 : 1),
|
|
671
|
+
product_options: item.type === 'main' ? item.product?.options : undefined
|
|
672
|
+
})),
|
|
673
|
+
vouchers: [{
|
|
674
|
+
...discount,
|
|
675
|
+
amount: Number(discount.par_value || 0),
|
|
676
|
+
balance: quota.type === 'unlimited' ? Number.MAX_SAFE_INTEGER : quota.balance,
|
|
677
|
+
type: 'entitlement_pass',
|
|
678
|
+
unified_available_status: discount.unified_available_status ?? 1,
|
|
679
|
+
available_product_type: discount.limited_relation_product_data?.type,
|
|
680
|
+
available_product_ids: discount.limited_relation_product_data?.product_ids
|
|
681
|
+
}]
|
|
682
|
+
});
|
|
683
|
+
discount.config = {
|
|
684
|
+
...result.config,
|
|
685
|
+
isAvailable: result.isAvailable
|
|
686
|
+
};
|
|
687
|
+
if (!result.isAvailable) {
|
|
688
|
+
discount.reason = result.reason;
|
|
689
|
+
discount.reasonCode = result.reasonCode || 'not_meet_the_required_conditions';
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
const entitlementTimeReasons = new Map();
|
|
693
|
+
const entitlementHolderMismatch = new Set();
|
|
543
694
|
|
|
544
695
|
// 记录每个优惠券适用的商品ID
|
|
545
696
|
const discountApplicability = new Map();
|
|
@@ -582,7 +733,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
582
733
|
}
|
|
583
734
|
|
|
584
735
|
// 编辑的商品使用了优惠券不可用
|
|
585
|
-
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));
|
|
736
|
+
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));
|
|
586
737
|
if (!isAvailableProduct) {
|
|
587
738
|
return false;
|
|
588
739
|
}
|
|
@@ -599,7 +750,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
599
750
|
const limitedData = discount.limited_relation_product_data;
|
|
600
751
|
|
|
601
752
|
// 时间限制检查
|
|
602
|
-
const timeLimit = !!(0, _utils.filterDiscountListByBookingTime)([discount], (
|
|
753
|
+
const timeLimit = !!(0, _utils.filterDiscountListByBookingTime)([discount], (0, _dayjs.default)(product?.startDate || undefined).format('YYYY-MM-DD HH:mm:ss')).length;
|
|
603
754
|
if (!timeLimit) {
|
|
604
755
|
return false;
|
|
605
756
|
}
|
|
@@ -760,16 +911,24 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
760
911
|
}
|
|
761
912
|
addModeDiscount.forEach(discount => {
|
|
762
913
|
const limitedData = discount?.limited_relation_product_data;
|
|
914
|
+
if (!limitedData || (0, _entitlementPass.isEntitlementPass)(discount) && discount.config?.isAvailable === false) return;
|
|
763
915
|
// 拿到discount配置的holder信息 product信息 product.holder 加在 isLimitedProduct
|
|
764
916
|
const _tempVar = flatItem?.type === 'bundle' ? flatItem?.parentProduct : flatItem?.product;
|
|
765
917
|
const isHolderMatch = this.checkHolderMatch(discount, {
|
|
766
918
|
isNeedHolder: resolveIsFormSubject(_tempVar) && !_tempVar?.isNormalProduct,
|
|
767
919
|
holder_id: _tempVar?.holder_id || product.holder_id
|
|
768
|
-
}, holders);
|
|
920
|
+
}, flatItem.originProduct, holders);
|
|
769
921
|
let timeLimit = true;
|
|
770
|
-
|
|
922
|
+
const useTime = (0, _dayjs.default)(product?.startDate || undefined).format('YYYY-MM-DD HH:mm:ss');
|
|
923
|
+
timeLimit = !!(0, _utils.filterDiscountListByBookingTime)([discount], useTime).length;
|
|
924
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
925
|
+
const reasons = entitlementTimeReasons.get(discount.id) || [];
|
|
926
|
+
reasons.push((0, _utils.getDiscountTimeUnavailableReason)(discount, useTime));
|
|
927
|
+
entitlementTimeReasons.set(discount.id, reasons);
|
|
928
|
+
if (!isHolderMatch) entitlementHolderMismatch.add(discount.id);
|
|
929
|
+
}
|
|
771
930
|
// 是符合折扣的商品
|
|
772
|
-
const isLimitedProduct = (limitedData.type === 'product_all' && limitedData
|
|
931
|
+
const isLimitedProduct = (limitedData.type === 'product_all' && isProductIncludedByLimitedData(limitedData, product.id) || limitedData.product_ids && limitedData.product_ids.includes(product.id)) && isHolderMatch;
|
|
773
932
|
|
|
774
933
|
// 编辑的商品 使用了优惠券不可用
|
|
775
934
|
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));
|
|
@@ -778,14 +937,14 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
778
937
|
const isBundleAvailable = this.checkPackageSubItemUsageRules(discount, flatItem);
|
|
779
938
|
|
|
780
939
|
// 判断优惠券是否适用于该商品
|
|
781
|
-
if (isAvailableProduct && isLimitedProduct && timeLimit && isBundleAvailable && discount.config?.isAvailable) {
|
|
940
|
+
if (isAvailableProduct && !(0, _entitlementState.isPersistedEntitlementLine)(originProduct, flatItem.type === 'main' ? flatItem.product : flatItem.parentProduct) && (!(0, _entitlementPass.isEntitlementPass)(discount) || (flatItem.type === 'main' ? flatItem.product : flatItem.parentProduct)?.vouchersApplicable !== false) && isLimitedProduct && timeLimit && isBundleAvailable && discount.config?.isAvailable) {
|
|
782
941
|
// 记录此优惠券适用的商品
|
|
783
942
|
discountApplicability.get(discount.id)?.push(product.id);
|
|
784
943
|
|
|
785
944
|
// 记录可抵扣的商品详情
|
|
786
945
|
const applicableProducts = discountApplicableProducts.get(discount.id) || [];
|
|
787
946
|
const discountType = discount.tag || discount.type;
|
|
788
|
-
const isGoodPass =
|
|
947
|
+
const isGoodPass = (0, _entitlementPass.isFullCoveragePass)(discount);
|
|
789
948
|
|
|
790
949
|
// 使用数量
|
|
791
950
|
const num = isGoodPass || flatItem?.type === 'main' ? 1 : product.num;
|
|
@@ -801,7 +960,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
801
960
|
title: discount.format_title,
|
|
802
961
|
original_amount: product.price || product.origin_total,
|
|
803
962
|
pre_value: discount.par_value,
|
|
804
|
-
product_id: originProduct.id || originProduct.product_id,
|
|
963
|
+
product_id: (0, _entitlementPass.isEntitlementPass)(discount) ? product.id : originProduct.id || originProduct.product_id,
|
|
805
964
|
discount_product_id: discount.product_id
|
|
806
965
|
},
|
|
807
966
|
metadata: {
|
|
@@ -910,7 +1069,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
910
1069
|
});
|
|
911
1070
|
|
|
912
1071
|
// 已持久化且已有钱包券/折扣卡的商品行跳过,保留后端历史折扣结果。
|
|
913
|
-
if (isPersistedProduct && hasExistingWalletDiscount) {
|
|
1072
|
+
if ((0, _entitlementState.isPersistedEntitlementLine)(originProduct, flatItem.type === 'main' ? flatItem.product : flatItem.parentProduct) || isPersistedProduct && hasExistingWalletDiscount) {
|
|
914
1073
|
if (flatItem.type === 'main') {
|
|
915
1074
|
processedProductsMap.set(product._id, [originProduct]);
|
|
916
1075
|
} else {
|
|
@@ -924,6 +1083,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
924
1083
|
|
|
925
1084
|
// 找到适用于此商品的所有优惠券,仅考虑isSelected不为false的优惠券
|
|
926
1085
|
const applicableDiscounts = sortedDiscountList.filter(discount => {
|
|
1086
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount) && (discount.config?.isAvailable !== true || (flatItem.type === 'main' ? flatItem.product : flatItem.parentProduct)?.vouchersApplicable === false || (0, _entitlementPass.getRemainingEntitlementUses)(discount, entitlementUsageCounts.get(discount.id) || 0, 1) <= 0)) return false;
|
|
927
1087
|
// 🔥 检查策略可用性(针对 good_pass 和折扣卡)
|
|
928
1088
|
const discountType = discount.tag || discount.type;
|
|
929
1089
|
const currentOriginUid = getOriginProductUid(originProduct);
|
|
@@ -942,16 +1102,16 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
942
1102
|
|
|
943
1103
|
// 如果商品价格为 0,其实不需要使用任何优惠券,直接 return true
|
|
944
1104
|
// 商品券时主商品价格为0不可用
|
|
945
|
-
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.length && (
|
|
1105
|
+
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.length && (0, _entitlementPass.isFullCoveragePass)(discount)) return false;
|
|
946
1106
|
|
|
947
1107
|
// 折扣卡商品价格为0时不可用
|
|
948
|
-
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.find(n => n.discount?.resource_id === discount.id) && (
|
|
1108
|
+
if ((Number(product.price) <= 0 || !product.price) && !product.discount_list?.find(n => n.discount?.resource_id === discount.id) && !(0, _entitlementPass.isFullCoveragePass)(discount)) return false;
|
|
949
1109
|
// 如果优惠券已被使用,则跳过
|
|
950
1110
|
const targetUsedDiscounts = usedDiscounts.get(discount.id);
|
|
951
1111
|
if (targetUsedDiscounts && (discount.tag || discount.type) === 'good_pass') return false;
|
|
952
1112
|
|
|
953
1113
|
// 🔥 检查 maxUsagePerOrder 限制:同一 product_id 的商品券使用数量不能超过配置值
|
|
954
|
-
if ((
|
|
1114
|
+
if ((0, _entitlementPass.isFullCoveragePass)(discount)) {
|
|
955
1115
|
const maxUsagePerOrder = discount.config?.maxUsagePerOrder;
|
|
956
1116
|
if (maxUsagePerOrder && maxUsagePerOrder > 0) {
|
|
957
1117
|
const currentUsedCount = usedProductIdCounts.get(discount.product_id) || 0;
|
|
@@ -973,17 +1133,18 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
973
1133
|
}
|
|
974
1134
|
}
|
|
975
1135
|
const limitedData = discount.limited_relation_product_data;
|
|
1136
|
+
if (!limitedData) return false;
|
|
976
1137
|
|
|
977
1138
|
// 拿到discount配置的holder信息 product信息 product.holder 不可用return false
|
|
978
1139
|
const _tempVar = flatItem?.type === 'bundle' ? flatItem?.parentProduct : flatItem?.product;
|
|
979
1140
|
const isHolderMatch = this.checkHolderMatch(discount, {
|
|
980
1141
|
isNeedHolder: resolveIsFormSubject(_tempVar) && !_tempVar?.isNormalProduct,
|
|
981
1142
|
holder_id: _tempVar?.holder_id || product.holder_id
|
|
982
|
-
}, holders);
|
|
1143
|
+
}, flatItem.originProduct, holders);
|
|
983
1144
|
// 如果 holder 不匹配,则不适用
|
|
984
1145
|
if (!isHolderMatch) return false;
|
|
985
1146
|
let timeLimit = true;
|
|
986
|
-
timeLimit = !!(0, _utils.filterDiscountListByBookingTime)([discount], (
|
|
1147
|
+
timeLimit = !!(0, _utils.filterDiscountListByBookingTime)([discount], (0, _dayjs.default)(product.startDate || undefined).format('YYYY-MM-DD HH:mm:ss')).length;
|
|
987
1148
|
if (!timeLimit) {
|
|
988
1149
|
return false;
|
|
989
1150
|
}
|
|
@@ -1012,8 +1173,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1012
1173
|
// 选择第一个适用的优惠券(已经按过期时间排序)
|
|
1013
1174
|
// 如果有 isScan 且 isSelected 的折扣卡,则优先选中,否则再选择第一个
|
|
1014
1175
|
// 用于处理:先手动扫描了一张折扣卡,扫描完以后带入客户信息,但是之前那张折扣卡不是最优惠的情况
|
|
1015
|
-
const selectedDiscountCard = applicableDiscounts.find(n => n.isScan && n.isSelected && (
|
|
1016
|
-
const
|
|
1176
|
+
const selectedDiscountCard = applicableDiscounts.find(n => n.isScan && n.isSelected && !(0, _entitlementPass.isFullCoveragePass)(n));
|
|
1177
|
+
const explicitlySelected = options?.isSelected === true ? applicableDiscounts.find(item => (0, _entitlementPass.isEntitlementPass)(item) && String(item.id) === String(options.discountId)) : undefined;
|
|
1178
|
+
const selectedDiscount = explicitlySelected || selectedDiscountCard || applicableDiscounts[0];
|
|
1017
1179
|
|
|
1018
1180
|
// 如果是手动折扣,则不适用优惠券
|
|
1019
1181
|
let isManualDiscount = false;
|
|
@@ -1124,7 +1286,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1124
1286
|
|
|
1125
1287
|
// 是否需要拆分(商品券需要拆分,折扣卡在 applicableProductLimit > 0 时也需要拆分)
|
|
1126
1288
|
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
1127
|
-
const isGoodPass =
|
|
1289
|
+
const isGoodPass = (0, _entitlementPass.isFullCoveragePass)(selectedDiscount);
|
|
1128
1290
|
const isDiscountCard = ['discount_card', 'product_discount_card'].includes(discountType);
|
|
1129
1291
|
const applicableProductLimit = selectedDiscount.config?.applicableProductLimit || 0;
|
|
1130
1292
|
|
|
@@ -1134,10 +1296,13 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1134
1296
|
// 需要拆分出来的数量
|
|
1135
1297
|
const totalQuantity = product.quantity || product.num || 1;
|
|
1136
1298
|
const availableGoodPassCount = applicableDiscounts.filter(item => (item.tag || item.type) === 'good_pass').length;
|
|
1299
|
+
const allocatedPasses = isGoodPass && applicableDiscounts.some(_entitlementPass.isEntitlementPass) ? getFullCoveragePasses([selectedDiscount, ...applicableDiscounts.filter(item => item !== selectedDiscount)], totalQuantity) : undefined;
|
|
1137
1300
|
|
|
1138
1301
|
// 🔥 计算最大使用次数限制
|
|
1139
1302
|
let maxUsageLimit;
|
|
1140
|
-
if (
|
|
1303
|
+
if (allocatedPasses) {
|
|
1304
|
+
maxUsageLimit = allocatedPasses.length;
|
|
1305
|
+
} else if (isGoodPass) {
|
|
1141
1306
|
// 商品券:使用 maxUsagePerOrder 限制
|
|
1142
1307
|
maxUsageLimit = availableGoodPassCount;
|
|
1143
1308
|
if (selectedDiscount.config && selectedDiscount.config.maxUsagePerOrder) {
|
|
@@ -1173,9 +1338,10 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1173
1338
|
for (let i = 0; i < splitCount; i++) {
|
|
1174
1339
|
// 商品券:每个拆分商品使用不同的商品券(从 applicableDiscounts 按索引取)
|
|
1175
1340
|
// 折扣卡:所有拆分商品使用同一张折扣卡(selectedDiscountCard 或 applicableDiscounts[0])
|
|
1176
|
-
const currentSelectedDiscount = isGoodPass ? selectedDiscountCard || applicableDiscounts[i] : selectedDiscountCard || applicableDiscounts[0];
|
|
1341
|
+
const currentSelectedDiscount = allocatedPasses?.[i] || (isGoodPass ? selectedDiscountCard || applicableDiscounts[i] : selectedDiscountCard || applicableDiscounts[0]);
|
|
1342
|
+
const selectedDiscount = currentSelectedDiscount;
|
|
1177
1343
|
// 标记优惠券为已使用
|
|
1178
|
-
|
|
1344
|
+
markDiscountUsed(currentSelectedDiscount);
|
|
1179
1345
|
// 🔥 更新 product_id 使用计数
|
|
1180
1346
|
if ((currentSelectedDiscount.tag || currentSelectedDiscount.type) === 'good_pass') {
|
|
1181
1347
|
const currentCount = usedProductIdCounts.get(currentSelectedDiscount.product_id) || 0;
|
|
@@ -1247,7 +1413,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1247
1413
|
const mainProductDiscountAmount = amount;
|
|
1248
1414
|
const lineDiscountAmount = new _decimal.default(mainProductDiscountAmount).plus(optionDiscountAmount).toNumber();
|
|
1249
1415
|
const discountType = selectedDiscount.tag || selectedDiscount.type;
|
|
1250
|
-
const currentIsGoodPass =
|
|
1416
|
+
const currentIsGoodPass = (0, _entitlementPass.isFullCoveragePass)(selectedDiscount);
|
|
1251
1417
|
const discountDetail = {
|
|
1252
1418
|
amount: lineDiscountAmount,
|
|
1253
1419
|
type: selectedDiscount.tag === 'product_discount_card' ? 'discount_card' : discountType,
|
|
@@ -1258,7 +1424,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1258
1424
|
resource_id: selectedDiscount.id,
|
|
1259
1425
|
title: selectedDiscount.format_title,
|
|
1260
1426
|
original_amount: product.price,
|
|
1261
|
-
product_id: originProduct.id || originProduct.product_id,
|
|
1427
|
+
product_id: (0, _entitlementPass.isEntitlementPass)(selectedDiscount) ? product.id : originProduct.id || originProduct.product_id,
|
|
1262
1428
|
percent: reapplyDiscountPercent ?? selectedDiscount.par_value,
|
|
1263
1429
|
discount_product_id: selectedDiscount.product_id
|
|
1264
1430
|
},
|
|
@@ -1326,8 +1492,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1326
1492
|
for (let i = 0; i < discountNum; i++) {
|
|
1327
1493
|
// 商品券:每个拆分商品使用不同的商品券
|
|
1328
1494
|
// 折扣卡:所有拆分商品使用同一张折扣卡
|
|
1329
|
-
const currentBundleDiscount = isGoodPass ? selectedDiscountCard || applicableDiscounts[i] : selectedDiscountCard || applicableDiscounts[0];
|
|
1330
|
-
|
|
1495
|
+
const currentBundleDiscount = allocatedPasses?.[i] || (isGoodPass ? selectedDiscountCard || applicableDiscounts[i] : selectedDiscountCard || applicableDiscounts[0]);
|
|
1496
|
+
markDiscountUsed(currentBundleDiscount);
|
|
1331
1497
|
// 🔥 更新 product_id 使用计数
|
|
1332
1498
|
if ((currentBundleDiscount.tag || currentBundleDiscount.type) === 'good_pass') {
|
|
1333
1499
|
const currentCount = usedProductIdCounts.get(currentBundleDiscount.product_id) || 0;
|
|
@@ -1349,7 +1515,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1349
1515
|
debugger;
|
|
1350
1516
|
|
|
1351
1517
|
// 确定折扣类型
|
|
1352
|
-
const bundleDiscountType = currentBundleDiscount.tag === 'product_discount_card' ? 'discount_card' : currentBundleDiscount.tag;
|
|
1518
|
+
const bundleDiscountType = currentBundleDiscount.tag === 'product_discount_card' ? 'discount_card' : currentBundleDiscount.tag || currentBundleDiscount.type;
|
|
1353
1519
|
const discountDetail = {
|
|
1354
1520
|
amount: bundleDiscountAmount,
|
|
1355
1521
|
type: bundleDiscountType,
|
|
@@ -1361,12 +1527,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1361
1527
|
title: currentBundleDiscount.format_title,
|
|
1362
1528
|
original_amount: product.origin_total,
|
|
1363
1529
|
product_id: product.id || product.product_id,
|
|
1364
|
-
discount_product_id:
|
|
1530
|
+
discount_product_id: currentBundleDiscount.product_id
|
|
1365
1531
|
},
|
|
1366
1532
|
metadata: {
|
|
1367
1533
|
// 🔥 使用拆分后的唯一 _id
|
|
1368
1534
|
custom_product_bundle_map_id: uniqueId,
|
|
1369
|
-
discount_rule_uncheck_flag:
|
|
1535
|
+
discount_rule_uncheck_flag: currentBundleDiscount?.discount_rule_uncheck_flag,
|
|
1370
1536
|
num: 1
|
|
1371
1537
|
},
|
|
1372
1538
|
_num: 1,
|
|
@@ -1399,6 +1565,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1399
1565
|
// 重新查找适用于剩余商品的可用折扣(已使用的会被自动过滤掉)
|
|
1400
1566
|
const nextApplicableDiscounts = sortedDiscountList.filter(nextDiscount => {
|
|
1401
1567
|
const nextDiscountType = nextDiscount.tag || nextDiscount.type;
|
|
1568
|
+
if ((0, _entitlementPass.isEntitlementPass)(nextDiscount)) {
|
|
1569
|
+
if (nextDiscount.config?.isAvailable !== true || flatItem.parentProduct?.vouchersApplicable === false || (0, _entitlementPass.getRemainingEntitlementUses)(nextDiscount, entitlementUsageCounts.get(nextDiscount.id) || 0, 1) <= 0 || !this.checkHolderMatch(nextDiscount, flatItem.parentProduct, originProduct, holders) || !(0, _utils.filterDiscountListByBookingTime)([nextDiscount], (0, _dayjs.default)(product.startDate || undefined).format('YYYY-MM-DD HH:mm:ss')).length) return false;
|
|
1570
|
+
}
|
|
1402
1571
|
// 检查策略可用性
|
|
1403
1572
|
if (['good_pass', 'discount_card', 'product_discount_card'].includes(nextDiscountType)) {
|
|
1404
1573
|
if (nextDiscount.config === undefined || !nextDiscount?.config?.isAvailable) {
|
|
@@ -1406,13 +1575,13 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1406
1575
|
}
|
|
1407
1576
|
}
|
|
1408
1577
|
// 折扣卡时总价为0时不可用
|
|
1409
|
-
if ((Number(product.total) <= 0 || !product.total) && !product.discount_list?.find(n => n.discount?.resource_id === nextDiscount.id) &&
|
|
1578
|
+
if ((Number(product.total) <= 0 || !product.total) && !product.discount_list?.find(n => n.discount?.resource_id === nextDiscount.id) && !(0, _entitlementPass.isFullCoveragePass)(nextDiscount)) return false;
|
|
1410
1579
|
|
|
1411
1580
|
// 商品券已被使用则跳过
|
|
1412
1581
|
if (usedDiscounts.get(nextDiscount.id) && nextDiscountType === 'good_pass') return false;
|
|
1413
1582
|
|
|
1414
1583
|
// 检查 maxUsagePerOrder 限制
|
|
1415
|
-
if (
|
|
1584
|
+
if ((0, _entitlementPass.isFullCoveragePass)(nextDiscount)) {
|
|
1416
1585
|
const maxUsagePerOrder = nextDiscount.config?.maxUsagePerOrder;
|
|
1417
1586
|
if (maxUsagePerOrder && maxUsagePerOrder > 0) {
|
|
1418
1587
|
const currentUsedCount = usedProductIdCounts.get(nextDiscount.product_id) || 0;
|
|
@@ -1433,8 +1602,9 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1433
1602
|
}
|
|
1434
1603
|
}
|
|
1435
1604
|
const limitedData = nextDiscount.limited_relation_product_data;
|
|
1605
|
+
if (!limitedData) return false;
|
|
1436
1606
|
if (limitedData.type === 'product_all') {
|
|
1437
|
-
if (limitedData
|
|
1607
|
+
if (!isProductIncludedByLimitedData(limitedData, product.id)) {
|
|
1438
1608
|
return false;
|
|
1439
1609
|
}
|
|
1440
1610
|
if (!this.checkPackageSubItemUsageRules(nextDiscount, flatItem)) {
|
|
@@ -1464,12 +1634,12 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1464
1634
|
}
|
|
1465
1635
|
|
|
1466
1636
|
// 选择最优折扣
|
|
1467
|
-
const nextSelectedDiscountCard = nextApplicableDiscounts.find(n => n.isScan && n.isSelected && (
|
|
1637
|
+
const nextSelectedDiscountCard = nextApplicableDiscounts.find(n => n.isScan && n.isSelected && !(0, _entitlementPass.isFullCoveragePass)(n));
|
|
1468
1638
|
const nextSelectedDiscount = nextSelectedDiscountCard || nextApplicableDiscounts[0];
|
|
1469
1639
|
|
|
1470
1640
|
// 判断新折扣是否需要拆分
|
|
1471
1641
|
const nextDiscType = nextSelectedDiscount.tag || nextSelectedDiscount.type;
|
|
1472
|
-
const nextIsGoodPass =
|
|
1642
|
+
const nextIsGoodPass = (0, _entitlementPass.isFullCoveragePass)(nextSelectedDiscount);
|
|
1473
1643
|
const nextIsDiscountCard = ['discount_card', 'product_discount_card'].includes(nextDiscType);
|
|
1474
1644
|
const nextApplicableProductLimit = nextSelectedDiscount.config?.applicableProductLimit || 0;
|
|
1475
1645
|
const nextIsNeedSplit = nextIsGoodPass || nextIsDiscountCard && nextApplicableProductLimit > 0;
|
|
@@ -1490,7 +1660,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1490
1660
|
const nextTargetTotal = (0, _utils.getDiscountAmount)(nextBundleDiscount, nextOriginTotal, nextOriginTotal);
|
|
1491
1661
|
const nextDiscountAmount = new _decimal.default(nextOriginTotal).minus(nextTargetTotal).toNumber();
|
|
1492
1662
|
const nextUniqueId = `${flatItem._id}_split_${continueSplitIndex}`;
|
|
1493
|
-
const nextBundleDiscType = nextBundleDiscount.tag === 'product_discount_card' ? 'discount_card' : nextBundleDiscount.tag;
|
|
1663
|
+
const nextBundleDiscType = nextBundleDiscount.tag === 'product_discount_card' ? 'discount_card' : nextBundleDiscount.tag || nextBundleDiscount.type;
|
|
1494
1664
|
const nextDiscountDetail = {
|
|
1495
1665
|
amount: nextDiscountAmount * remainingNum,
|
|
1496
1666
|
type: nextBundleDiscType,
|
|
@@ -1529,8 +1699,11 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1529
1699
|
}
|
|
1530
1700
|
|
|
1531
1701
|
// 🔥 需要拆分:计算最大使用次数
|
|
1702
|
+
const nextAllocatedPasses = nextIsGoodPass && nextApplicableDiscounts.some(_entitlementPass.isEntitlementPass) ? getFullCoveragePasses([nextSelectedDiscount, ...nextApplicableDiscounts.filter(item => item !== nextSelectedDiscount)], remainingNum) : undefined;
|
|
1532
1703
|
let nextMaxUsageLimit;
|
|
1533
|
-
if (
|
|
1704
|
+
if (nextAllocatedPasses) {
|
|
1705
|
+
nextMaxUsageLimit = nextAllocatedPasses.length;
|
|
1706
|
+
} else if (nextIsGoodPass) {
|
|
1534
1707
|
const nextGoodPassCount = nextApplicableDiscounts.filter(item => (item.tag || item.type) === 'good_pass').length;
|
|
1535
1708
|
nextMaxUsageLimit = nextGoodPassCount;
|
|
1536
1709
|
if (nextSelectedDiscount.config?.maxUsagePerOrder) {
|
|
@@ -1559,8 +1732,8 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1559
1732
|
|
|
1560
1733
|
// 生成有折扣的商品
|
|
1561
1734
|
for (let j = 0; j < nextSplitCount; j++) {
|
|
1562
|
-
const nextBundleDiscount = nextIsGoodPass ? nextSelectedDiscountCard || nextApplicableDiscounts[j] : nextSelectedDiscountCard || nextApplicableDiscounts[0];
|
|
1563
|
-
|
|
1735
|
+
const nextBundleDiscount = nextAllocatedPasses?.[j] || (nextIsGoodPass ? nextSelectedDiscountCard || nextApplicableDiscounts[j] : nextSelectedDiscountCard || nextApplicableDiscounts[0]);
|
|
1736
|
+
markDiscountUsed(nextBundleDiscount);
|
|
1564
1737
|
if ((nextBundleDiscount.tag || nextBundleDiscount.type) === 'good_pass') {
|
|
1565
1738
|
const curCount = usedProductIdCounts.get(nextBundleDiscount.product_id) || 0;
|
|
1566
1739
|
usedProductIdCounts.set(nextBundleDiscount.product_id, curCount + 1);
|
|
@@ -1573,7 +1746,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1573
1746
|
const nextUniqueId = `${flatItem._id}_split_${continueSplitIndex}`;
|
|
1574
1747
|
const nextDiscountedPrice = (0, _utils.getDiscountAmount)(nextBundleDiscount, product.origin_total, product.origin_total);
|
|
1575
1748
|
const nextBundleDiscAmount = new _decimal.default(product.origin_total || 0).minus(nextDiscountedPrice).toNumber();
|
|
1576
|
-
const nextBundleDiscTypeStr = nextBundleDiscount.tag === 'product_discount_card' ? 'discount_card' : nextBundleDiscount.tag;
|
|
1749
|
+
const nextBundleDiscTypeStr = nextBundleDiscount.tag === 'product_discount_card' ? 'discount_card' : nextBundleDiscount.tag || nextBundleDiscount.type;
|
|
1577
1750
|
const nextDiscountDetail = {
|
|
1578
1751
|
amount: nextBundleDiscAmount,
|
|
1579
1752
|
type: nextBundleDiscTypeStr,
|
|
@@ -1704,8 +1877,37 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1704
1877
|
originalProductList.forEach(originProduct => {
|
|
1705
1878
|
const product = this.hooks.getProduct(originProduct);
|
|
1706
1879
|
|
|
1880
|
+
// 已提交的扣次不参与运行时重组,避免价格或使用明细被二次改写。
|
|
1881
|
+
if ((0, _entitlementState.isPersistedEntitlementLine)(originProduct, product)) {
|
|
1882
|
+
result.push(originProduct);
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1707
1886
|
// 获取主商品处理结果
|
|
1708
1887
|
const mainProductArr = processedProductsMap.get(product._id);
|
|
1888
|
+
if ((0, _entitlementLines.isEntitlementRuntimeLine)(originProduct, product) && !product.isManualDiscount && mainProductArr?.length) {
|
|
1889
|
+
const bundle = (product.bundle || []).flatMap((item, index) => {
|
|
1890
|
+
const processed = processedFlatItemsMap.get(`${product._id}_bundle_${index}`);
|
|
1891
|
+
if (!processed?.length) return [item];
|
|
1892
|
+
return processed.map(child => ({
|
|
1893
|
+
...item,
|
|
1894
|
+
_id: child._id,
|
|
1895
|
+
price: child.price,
|
|
1896
|
+
bundle_selling_price: child.price,
|
|
1897
|
+
num: child.num,
|
|
1898
|
+
discount_list: (child.discount_list || []).map(detail => ({
|
|
1899
|
+
...detail,
|
|
1900
|
+
metadata: {
|
|
1901
|
+
...detail.metadata,
|
|
1902
|
+
num: child.num,
|
|
1903
|
+
custom_product_bundle_map_id: child._id
|
|
1904
|
+
}
|
|
1905
|
+
}))
|
|
1906
|
+
}));
|
|
1907
|
+
});
|
|
1908
|
+
result.push(...mainProductArr.map(item => (0, _entitlementLines.rebuildEntitlementLine)(item, bundle, this.hooks)));
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1709
1911
|
if (!mainProductArr || mainProductArr.length === 0) {
|
|
1710
1912
|
// 如果没有处理结果,返回默认商品(还原 option 价格)
|
|
1711
1913
|
const getDefaultProduct = () => {
|
|
@@ -1768,7 +1970,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
1768
1970
|
// 处理过的bundle item
|
|
1769
1971
|
bundleProcessingInfo.set(bundleIndex, processedBundleItems);
|
|
1770
1972
|
// 🔥 检查是否应用了商品券(good_pass)
|
|
1771
|
-
const hasGoodPass = processedBundleItems.some(item => item.discount_list?.some(discount =>
|
|
1973
|
+
const hasGoodPass = processedBundleItems.some(item => item.discount_list?.some(discount => (0, _entitlementPass.isFullCoveragePass)(discount)));
|
|
1772
1974
|
if (hasGoodPass) {
|
|
1773
1975
|
hasGoodPassApplied = true;
|
|
1774
1976
|
}
|
|
@@ -2084,6 +2286,27 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
2084
2286
|
|
|
2085
2287
|
// 获取此优惠券实际应用的商品详情
|
|
2086
2288
|
const appliedProductDetails = appliedDiscountProducts.get(discount.id) || [];
|
|
2289
|
+
if ((0, _entitlementPass.isEntitlementPass)(discount)) {
|
|
2290
|
+
const committed = discount.committedAppliedProductDetails || [];
|
|
2291
|
+
const details = [...committed, ...appliedProductDetails];
|
|
2292
|
+
const isAvailable = applicableProducts.length > 0;
|
|
2293
|
+
const timeReasons = entitlementTimeReasons.get(discount.id) || [];
|
|
2294
|
+
const timeReason = timeReasons.length ? timeReasons.every(Boolean) ? timeReasons[0] : undefined : (0, _utils.getDiscountTimeUnavailableReason)(discount, (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss'));
|
|
2295
|
+
const reasonCode = isAvailable ? undefined : discount.reasonCode || timeReason || (entitlementHolderMismatch.has(discount.id) ? 'holder_not_matched' : 'product_not_applicable');
|
|
2296
|
+
return {
|
|
2297
|
+
...discount,
|
|
2298
|
+
isAvailable: isAvailable || committed.length > 0,
|
|
2299
|
+
isSelected: details.length > 0,
|
|
2300
|
+
isUsed: details.length > 0,
|
|
2301
|
+
reasonCode,
|
|
2302
|
+
reason: reasonCode === discount.reasonCode ? discount.reason : undefined,
|
|
2303
|
+
isManualSelect: options?.scan ? false : discount.isManualSelect,
|
|
2304
|
+
applicableProductIds: applicableProducts,
|
|
2305
|
+
applicableProductDetails,
|
|
2306
|
+
appliedProductDetails: details,
|
|
2307
|
+
savedAmount: details.reduce((sum, detail) => new _decimal.default(sum).plus(new _decimal.default(detail.discount?.fixed_amount || 0).mul(detail._num || 1)).toNumber(), 0)
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2087
2310
|
|
|
2088
2311
|
// 根据是否有适用商品决定isAvailable
|
|
2089
2312
|
const isAvailable = applicableProducts.length > 0;
|
|
@@ -2141,7 +2364,7 @@ class RulesModule extends _BaseModule.BaseModule {
|
|
|
2141
2364
|
};
|
|
2142
2365
|
});
|
|
2143
2366
|
return {
|
|
2144
|
-
productList: processedProductList,
|
|
2367
|
+
productList: this.entitlementPassEnabled ? (0, _entitlementLines.finalizeEntitlementLines)(processedProductList, this.hooks) : processedProductList,
|
|
2145
2368
|
discountList: [...editModeDiscount, ...updatedDiscountList]
|
|
2146
2369
|
};
|
|
2147
2370
|
}
|