@pisell/pisellos 1.0.135 → 1.0.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/model/strategy/adapter/walletPass/evaluator.js +2 -1
- package/dist/model/strategy/adapter/walletPass/locales.js +3 -0
- package/dist/model/strategy/adapter/walletPass/type.d.ts +4 -0
- package/dist/model/strategy/adapter/walletPass/utils.js +108 -9
- package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -1
- package/lib/model/strategy/adapter/walletPass/locales.js +3 -0
- package/lib/model/strategy/adapter/walletPass/type.d.ts +4 -0
- package/lib/model/strategy/adapter/walletPass/utils.js +68 -8
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ export var locales = {
|
|
|
3
3
|
'not_meet_the_required_conditions': '未达到使用条件',
|
|
4
4
|
'exceeds_the_maximum_deduction_limit': '超出最大抵扣金额限制',
|
|
5
5
|
'usage_limit_reached': '已达到使用次数上限',
|
|
6
|
+
'max_passes_per_item_reached': '该商品已达到卡券使用上限',
|
|
6
7
|
'not_available_for_this_channel': '当前渠道不可使用',
|
|
7
8
|
'not_valid_for_this_order_type': '当前订单类型不适用'
|
|
8
9
|
},
|
|
@@ -10,6 +11,7 @@ export var locales = {
|
|
|
10
11
|
'not_meet_the_required_conditions': 'Not meet the required conditions.',
|
|
11
12
|
'exceeds_the_maximum_deduction_limit': 'Exceeds the maximum deduction limit.',
|
|
12
13
|
'usage_limit_reached': 'Usage limit reached.',
|
|
14
|
+
'max_passes_per_item_reached': 'Max passes per item reached.',
|
|
13
15
|
'not_available_for_this_channel': 'Not available for this channel.',
|
|
14
16
|
'not_valid_for_this_order_type': 'Not valid for this order type.'
|
|
15
17
|
},
|
|
@@ -17,6 +19,7 @@ export var locales = {
|
|
|
17
19
|
'not_meet_the_required_conditions': '未達使用條件',
|
|
18
20
|
'exceeds_the_maximum_deduction_limit': '超出最大抵扣金額限制',
|
|
19
21
|
'usage_limit_reached': '已達使用次數上限',
|
|
22
|
+
'max_passes_per_item_reached': '該商品已達卡券使用上限',
|
|
20
23
|
'not_available_for_this_channel': '當前渠道不可使用',
|
|
21
24
|
'not_valid_for_this_order_type': '當前訂單類型不適用'
|
|
22
25
|
}
|
|
@@ -41,6 +41,8 @@ export interface Voucher {
|
|
|
41
41
|
allowCrossProduct: boolean;
|
|
42
42
|
/** 可用商品数量上限 (仅多商品) 默认为0,表示不限制商品数量。 */
|
|
43
43
|
applicableProductLimit: number;
|
|
44
|
+
/** 单商品可用卡券上限(同一 Wallet Pass 商品生成的卡券对同一商品行最多抵扣次数)。默认为 0,表示不限制。 */
|
|
45
|
+
maxPassesPerItem: number;
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
@@ -130,6 +132,8 @@ export interface EvaluatorInput {
|
|
|
130
132
|
allowCrossProduct: boolean;
|
|
131
133
|
/** 可用商品数量上限 (仅多商品) 默认为0,表示不限制商品数量。 */
|
|
132
134
|
applicableProductLimit: number;
|
|
135
|
+
/** 单商品可用卡券上限(同一 Wallet Pass 商品生成的卡券对同一商品行最多抵扣次数)。默认为 0,表示不限制。 */
|
|
136
|
+
maxPassesPerItem: number;
|
|
133
137
|
}>[];
|
|
134
138
|
}
|
|
135
139
|
/**
|
|
@@ -118,8 +118,33 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
118
118
|
var productsCopy = expandProductsWithBundleItems(products, true);
|
|
119
119
|
var remainingOrderAmount = new Decimal(orderTotalAmount); // 订单剩余应付金额
|
|
120
120
|
|
|
121
|
+
// ========== 辅助工具:maxPassesPerItem(单商品可用卡券上限)追踪 ==========
|
|
122
|
+
// 获取指定 Wallet Pass 商品对指定订单商品行的已用卡券次数
|
|
123
|
+
var getItemPassUsage = function getItemPassUsage(usageMap, walletPassProductId, orderItemProductId) {
|
|
124
|
+
var _usageMap$get;
|
|
125
|
+
return ((_usageMap$get = usageMap.get(walletPassProductId)) === null || _usageMap$get === void 0 ? void 0 : _usageMap$get.get(orderItemProductId)) || 0;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// 更新指定 Wallet Pass 商品对指定订单商品行的已用卡券次数 +1
|
|
129
|
+
var incrementItemPassUsage = function incrementItemPassUsage(usageMap, walletPassProductId, orderItemProductId) {
|
|
130
|
+
if (!usageMap.has(walletPassProductId)) {
|
|
131
|
+
usageMap.set(walletPassProductId, new Map());
|
|
132
|
+
}
|
|
133
|
+
var innerMap = usageMap.get(walletPassProductId);
|
|
134
|
+
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// 按 maxPassesPerItem 过滤商品:排除已达到单商品可用卡券上限的商品
|
|
138
|
+
var filterByMaxPassesPerItem = function filterByMaxPassesPerItem(products, usageMap, walletPassProductId, maxPassesPerItem) {
|
|
139
|
+
if (maxPassesPerItem <= 0) return products; // 0 = 不限制
|
|
140
|
+
return products.filter(function (p) {
|
|
141
|
+
return getItemPassUsage(usageMap, walletPassProductId, p.product_id) < maxPassesPerItem;
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
// ================================================================
|
|
145
|
+
|
|
121
146
|
// 辅助函数:计算单张券的 _available_max_amount
|
|
122
|
-
var calculateAvailableMaxAmount = function calculateAvailableMaxAmount(voucher, productsData) {
|
|
147
|
+
var calculateAvailableMaxAmount = function calculateAvailableMaxAmount(voucher, productsData, itemPassUsage) {
|
|
123
148
|
var config = voucher.config;
|
|
124
149
|
var _ref2 = config !== null && config !== void 0 ? config : {},
|
|
125
150
|
_ref2$maxDeductionAmo = _ref2.maxDeductionAmount,
|
|
@@ -129,7 +154,9 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
129
154
|
_ref2$applicableProdu = _ref2.applicableProductLimit,
|
|
130
155
|
applicableProductLimit = _ref2$applicableProdu === void 0 ? 0 : _ref2$applicableProdu,
|
|
131
156
|
_ref2$deductTaxAndFee = _ref2.deductTaxAndFee,
|
|
132
|
-
deductTaxAndFee = _ref2$deductTaxAndFee === void 0 ? true : _ref2$deductTaxAndFee
|
|
157
|
+
deductTaxAndFee = _ref2$deductTaxAndFee === void 0 ? true : _ref2$deductTaxAndFee,
|
|
158
|
+
_ref2$maxPassesPerIte = _ref2.maxPassesPerItem,
|
|
159
|
+
maxPassesPerItem = _ref2$maxPassesPerIte === void 0 ? 0 : _ref2$maxPassesPerIte;
|
|
133
160
|
|
|
134
161
|
// 根据 deductTaxAndFee 配置获取推荐金额
|
|
135
162
|
var recommendedAmount = getRecommendedAmount(voucher);
|
|
@@ -145,6 +172,11 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
145
172
|
var applicableProducts = getApplicableProducts(voucher, productsData).filter(function (p) {
|
|
146
173
|
return p[amountField].greaterThan(0);
|
|
147
174
|
});
|
|
175
|
+
|
|
176
|
+
// 按 maxPassesPerItem 过滤:排除已达到单商品可用卡券上限的商品
|
|
177
|
+
if (itemPassUsage) {
|
|
178
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsage, voucher.product_id, maxPassesPerItem);
|
|
179
|
+
}
|
|
148
180
|
if (applicableProducts.length === 0) {
|
|
149
181
|
return new Decimal(0);
|
|
150
182
|
}
|
|
@@ -202,7 +234,7 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
202
234
|
};
|
|
203
235
|
|
|
204
236
|
// 辅助函数:判断券是否可用,返回可用性和原因代码
|
|
205
|
-
var isVoucherAvailable = function isVoucherAvailable(voucher, productsData, usedVoucherCounts) {
|
|
237
|
+
var isVoucherAvailable = function isVoucherAvailable(voucher, productsData, usedVoucherCounts, itemPassUsage) {
|
|
206
238
|
var config = voucher.config,
|
|
207
239
|
id = voucher.id,
|
|
208
240
|
product_id = voucher.product_id;
|
|
@@ -245,6 +277,25 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
245
277
|
};
|
|
246
278
|
}
|
|
247
279
|
}
|
|
280
|
+
|
|
281
|
+
// 检查 maxPassesPerItem:如果所有适用商品都已达到单商品可用卡券上限,则不可用
|
|
282
|
+
var maxPassesPerItem = (config === null || config === void 0 ? void 0 : config.maxPassesPerItem) || 0;
|
|
283
|
+
if (maxPassesPerItem > 0 && itemPassUsage) {
|
|
284
|
+
var _config$deductTaxAndF3;
|
|
285
|
+
var deductTaxAndFee = (_config$deductTaxAndF3 = config === null || config === void 0 ? void 0 : config.deductTaxAndFee) !== null && _config$deductTaxAndF3 !== void 0 ? _config$deductTaxAndF3 : true;
|
|
286
|
+
var amountField = deductTaxAndFee ? 'remainingAmountWithTax' : 'remainingAmountPure';
|
|
287
|
+
var availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter(function (p) {
|
|
288
|
+
return p[amountField].greaterThan(0);
|
|
289
|
+
}).filter(function (p) {
|
|
290
|
+
return getItemPassUsage(itemPassUsage, product_id, p.product_id) < maxPassesPerItem;
|
|
291
|
+
});
|
|
292
|
+
if (availableAfterPassLimit.length === 0) {
|
|
293
|
+
return {
|
|
294
|
+
isAvailable: false,
|
|
295
|
+
reasonCode: 'max_passes_per_item_reached'
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
248
299
|
return {
|
|
249
300
|
isAvailable: true
|
|
250
301
|
};
|
|
@@ -272,6 +323,8 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
272
323
|
// 重置商品余额追踪(同时维护含税和不含税两个金额池)
|
|
273
324
|
var productsForRecommendation = expandProductsWithBundleItems(products, true);
|
|
274
325
|
remainingOrderAmount = new Decimal(orderTotalAmount);
|
|
326
|
+
// 追踪推荐阶段每个 Wallet Pass 商品对每个订单商品行的已使用卡券次数
|
|
327
|
+
var itemPassUsageMap = new Map();
|
|
275
328
|
|
|
276
329
|
/**
|
|
277
330
|
// 分类券:未跨商品券 vs 跨商品券
|
|
@@ -302,7 +355,7 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
302
355
|
// 处理单张券的抵扣
|
|
303
356
|
var applyVoucher = function applyVoucher(voucher) {
|
|
304
357
|
// 检查是否可用
|
|
305
|
-
var availabilityCheck = isVoucherAvailable(voucher, productsForRecommendation, usedVoucherCounts);
|
|
358
|
+
var availabilityCheck = isVoucherAvailable(voucher, productsForRecommendation, usedVoucherCounts, itemPassUsageMap);
|
|
306
359
|
if (!availabilityCheck.isAvailable) {
|
|
307
360
|
return false;
|
|
308
361
|
}
|
|
@@ -317,7 +370,9 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
317
370
|
_ref3$applicableProdu = _ref3.applicableProductLimit,
|
|
318
371
|
applicableProductLimit = _ref3$applicableProdu === void 0 ? 0 : _ref3$applicableProdu,
|
|
319
372
|
_ref3$deductTaxAndFee = _ref3.deductTaxAndFee,
|
|
320
|
-
deductTaxAndFee = _ref3$deductTaxAndFee === void 0 ? true : _ref3$deductTaxAndFee
|
|
373
|
+
deductTaxAndFee = _ref3$deductTaxAndFee === void 0 ? true : _ref3$deductTaxAndFee,
|
|
374
|
+
_ref3$maxPassesPerIte = _ref3.maxPassesPerItem,
|
|
375
|
+
maxPassesPerItem = _ref3$maxPassesPerIte === void 0 ? 0 : _ref3$maxPassesPerIte;
|
|
321
376
|
|
|
322
377
|
// 根据券的配置选择使用哪个单价字段和金额字段
|
|
323
378
|
var unitPriceField = deductTaxAndFee ? 'unitPriceWithTax' : 'unitPricePure';
|
|
@@ -327,6 +382,9 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
327
382
|
var applicableProducts = getApplicableProducts(voucher, productsForRecommendation).filter(function (p) {
|
|
328
383
|
return p[amountField].greaterThan(0);
|
|
329
384
|
});
|
|
385
|
+
|
|
386
|
+
// 按 maxPassesPerItem 过滤:排除已达到单商品可用卡券上限的商品
|
|
387
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsageMap, product_id, maxPassesPerItem);
|
|
330
388
|
if (applicableProducts.length === 0) return false;
|
|
331
389
|
|
|
332
390
|
// ========== 关键修改:在应用券之前,基于当前剩余金额计算 _available_max_amount ==========
|
|
@@ -466,6 +524,11 @@ export function processVouchers(applicableVouchers, orderTotalAmount, products)
|
|
|
466
524
|
// 更新券使用次数(按 product_id 统计)
|
|
467
525
|
usedVoucherCounts.set(product_id, (usedVoucherCounts.get(product_id) || 0) + 1);
|
|
468
526
|
|
|
527
|
+
// 更新 maxPassesPerItem 追踪:记录每个被抵扣的商品行
|
|
528
|
+
deductionDetails.forEach(function (detail) {
|
|
529
|
+
incrementItemPassUsage(itemPassUsageMap, product_id, detail.product_id);
|
|
530
|
+
});
|
|
531
|
+
|
|
469
532
|
// 添加到推荐列表(包含基于当前剩余金额计算的 available_max_amount)
|
|
470
533
|
recommendedVouchers.push(_objectSpread(_objectSpread({}, voucher), {}, {
|
|
471
534
|
actualDeduction: totalDeducted.toNumber(),
|
|
@@ -547,6 +610,27 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
|
|
|
547
610
|
var remainingOrderAmount = new Decimal(orderTotalAmount);
|
|
548
611
|
var selectedWithDetails = [];
|
|
549
612
|
|
|
613
|
+
// 追踪每个 Wallet Pass 商品对每个订单商品行的已使用卡券次数
|
|
614
|
+
// Map<walletPassProductId, Map<orderItemProductId, usedCount>>
|
|
615
|
+
var itemPassUsageMap = new Map();
|
|
616
|
+
var getItemPassUsage = function getItemPassUsage(walletPassProductId, orderItemProductId) {
|
|
617
|
+
var _itemPassUsageMap$get;
|
|
618
|
+
return ((_itemPassUsageMap$get = itemPassUsageMap.get(walletPassProductId)) === null || _itemPassUsageMap$get === void 0 ? void 0 : _itemPassUsageMap$get.get(orderItemProductId)) || 0;
|
|
619
|
+
};
|
|
620
|
+
var incrementItemPassUsage = function incrementItemPassUsage(walletPassProductId, orderItemProductId) {
|
|
621
|
+
if (!itemPassUsageMap.has(walletPassProductId)) {
|
|
622
|
+
itemPassUsageMap.set(walletPassProductId, new Map());
|
|
623
|
+
}
|
|
624
|
+
var innerMap = itemPassUsageMap.get(walletPassProductId);
|
|
625
|
+
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
|
|
626
|
+
};
|
|
627
|
+
var filterByMaxPassesPerItem = function filterByMaxPassesPerItem(products, walletPassProductId, maxPassesPerItem) {
|
|
628
|
+
if (maxPassesPerItem <= 0) return products; // 0 = 不限制
|
|
629
|
+
return products.filter(function (p) {
|
|
630
|
+
return getItemPassUsage(walletPassProductId, p.product_id) < maxPassesPerItem;
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
|
|
550
634
|
// 第一步:按顺序应用已选中的券,计算实际抵扣
|
|
551
635
|
selectedVouchers.forEach(function (selectedVoucher) {
|
|
552
636
|
var config = selectedVoucher.config,
|
|
@@ -554,8 +638,10 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
|
|
|
554
638
|
var maxDeductionAmount = config.maxDeductionAmount,
|
|
555
639
|
allowCrossProduct = config.allowCrossProduct,
|
|
556
640
|
applicableProductLimit = config.applicableProductLimit,
|
|
557
|
-
_config$
|
|
558
|
-
deductTaxAndFee = _config$
|
|
641
|
+
_config$deductTaxAndF4 = config.deductTaxAndFee,
|
|
642
|
+
deductTaxAndFee = _config$deductTaxAndF4 === void 0 ? true : _config$deductTaxAndF4,
|
|
643
|
+
_config$maxPassesPerI = config.maxPassesPerItem,
|
|
644
|
+
maxPassesPerItem = _config$maxPassesPerI === void 0 ? 0 : _config$maxPassesPerI;
|
|
559
645
|
|
|
560
646
|
// 根据券的配置选择使用哪个单价字段和金额字段
|
|
561
647
|
var unitPriceField = deductTaxAndFee ? 'unitPriceWithTax' : 'unitPricePure';
|
|
@@ -565,6 +651,9 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
|
|
|
565
651
|
var applicableProducts = getApplicableProducts(selectedVoucher, productsForCalc).filter(function (p) {
|
|
566
652
|
return p[amountField].greaterThan(0);
|
|
567
653
|
});
|
|
654
|
+
|
|
655
|
+
// 按 maxPassesPerItem 过滤:排除已达到单商品可用卡券上限的商品
|
|
656
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, selectedVoucher.product_id, maxPassesPerItem);
|
|
568
657
|
if (applicableProducts.length === 0) {
|
|
569
658
|
// 无适用商品,跳过
|
|
570
659
|
selectedWithDetails.push(_objectSpread(_objectSpread({}, selectedVoucher), {}, {
|
|
@@ -658,6 +747,11 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
|
|
|
658
747
|
|
|
659
748
|
// 更新订单剩余金额
|
|
660
749
|
remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
|
|
750
|
+
|
|
751
|
+
// 更新 maxPassesPerItem 追踪:记录每个被抵扣的商品行
|
|
752
|
+
deductionDetails.forEach(function (detail) {
|
|
753
|
+
incrementItemPassUsage(selectedVoucher.product_id, detail.product_id);
|
|
754
|
+
});
|
|
661
755
|
selectedWithDetails.push(_objectSpread(_objectSpread({}, selectedVoucher), {}, {
|
|
662
756
|
actualDeduction: totalDeducted.toNumber(),
|
|
663
757
|
// 转换为数字
|
|
@@ -692,8 +786,10 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
|
|
|
692
786
|
var maxDeductionAmount = config.maxDeductionAmount,
|
|
693
787
|
allowCrossProduct = config.allowCrossProduct,
|
|
694
788
|
applicableProductLimit = config.applicableProductLimit,
|
|
695
|
-
_config$
|
|
696
|
-
deductTaxAndFee = _config$
|
|
789
|
+
_config$deductTaxAndF5 = config.deductTaxAndFee,
|
|
790
|
+
deductTaxAndFee = _config$deductTaxAndF5 === void 0 ? true : _config$deductTaxAndF5,
|
|
791
|
+
_config$maxPassesPerI2 = config.maxPassesPerItem,
|
|
792
|
+
maxPassesPerItem = _config$maxPassesPerI2 === void 0 ? 0 : _config$maxPassesPerI2;
|
|
697
793
|
|
|
698
794
|
// 根据券的配置选择使用哪个单价字段和金额字段
|
|
699
795
|
var unitPriceField = deductTaxAndFee ? 'unitPriceWithTax' : 'unitPricePure';
|
|
@@ -726,6 +822,9 @@ export function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmo
|
|
|
726
822
|
var applicableProducts = getApplicableProducts(voucher, productsForCalc).filter(function (p) {
|
|
727
823
|
return p[amountField].greaterThan(0);
|
|
728
824
|
});
|
|
825
|
+
|
|
826
|
+
// 按 maxPassesPerItem 过滤:排除已达到单商品可用卡券上限的商品
|
|
827
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, product_id, maxPassesPerItem);
|
|
729
828
|
if (applicableProducts.length === 0) {
|
|
730
829
|
isAvailable = false;
|
|
731
830
|
reasonCode = 'not_meet_the_required_conditions';
|
|
@@ -27,6 +27,7 @@ var locales = {
|
|
|
27
27
|
"not_meet_the_required_conditions": "未达到使用条件",
|
|
28
28
|
"exceeds_the_maximum_deduction_limit": "超出最大抵扣金额限制",
|
|
29
29
|
"usage_limit_reached": "已达到使用次数上限",
|
|
30
|
+
"max_passes_per_item_reached": "该商品已达到卡券使用上限",
|
|
30
31
|
"not_available_for_this_channel": "当前渠道不可使用",
|
|
31
32
|
"not_valid_for_this_order_type": "当前订单类型不适用"
|
|
32
33
|
},
|
|
@@ -34,6 +35,7 @@ var locales = {
|
|
|
34
35
|
"not_meet_the_required_conditions": "Not meet the required conditions.",
|
|
35
36
|
"exceeds_the_maximum_deduction_limit": "Exceeds the maximum deduction limit.",
|
|
36
37
|
"usage_limit_reached": "Usage limit reached.",
|
|
38
|
+
"max_passes_per_item_reached": "Max passes per item reached.",
|
|
37
39
|
"not_available_for_this_channel": "Not available for this channel.",
|
|
38
40
|
"not_valid_for_this_order_type": "Not valid for this order type."
|
|
39
41
|
},
|
|
@@ -41,6 +43,7 @@ var locales = {
|
|
|
41
43
|
"not_meet_the_required_conditions": "未達使用條件",
|
|
42
44
|
"exceeds_the_maximum_deduction_limit": "超出最大抵扣金額限制",
|
|
43
45
|
"usage_limit_reached": "已達使用次數上限",
|
|
46
|
+
"max_passes_per_item_reached": "該商品已達卡券使用上限",
|
|
44
47
|
"not_available_for_this_channel": "當前渠道不可使用",
|
|
45
48
|
"not_valid_for_this_order_type": "當前訂單類型不適用"
|
|
46
49
|
}
|
|
@@ -41,6 +41,8 @@ export interface Voucher {
|
|
|
41
41
|
allowCrossProduct: boolean;
|
|
42
42
|
/** 可用商品数量上限 (仅多商品) 默认为0,表示不限制商品数量。 */
|
|
43
43
|
applicableProductLimit: number;
|
|
44
|
+
/** 单商品可用卡券上限(同一 Wallet Pass 商品生成的卡券对同一商品行最多抵扣次数)。默认为 0,表示不限制。 */
|
|
45
|
+
maxPassesPerItem: number;
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
@@ -130,6 +132,8 @@ export interface EvaluatorInput {
|
|
|
130
132
|
allowCrossProduct: boolean;
|
|
131
133
|
/** 可用商品数量上限 (仅多商品) 默认为0,表示不限制商品数量。 */
|
|
132
134
|
applicableProductLimit: number;
|
|
135
|
+
/** 单商品可用卡券上限(同一 Wallet Pass 商品生成的卡券对同一商品行最多抵扣次数)。默认为 0,表示不限制。 */
|
|
136
|
+
maxPassesPerItem: number;
|
|
133
137
|
}>[];
|
|
134
138
|
}
|
|
135
139
|
/**
|
|
@@ -90,9 +90,27 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
90
90
|
console.log(products, "products123");
|
|
91
91
|
const productsCopy = expandProductsWithBundleItems(products, true);
|
|
92
92
|
let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
|
|
93
|
-
const
|
|
93
|
+
const getItemPassUsage = (usageMap, walletPassProductId, orderItemProductId) => {
|
|
94
|
+
var _a;
|
|
95
|
+
return ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(orderItemProductId)) || 0;
|
|
96
|
+
};
|
|
97
|
+
const incrementItemPassUsage = (usageMap, walletPassProductId, orderItemProductId) => {
|
|
98
|
+
if (!usageMap.has(walletPassProductId)) {
|
|
99
|
+
usageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
|
|
100
|
+
}
|
|
101
|
+
const innerMap = usageMap.get(walletPassProductId);
|
|
102
|
+
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
|
|
103
|
+
};
|
|
104
|
+
const filterByMaxPassesPerItem = (products2, usageMap, walletPassProductId, maxPassesPerItem) => {
|
|
105
|
+
if (maxPassesPerItem <= 0)
|
|
106
|
+
return products2;
|
|
107
|
+
return products2.filter(
|
|
108
|
+
(p) => getItemPassUsage(usageMap, walletPassProductId, p.product_id) < maxPassesPerItem
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
const calculateAvailableMaxAmount = (voucher, productsData, itemPassUsage) => {
|
|
94
112
|
const { config } = voucher;
|
|
95
|
-
const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true } = config ?? {};
|
|
113
|
+
const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true, maxPassesPerItem = 0 } = config ?? {};
|
|
96
114
|
const recommendedAmount = getRecommendedAmount(voucher);
|
|
97
115
|
const baseAmount = import_decimal.default.min(
|
|
98
116
|
new import_decimal.default(recommendedAmount),
|
|
@@ -100,7 +118,10 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
100
118
|
);
|
|
101
119
|
const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
|
|
102
120
|
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
|
|
103
|
-
|
|
121
|
+
let applicableProducts = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0));
|
|
122
|
+
if (itemPassUsage) {
|
|
123
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsage, voucher.product_id, maxPassesPerItem);
|
|
124
|
+
}
|
|
104
125
|
if (applicableProducts.length === 0) {
|
|
105
126
|
return new import_decimal.default(0);
|
|
106
127
|
}
|
|
@@ -140,7 +161,7 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
140
161
|
}
|
|
141
162
|
return import_decimal.default.min(baseAmount, finalApplicableAmount, remainingOrderAmount);
|
|
142
163
|
};
|
|
143
|
-
const isVoucherAvailable = (voucher, productsData, usedVoucherCounts2) => {
|
|
164
|
+
const isVoucherAvailable = (voucher, productsData, usedVoucherCounts2, itemPassUsage) => {
|
|
144
165
|
const { config, id, product_id } = voucher;
|
|
145
166
|
const recommendedAmount = getRecommendedAmount(voucher);
|
|
146
167
|
if (recommendedAmount <= 0) {
|
|
@@ -159,6 +180,15 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
159
180
|
return { isAvailable: false, reasonCode: "usage_limit_reached" };
|
|
160
181
|
}
|
|
161
182
|
}
|
|
183
|
+
const maxPassesPerItem = (config == null ? void 0 : config.maxPassesPerItem) || 0;
|
|
184
|
+
if (maxPassesPerItem > 0 && itemPassUsage) {
|
|
185
|
+
const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
|
|
186
|
+
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
|
|
187
|
+
const availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0)).filter((p) => getItemPassUsage(itemPassUsage, product_id, p.product_id) < maxPassesPerItem);
|
|
188
|
+
if (availableAfterPassLimit.length === 0) {
|
|
189
|
+
return { isAvailable: false, reasonCode: "max_passes_per_item_reached" };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
162
192
|
return { isAvailable: true };
|
|
163
193
|
};
|
|
164
194
|
const usedVoucherCountsForAll = /* @__PURE__ */ new Map();
|
|
@@ -185,23 +215,26 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
185
215
|
const usedVoucherCounts = /* @__PURE__ */ new Map();
|
|
186
216
|
const productsForRecommendation = expandProductsWithBundleItems(products, true);
|
|
187
217
|
remainingOrderAmount = new import_decimal.default(orderTotalAmount);
|
|
218
|
+
const itemPassUsageMap = /* @__PURE__ */ new Map();
|
|
188
219
|
const applyVoucher = (voucher) => {
|
|
189
220
|
const availabilityCheck = isVoucherAvailable(
|
|
190
221
|
voucher,
|
|
191
222
|
productsForRecommendation,
|
|
192
|
-
usedVoucherCounts
|
|
223
|
+
usedVoucherCounts,
|
|
224
|
+
itemPassUsageMap
|
|
193
225
|
);
|
|
194
226
|
if (!availabilityCheck.isAvailable) {
|
|
195
227
|
return false;
|
|
196
228
|
}
|
|
197
229
|
const { config, id, product_id } = voucher;
|
|
198
|
-
const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true } = config ?? {};
|
|
230
|
+
const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true, maxPassesPerItem = 0 } = config ?? {};
|
|
199
231
|
const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
|
|
200
232
|
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
|
|
201
233
|
let applicableProducts = getApplicableProducts(
|
|
202
234
|
voucher,
|
|
203
235
|
productsForRecommendation
|
|
204
236
|
).filter((p) => p[amountField].greaterThan(0));
|
|
237
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsageMap, product_id, maxPassesPerItem);
|
|
205
238
|
if (applicableProducts.length === 0)
|
|
206
239
|
return false;
|
|
207
240
|
const usageAmount = typeof voucher.edit_current_amount === "number" ? voucher.edit_current_amount : getRecommendedAmount(voucher);
|
|
@@ -311,6 +344,9 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
|
|
|
311
344
|
if (totalDeducted.greaterThan(0)) {
|
|
312
345
|
remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
|
|
313
346
|
usedVoucherCounts.set(product_id, (usedVoucherCounts.get(product_id) || 0) + 1);
|
|
347
|
+
deductionDetails.forEach((detail) => {
|
|
348
|
+
incrementItemPassUsage(itemPassUsageMap, product_id, detail.product_id);
|
|
349
|
+
});
|
|
314
350
|
recommendedVouchers.push({
|
|
315
351
|
...voucher,
|
|
316
352
|
actualDeduction: totalDeducted.toNumber(),
|
|
@@ -352,15 +388,35 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
352
388
|
const productsForCalc = expandProductsWithBundleItems(products, true);
|
|
353
389
|
let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
|
|
354
390
|
const selectedWithDetails = [];
|
|
391
|
+
const itemPassUsageMap = /* @__PURE__ */ new Map();
|
|
392
|
+
const getItemPassUsage = (walletPassProductId, orderItemProductId) => {
|
|
393
|
+
var _a;
|
|
394
|
+
return ((_a = itemPassUsageMap.get(walletPassProductId)) == null ? void 0 : _a.get(orderItemProductId)) || 0;
|
|
395
|
+
};
|
|
396
|
+
const incrementItemPassUsage = (walletPassProductId, orderItemProductId) => {
|
|
397
|
+
if (!itemPassUsageMap.has(walletPassProductId)) {
|
|
398
|
+
itemPassUsageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
|
|
399
|
+
}
|
|
400
|
+
const innerMap = itemPassUsageMap.get(walletPassProductId);
|
|
401
|
+
innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
|
|
402
|
+
};
|
|
403
|
+
const filterByMaxPassesPerItem = (products2, walletPassProductId, maxPassesPerItem) => {
|
|
404
|
+
if (maxPassesPerItem <= 0)
|
|
405
|
+
return products2;
|
|
406
|
+
return products2.filter(
|
|
407
|
+
(p) => getItemPassUsage(walletPassProductId, p.product_id) < maxPassesPerItem
|
|
408
|
+
);
|
|
409
|
+
};
|
|
355
410
|
selectedVouchers.forEach((selectedVoucher) => {
|
|
356
411
|
const { config, id } = selectedVoucher;
|
|
357
|
-
const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true } = config;
|
|
412
|
+
const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
|
|
358
413
|
const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
|
|
359
414
|
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
|
|
360
415
|
let applicableProducts = getApplicableProducts(
|
|
361
416
|
selectedVoucher,
|
|
362
417
|
productsForCalc
|
|
363
418
|
).filter((p) => p[amountField].greaterThan(0));
|
|
419
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, selectedVoucher.product_id, maxPassesPerItem);
|
|
364
420
|
if (applicableProducts.length === 0) {
|
|
365
421
|
selectedWithDetails.push({
|
|
366
422
|
...selectedVoucher,
|
|
@@ -435,6 +491,9 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
435
491
|
}
|
|
436
492
|
const totalDeducted = maxDeduction.minus(deductionLeft);
|
|
437
493
|
remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
|
|
494
|
+
deductionDetails.forEach((detail) => {
|
|
495
|
+
incrementItemPassUsage(selectedVoucher.product_id, detail.product_id);
|
|
496
|
+
});
|
|
438
497
|
selectedWithDetails.push({
|
|
439
498
|
...selectedVoucher,
|
|
440
499
|
actualDeduction: totalDeducted.toNumber(),
|
|
@@ -458,7 +517,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
458
517
|
return selectedDetail || voucher;
|
|
459
518
|
}
|
|
460
519
|
const { config, id, product_id } = voucher;
|
|
461
|
-
const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true } = config;
|
|
520
|
+
const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
|
|
462
521
|
const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
|
|
463
522
|
const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
|
|
464
523
|
const recommendedAmount = getRecommendedAmount(voucher);
|
|
@@ -484,6 +543,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
|
|
|
484
543
|
voucher,
|
|
485
544
|
productsForCalc
|
|
486
545
|
).filter((p) => p[amountField].greaterThan(0));
|
|
546
|
+
applicableProducts = filterByMaxPassesPerItem(applicableProducts, product_id, maxPassesPerItem);
|
|
487
547
|
if (applicableProducts.length === 0) {
|
|
488
548
|
isAvailable = false;
|
|
489
549
|
reasonCode = "not_meet_the_required_conditions";
|