@pisell/pisellos 2.2.77 → 2.2.78

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.
@@ -19,7 +19,8 @@ var defaultStrategyMetadataCustom = {
19
19
  maxUsagePerOrder: 0,
20
20
  allowCrossProduct: true,
21
21
  applicableProductLimit: 0,
22
- deductTaxAndFee: true
22
+ deductTaxAndFee: true,
23
+ maxPassesPerItem: 0
23
24
  };
24
25
 
25
26
  /**
@@ -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
  /**
@@ -127,6 +129,8 @@ export interface EvaluatorInput {
127
129
  allowCrossProduct: boolean;
128
130
  /** 可用商品数量上限 (仅多商品) 默认为0,表示不限制商品数量。 */
129
131
  applicableProductLimit: number;
132
+ /** 单商品可用卡券上限(同一 Wallet Pass 商品生成的卡券对同一商品行最多抵扣次数)。默认为 0,表示不限制。 */
133
+ maxPassesPerItem: number;
130
134
  }>[];
131
135
  }
132
136
  /**
@@ -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$deductTaxAndF3 = config.deductTaxAndFee,
558
- deductTaxAndFee = _config$deductTaxAndF3 === void 0 ? true : _config$deductTaxAndF3;
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$deductTaxAndF4 = config.deductTaxAndFee,
696
- deductTaxAndFee = _config$deductTaxAndF4 === void 0 ? true : _config$deductTaxAndF4;
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';
@@ -41,7 +41,8 @@ var defaultStrategyMetadataCustom = {
41
41
  maxUsagePerOrder: 0,
42
42
  allowCrossProduct: true,
43
43
  applicableProductLimit: 0,
44
- deductTaxAndFee: true
44
+ deductTaxAndFee: true,
45
+ maxPassesPerItem: 0
45
46
  };
46
47
  var WalletPassEvaluator = class {
47
48
  constructor() {
@@ -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
  /**
@@ -127,6 +129,8 @@ export interface EvaluatorInput {
127
129
  allowCrossProduct: boolean;
128
130
  /** 可用商品数量上限 (仅多商品) 默认为0,表示不限制商品数量。 */
129
131
  applicableProductLimit: number;
132
+ /** 单商品可用卡券上限(同一 Wallet Pass 商品生成的卡券对同一商品行最多抵扣次数)。默认为 0,表示不限制。 */
133
+ maxPassesPerItem: number;
130
134
  }>[];
131
135
  }
132
136
  /**
@@ -86,9 +86,27 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
86
86
  console.log(products, "products123");
87
87
  const productsCopy = expandProductsWithBundleItems(products, true);
88
88
  let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
89
- const calculateAvailableMaxAmount = (voucher, productsData) => {
89
+ const getItemPassUsage = (usageMap, walletPassProductId, orderItemProductId) => {
90
+ var _a;
91
+ return ((_a = usageMap.get(walletPassProductId)) == null ? void 0 : _a.get(orderItemProductId)) || 0;
92
+ };
93
+ const incrementItemPassUsage = (usageMap, walletPassProductId, orderItemProductId) => {
94
+ if (!usageMap.has(walletPassProductId)) {
95
+ usageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
96
+ }
97
+ const innerMap = usageMap.get(walletPassProductId);
98
+ innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
99
+ };
100
+ const filterByMaxPassesPerItem = (products2, usageMap, walletPassProductId, maxPassesPerItem) => {
101
+ if (maxPassesPerItem <= 0)
102
+ return products2;
103
+ return products2.filter(
104
+ (p) => getItemPassUsage(usageMap, walletPassProductId, p.product_id) < maxPassesPerItem
105
+ );
106
+ };
107
+ const calculateAvailableMaxAmount = (voucher, productsData, itemPassUsage) => {
90
108
  const { config } = voucher;
91
- const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true } = config ?? {};
109
+ const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true, maxPassesPerItem = 0 } = config ?? {};
92
110
  const recommendedAmount = getRecommendedAmount(voucher);
93
111
  const baseAmount = import_decimal.default.min(
94
112
  new import_decimal.default(recommendedAmount),
@@ -96,7 +114,10 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
96
114
  );
97
115
  const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
98
116
  const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
99
- const applicableProducts = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0));
117
+ let applicableProducts = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0));
118
+ if (itemPassUsage) {
119
+ applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsage, voucher.product_id, maxPassesPerItem);
120
+ }
100
121
  if (applicableProducts.length === 0) {
101
122
  return new import_decimal.default(0);
102
123
  }
@@ -136,7 +157,7 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
136
157
  }
137
158
  return import_decimal.default.min(baseAmount, finalApplicableAmount, remainingOrderAmount);
138
159
  };
139
- const isVoucherAvailable = (voucher, productsData, usedVoucherCounts2) => {
160
+ const isVoucherAvailable = (voucher, productsData, usedVoucherCounts2, itemPassUsage) => {
140
161
  const { config, id, product_id } = voucher;
141
162
  const recommendedAmount = getRecommendedAmount(voucher);
142
163
  if (recommendedAmount <= 0) {
@@ -155,6 +176,15 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
155
176
  return { isAvailable: false, reasonCode: "usage_limit_reached" };
156
177
  }
157
178
  }
179
+ const maxPassesPerItem = (config == null ? void 0 : config.maxPassesPerItem) || 0;
180
+ if (maxPassesPerItem > 0 && itemPassUsage) {
181
+ const deductTaxAndFee = (config == null ? void 0 : config.deductTaxAndFee) ?? true;
182
+ const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
183
+ const availableAfterPassLimit = getApplicableProducts(voucher, productsData).filter((p) => p[amountField].greaterThan(0)).filter((p) => getItemPassUsage(itemPassUsage, product_id, p.product_id) < maxPassesPerItem);
184
+ if (availableAfterPassLimit.length === 0) {
185
+ return { isAvailable: false, reasonCode: "max_passes_per_item_reached" };
186
+ }
187
+ }
158
188
  return { isAvailable: true };
159
189
  };
160
190
  const usedVoucherCountsForAll = /* @__PURE__ */ new Map();
@@ -181,23 +211,26 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
181
211
  const usedVoucherCounts = /* @__PURE__ */ new Map();
182
212
  const productsForRecommendation = expandProductsWithBundleItems(products, true);
183
213
  remainingOrderAmount = new import_decimal.default(orderTotalAmount);
214
+ const itemPassUsageMap = /* @__PURE__ */ new Map();
184
215
  const applyVoucher = (voucher) => {
185
216
  const availabilityCheck = isVoucherAvailable(
186
217
  voucher,
187
218
  productsForRecommendation,
188
- usedVoucherCounts
219
+ usedVoucherCounts,
220
+ itemPassUsageMap
189
221
  );
190
222
  if (!availabilityCheck.isAvailable) {
191
223
  return false;
192
224
  }
193
225
  const { config, id, product_id } = voucher;
194
- const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true } = config ?? {};
226
+ const { maxDeductionAmount = 0, allowCrossProduct = true, applicableProductLimit = 0, deductTaxAndFee = true, maxPassesPerItem = 0 } = config ?? {};
195
227
  const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
196
228
  const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
197
229
  let applicableProducts = getApplicableProducts(
198
230
  voucher,
199
231
  productsForRecommendation
200
232
  ).filter((p) => p[amountField].greaterThan(0));
233
+ applicableProducts = filterByMaxPassesPerItem(applicableProducts, itemPassUsageMap, product_id, maxPassesPerItem);
201
234
  if (applicableProducts.length === 0)
202
235
  return false;
203
236
  const usageAmount = typeof voucher.edit_current_amount === "number" ? voucher.edit_current_amount : getRecommendedAmount(voucher);
@@ -307,6 +340,9 @@ function processVouchers(applicableVouchers, orderTotalAmount, products) {
307
340
  if (totalDeducted.greaterThan(0)) {
308
341
  remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
309
342
  usedVoucherCounts.set(product_id, (usedVoucherCounts.get(product_id) || 0) + 1);
343
+ deductionDetails.forEach((detail) => {
344
+ incrementItemPassUsage(itemPassUsageMap, product_id, detail.product_id);
345
+ });
310
346
  recommendedVouchers.push({
311
347
  ...voucher,
312
348
  actualDeduction: totalDeducted.toNumber(),
@@ -348,15 +384,35 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
348
384
  const productsForCalc = expandProductsWithBundleItems(products, true);
349
385
  let remainingOrderAmount = new import_decimal.default(orderTotalAmount);
350
386
  const selectedWithDetails = [];
387
+ const itemPassUsageMap = /* @__PURE__ */ new Map();
388
+ const getItemPassUsage = (walletPassProductId, orderItemProductId) => {
389
+ var _a;
390
+ return ((_a = itemPassUsageMap.get(walletPassProductId)) == null ? void 0 : _a.get(orderItemProductId)) || 0;
391
+ };
392
+ const incrementItemPassUsage = (walletPassProductId, orderItemProductId) => {
393
+ if (!itemPassUsageMap.has(walletPassProductId)) {
394
+ itemPassUsageMap.set(walletPassProductId, /* @__PURE__ */ new Map());
395
+ }
396
+ const innerMap = itemPassUsageMap.get(walletPassProductId);
397
+ innerMap.set(orderItemProductId, (innerMap.get(orderItemProductId) || 0) + 1);
398
+ };
399
+ const filterByMaxPassesPerItem = (products2, walletPassProductId, maxPassesPerItem) => {
400
+ if (maxPassesPerItem <= 0)
401
+ return products2;
402
+ return products2.filter(
403
+ (p) => getItemPassUsage(walletPassProductId, p.product_id) < maxPassesPerItem
404
+ );
405
+ };
351
406
  selectedVouchers.forEach((selectedVoucher) => {
352
407
  const { config, id } = selectedVoucher;
353
- const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true } = config;
408
+ const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
354
409
  const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
355
410
  const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
356
411
  let applicableProducts = getApplicableProducts(
357
412
  selectedVoucher,
358
413
  productsForCalc
359
414
  ).filter((p) => p[amountField].greaterThan(0));
415
+ applicableProducts = filterByMaxPassesPerItem(applicableProducts, selectedVoucher.product_id, maxPassesPerItem);
360
416
  if (applicableProducts.length === 0) {
361
417
  selectedWithDetails.push({
362
418
  ...selectedVoucher,
@@ -431,6 +487,9 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
431
487
  }
432
488
  const totalDeducted = maxDeduction.minus(deductionLeft);
433
489
  remainingOrderAmount = remainingOrderAmount.minus(totalDeducted);
490
+ deductionDetails.forEach((detail) => {
491
+ incrementItemPassUsage(selectedVoucher.product_id, detail.product_id);
492
+ });
434
493
  selectedWithDetails.push({
435
494
  ...selectedVoucher,
436
495
  actualDeduction: totalDeducted.toNumber(),
@@ -454,7 +513,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
454
513
  return selectedDetail || voucher;
455
514
  }
456
515
  const { config, id, product_id } = voucher;
457
- const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true } = config;
516
+ const { maxDeductionAmount, allowCrossProduct, applicableProductLimit, deductTaxAndFee = true, maxPassesPerItem = 0 } = config;
458
517
  const unitPriceField = deductTaxAndFee ? "unitPriceWithTax" : "unitPricePure";
459
518
  const amountField = deductTaxAndFee ? "remainingAmountWithTax" : "remainingAmountPure";
460
519
  const recommendedAmount = getRecommendedAmount(voucher);
@@ -480,6 +539,7 @@ function recalculateVouchers(allVouchers, selectedVouchers, orderTotalAmount, pr
480
539
  voucher,
481
540
  productsForCalc
482
541
  ).filter((p) => p[amountField].greaterThan(0));
542
+ applicableProducts = filterByMaxPassesPerItem(applicableProducts, product_id, maxPassesPerItem);
483
543
  if (applicableProducts.length === 0) {
484
544
  isAvailable = false;
485
545
  reasonCode = "not_meet_the_required_conditions";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.2.77",
4
+ "version": "2.2.78",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",