@pisell/pisellos 0.10.44 → 0.10.45

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.
Files changed (40) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  2. package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  3. package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
  4. package/dist/modules/Customer/index.js +2 -1
  5. package/dist/modules/Discount/availability.d.ts +7 -0
  6. package/dist/modules/Discount/availability.js +49 -0
  7. package/dist/modules/Discount/index.d.ts +1 -3
  8. package/dist/modules/Discount/index.js +12 -151
  9. package/dist/modules/Discount/types.d.ts +6 -0
  10. package/dist/modules/Order/index.js +155 -130
  11. package/dist/modules/Order/utils.d.ts +1 -1
  12. package/dist/modules/Order/utils.js +84 -59
  13. package/dist/modules/Product/index.d.ts +1 -1
  14. package/dist/modules/Rules/index.js +50 -16
  15. package/dist/solution/BaseSales/types.d.ts +2 -2
  16. package/dist/solution/BookingByStep/index.d.ts +1 -1
  17. package/dist/solution/ShopDiscount/index.d.ts +1 -0
  18. package/dist/solution/ShopDiscount/index.js +59 -22
  19. package/dist/solution/ShopDiscount/utils.d.ts +4 -2
  20. package/dist/solution/ShopDiscount/utils.js +40 -21
  21. package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  22. package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
  23. package/lib/modules/Customer/index.js +1 -0
  24. package/lib/modules/Discount/availability.d.ts +7 -0
  25. package/lib/modules/Discount/availability.js +43 -0
  26. package/lib/modules/Discount/index.d.ts +1 -3
  27. package/lib/modules/Discount/index.js +10 -131
  28. package/lib/modules/Discount/types.d.ts +6 -0
  29. package/lib/modules/Order/index.js +22 -8
  30. package/lib/modules/Order/utils.d.ts +1 -1
  31. package/lib/modules/Order/utils.js +20 -2
  32. package/lib/modules/Product/index.d.ts +1 -1
  33. package/lib/modules/Rules/index.js +42 -5
  34. package/lib/solution/BaseSales/types.d.ts +2 -2
  35. package/lib/solution/BookingByStep/index.d.ts +1 -1
  36. package/lib/solution/ShopDiscount/index.d.ts +1 -0
  37. package/lib/solution/ShopDiscount/index.js +29 -3
  38. package/lib/solution/ShopDiscount/utils.d.ts +4 -2
  39. package/lib/solution/ShopDiscount/utils.js +39 -23
  40. package/package.json +1 -1
@@ -30,6 +30,7 @@ import { RulesModule } from "../../modules/Rules";
30
30
  import { isEntitlementPass } from "../../modules/Discount/entitlementPass";
31
31
  import { filterDiscountListByBookingTime as _filterDiscountListByBookingTime, isAllNormalProduct } from "./utils";
32
32
  import { UnavailableReason } from "../../modules/Rules/types";
33
+ import { getDiscountLifecycleReason } from "../../modules/Discount/availability";
33
34
  import Decimal from 'decimal.js';
34
35
  import { isBoolean } from 'lodash-es';
35
36
  export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
@@ -441,7 +442,7 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
441
442
  key: "scanCode",
442
443
  value: function () {
443
444
  var _scanCode = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(code, customerId) {
444
- var _this$store$discount4, _this$store$bookingSu2, resultDiscountWithReason, resultDiscountList, unavailableReasonKey, rulesModule, withScanList, currentSelectedDiscountList, _ref2, newProductList, newDiscountList, isAvailable, unavailableReason, _this$options$otherPa6;
445
+ var _this$store$discount4, _this$store$bookingSu2, resultDiscountWithReason, resultDiscountList, unavailableReasonKey, rulesModule, withScanList, currentSelectedDiscountList, _ref2, newProductList, newDiscountList, evaluatedDiscountList, isAvailable, unavailableReason, isAccepted, _this$options$otherPa6, scannedIds, retainedScannedDiscounts, _this$store$discount5, retainedDiscounts;
445
446
  return _regeneratorRuntime().wrap(function _callee6$(_context6) {
446
447
  while (1) switch (_context6.prev = _context6.next) {
447
448
  case 0:
@@ -523,30 +524,65 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
523
524
  isAvailable: false,
524
525
  productList: this.store.productList || [],
525
526
  discountList: this.getDiscountList()
526
- }, newProductList = _ref2.productList, newDiscountList = _ref2.discountList, isAvailable = _ref2.isAvailable, unavailableReason = _ref2.unavailableReason;
527
+ }, newProductList = _ref2.productList, newDiscountList = _ref2.discountList, evaluatedDiscountList = _ref2.evaluatedDiscountList, isAvailable = _ref2.isAvailable, unavailableReason = _ref2.unavailableReason;
528
+ isAccepted = isAvailable;
527
529
  if (!isAvailable) {
528
- _context6.next = 25;
530
+ _context6.next = 28;
529
531
  break;
530
532
  }
531
533
  this.setDiscountList(newDiscountList || []);
532
534
  this.store.originalDiscountList = newDiscountList || [];
533
535
  this.setProductList(newProductList || []);
534
536
  if (!(this.isWalkIn() && resultDiscountList.length && ((_this$options$otherPa6 = this.options.otherParams) === null || _this$options$otherPa6 === void 0 ? void 0 : _this$options$otherPa6.platform) === 'shop' && !((resultDiscountList[0].customer_id || 1) === 1))) {
535
- _context6.next = 25;
537
+ _context6.next = 26;
536
538
  break;
537
539
  }
538
- _context6.next = 25;
540
+ _context6.next = 26;
539
541
  return this.getCustomerWallet(resultDiscountList[0].customer_id);
540
- case 25:
542
+ case 26:
543
+ _context6.next = 38;
544
+ break;
545
+ case 28:
546
+ scannedIds = new Set(withScanList.map(function (discount) {
547
+ return String(discount.id);
548
+ }));
549
+ retainedScannedDiscounts = (evaluatedDiscountList || withScanList).filter(function (discount) {
550
+ return scannedIds.has(String(discount.id)) && !getDiscountLifecycleReason(discount);
551
+ });
552
+ if (!retainedScannedDiscounts.length) {
553
+ _context6.next = 38;
554
+ break;
555
+ }
556
+ retainedDiscounts = new Map(this.getDiscountList().map(function (discount) {
557
+ return [String(discount.id), discount];
558
+ }));
559
+ retainedScannedDiscounts.forEach(function (discount) {
560
+ retainedDiscounts.set(String(discount.id), _objectSpread(_objectSpread({}, discount), {}, {
561
+ isScan: true,
562
+ isAvailable: false,
563
+ isSelected: false,
564
+ reasonCode: discount.reasonCode || 'product_not_applicable',
565
+ appliedProductDetails: [],
566
+ savedAmount: 0
567
+ }));
568
+ });
569
+ this.setDiscountList(_toConsumableArray(retainedDiscounts.values()));
570
+ this.store.originalDiscountList = this.getDiscountList();
571
+ _context6.next = 37;
572
+ return (_this$store$discount5 = this.store.discount) === null || _this$store$discount5 === void 0 ? void 0 : _this$store$discount5.setOriginalDiscountList(this.getDiscountList());
573
+ case 37:
574
+ isAccepted = true;
575
+ case 38:
541
576
  return _context6.abrupt("return", {
542
577
  type: "clientCalc",
543
578
  isAvailable: isAvailable || false,
579
+ isAccepted: Boolean(isAccepted),
544
580
  productList: newProductList || this.store.productList || [],
545
- discountList: newDiscountList || this.getDiscountList(),
581
+ discountList: this.getDiscountList(),
546
582
  unavailableReason: unavailableReason
547
583
  });
548
- case 28:
549
- _context6.prev = 28;
584
+ case 41:
585
+ _context6.prev = 41;
550
586
  _context6.t1 = _context6["catch"](0);
551
587
  console.error('[ShopDiscount] 扫码出错:', _context6.t1);
552
588
  return _context6.abrupt("return", {
@@ -556,11 +592,11 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
556
592
  discountList: this.getDiscountList(),
557
593
  unavailableReason: UnavailableReason.Unknown
558
594
  });
559
- case 32:
595
+ case 45:
560
596
  case "end":
561
597
  return _context6.stop();
562
598
  }
563
- }, _callee6, this, [[0, 28]]);
599
+ }, _callee6, this, [[0, 41]]);
564
600
  }));
565
601
  function scanCode(_x5, _x6) {
566
602
  return _scanCode.apply(this, arguments);
@@ -570,8 +606,8 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
570
606
  }, {
571
607
  key: "calcDiscountApplicableProductTotalPrice",
572
608
  value: function calcDiscountApplicableProductTotalPrice(discount) {
573
- var _this$store$discount5;
574
- return (_this$store$discount5 = this.store.discount) === null || _this$store$discount5 === void 0 ? void 0 : _this$store$discount5.calcDiscountApplicableProductTotalPrice(discount);
609
+ var _this$store$discount6;
610
+ return (_this$store$discount6 = this.store.discount) === null || _this$store$discount6 === void 0 ? void 0 : _this$store$discount6.calcDiscountApplicableProductTotalPrice(discount);
575
611
  }
576
612
 
577
613
  // =========== 私有辅助方法 ===========
@@ -616,7 +652,7 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
616
652
  key: "setDiscountList",
617
653
  value: function setDiscountList(discountList) {
618
654
  var _this3 = this,
619
- _this$store$discount6;
655
+ _this$store$discount7;
620
656
  var productList = this.store.productList || [];
621
657
  var editModeDiscountList = [];
622
658
  productList.forEach(function (item) {
@@ -699,7 +735,7 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
699
735
  item.isDisabledForProductUsed = false;
700
736
  }
701
737
  });
702
- (_this$store$discount6 = this.store.discount) === null || _this$store$discount6 === void 0 || _this$store$discount6.setDiscountList(newDiscountList);
738
+ (_this$store$discount7 = this.store.discount) === null || _this$store$discount7 === void 0 || _this$store$discount7.setDiscountList(newDiscountList);
703
739
  this.emitDiscountListChange(newDiscountList);
704
740
  return newDiscountList;
705
741
  }
@@ -708,8 +744,8 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
708
744
  }, {
709
745
  key: "getDiscountList",
710
746
  value: function getDiscountList() {
711
- var _this$store$discount7;
712
- return ((_this$store$discount7 = this.store.discount) === null || _this$store$discount7 === void 0 ? void 0 : _this$store$discount7.getDiscountList()) || [];
747
+ var _this$store$discount8;
748
+ return ((_this$store$discount8 = this.store.discount) === null || _this$store$discount8 === void 0 ? void 0 : _this$store$discount8.getDiscountList()) || [];
713
749
  }
714
750
 
715
751
  // 获取客户钱包信息
@@ -791,7 +827,7 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
791
827
  key: "loadPrepareConfig",
792
828
  value: function () {
793
829
  var _loadPrepareConfig = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee10(params) {
794
- var _this$getCustomer2, _this$store$discount8, _this$getDiscountList, _this$store$discount9, _this$store$productLi2, orderId, customerId, prepareConfigResult, goodPassList, scanDiscount, scanDiscountById, refreshedScanEntitlementIds, newGoodPassList, retainedScanDiscounts, newDiscountList, filteredDiscountList, _result3;
830
+ var _this$getCustomer2, _this$store$discount9, _this$getDiscountList, _this$store$discount10, _this$store$productLi2, orderId, customerId, prepareConfigResult, goodPassList, scanDiscount, scanDiscountById, refreshedScanEntitlementIds, newGoodPassList, retainedScanDiscounts, newDiscountList, filteredDiscountList, _result3;
795
831
  return _regeneratorRuntime().wrap(function _callee10$(_context10) {
796
832
  while (1) switch (_context10.prev = _context10.next) {
797
833
  case 0:
@@ -801,13 +837,14 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
801
837
  // return
802
838
  // }
803
839
  _context10.next = 5;
804
- return (_this$store$discount8 = this.store.discount) === null || _this$store$discount8 === void 0 ? void 0 : _this$store$discount8.loadPrepareConfig(_objectSpread({
840
+ return (_this$store$discount9 = this.store.discount) === null || _this$store$discount9 === void 0 ? void 0 : _this$store$discount9.loadPrepareConfig(_objectSpread({
805
841
  customer_id: customerId,
806
842
  action: orderId ? 'edit' : 'create',
807
843
  with_good_pass: 1,
808
844
  with_discount_card: 1,
809
845
  with_wallet_pass_holder: 1,
810
846
  with_entitlement_pass: 1,
847
+ filter_order_wallet_pass_usage: 0,
811
848
  request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
812
849
  }, orderId ? {
813
850
  order_id: orderId
@@ -844,10 +881,10 @@ export var ShopDiscountImpl = /*#__PURE__*/function (_BaseModule) {
844
881
  newDiscountList = [].concat(_toConsumableArray(retainedScanDiscounts), _toConsumableArray(newGoodPassList)); // 存储原始优惠券列表
845
882
  this.store.originalDiscountList = newDiscountList;
846
883
  _context10.next = 16;
847
- return (_this$store$discount9 = this.store.discount) === null || _this$store$discount9 === void 0 ? void 0 : _this$store$discount9.setOriginalDiscountList(newDiscountList);
884
+ return (_this$store$discount10 = this.store.discount) === null || _this$store$discount10 === void 0 ? void 0 : _this$store$discount10.setOriginalDiscountList(newDiscountList);
848
885
  case 16:
849
- // 根据当前预约时间过滤优惠券列表
850
- filteredDiscountList = this.filterDiscountListByBookingTime(newDiscountList, this.store.currentBookingTime);
886
+ // 日程限制交给 Rules 计算可用状态,展示列表保留当前不可用的卡券。
887
+ filteredDiscountList = newDiscountList;
851
888
  this.store.filteredDiscountList = filteredDiscountList;
852
889
  this.setDiscountList(filteredDiscountList || []);
853
890
  if (!((_this$store$productLi2 = this.store.productList) !== null && _this$store$productLi2 !== void 0 && _this$store$productLi2.length)) {
@@ -59,12 +59,14 @@ export interface ScheduleItem {
59
59
  }
60
60
  export declare const getDateIsInSchedule: (dateTime: string, scheduleList: ScheduleItem[]) => boolean;
61
61
  /**
62
- * 根据预约时间过滤优惠券列表
62
+ * 根据商品使用时间过滤优惠券列表(预约开始时间或普通商品的当前时间)
63
63
  * @param discountList 优惠券列表
64
- * @param bookingTime 预约时间 (格式: YYYY-MM-DD HH:mm:ss)
64
+ * @param bookingTime 使用时间 (格式: YYYY-MM-DD HH:mm:ss),未确定时不提前过滤
65
65
  * @returns 过滤后的优惠券列表
66
66
  */
67
67
  export declare const filterDiscountListByBookingTime: (discountList: Discount[], bookingTime: string | null) => Discount[];
68
+ /** 与抵扣计算共用时间判断,区分未开始、有效期不匹配和日程限制。 */
69
+ export declare const getDiscountTimeUnavailableReason: (discount: Discount, useTime: string) => 'not_started' | 'time_limit' | 'outside_schedule' | undefined;
68
70
  /**
69
71
  * 判断是否是订单级别的固定金额折扣卡
70
72
  * @param discount 折扣
@@ -9,7 +9,11 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
9
9
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
10
10
  import Decimal from "decimal.js";
11
11
  import dayjs from "dayjs";
12
+ import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
13
+ import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
12
14
  import { isFullCoveragePass } from "../../modules/Discount/entitlementPass";
15
+ dayjs.extend(isSameOrAfter);
16
+ dayjs.extend(isSameOrBefore);
13
17
  export var uniqueById = function uniqueById(arr) {
14
18
  var key = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'id';
15
19
  var seen = new Set();
@@ -440,36 +444,51 @@ var isInWeeklyRepeatForDate = function isInWeeklyRepeatForDate(targetDate, start
440
444
  };
441
445
 
442
446
  /**
443
- * 根据预约时间过滤优惠券列表
447
+ * 根据商品使用时间过滤优惠券列表(预约开始时间或普通商品的当前时间)
444
448
  * @param discountList 优惠券列表
445
- * @param bookingTime 预约时间 (格式: YYYY-MM-DD HH:mm:ss)
449
+ * @param bookingTime 使用时间 (格式: YYYY-MM-DD HH:mm:ss),未确定时不提前过滤
446
450
  * @returns 过滤后的优惠券列表
447
451
  */
448
452
  export var filterDiscountListByBookingTime = function filterDiscountListByBookingTime(discountList, bookingTime) {
449
- if (!bookingTime) {
450
- return discountList;
451
- }
453
+ if (!bookingTime) return discountList;
452
454
  return discountList.filter(function (discount) {
453
- var _discount$metadata2;
455
+ return !getDiscountTimeUnavailableReason(discount, bookingTime);
456
+ });
457
+ };
458
+
459
+ /** 与抵扣计算共用时间判断,区分未开始、有效期不匹配和日程限制。 */
460
+ export var getDiscountTimeUnavailableReason = function getDiscountTimeUnavailableReason(discount, useTime) {
461
+ var useTimeDayjs = dayjs(useTime);
462
+ if (!useTimeDayjs.isValid()) return 'time_limit';
463
+ try {
464
+ var _discount$metadata2, _discount$extension_d, _discount$custom_sche2;
454
465
  if ((discount === null || discount === void 0 || (_discount$metadata2 = discount.metadata) === null || _discount$metadata2 === void 0 ? void 0 : _discount$metadata2.validity_type) === "custom_schedule_validity") {
455
466
  var _discount$custom_sche;
456
- return getDateIsInSchedule(bookingTime, ((_discount$custom_sche = discount.custom_schedule_snapshot) === null || _discount$custom_sche === void 0 ? void 0 : _discount$custom_sche.data) || []);
467
+ return getDateIsInSchedule(useTime, ((_discount$custom_sche = discount.custom_schedule_snapshot) === null || _discount$custom_sche === void 0 ? void 0 : _discount$custom_sche.data) || []) ? undefined : 'outside_schedule';
457
468
  }
458
- try {
459
- var isHasStart = !!discount.start_date && !!discount.start_time;
460
- var startDate = dayjs("".concat(discount.start_date, " ").concat(discount.start_time));
461
- var bookingTimeDayjs = dayjs(bookingTime);
462
-
463
- // 判断预约时间是否在开始时间之后
464
- var isAfterStart = isHasStart ? bookingTimeDayjs.isSameOrAfter(startDate) : true;
465
-
466
- // 判断预约时间是否在过期时间之前(expire_time 为空时为长期有效)
467
- var isBeforeExpire = discount.expire_time ? bookingTimeDayjs.isSameOrBefore(dayjs(discount.expire_time)) : true;
468
- return isAfterStart && isBeforeExpire;
469
- } catch (error) {
470
- return true;
469
+ var isHasStart = !!discount.start_date && !!discount.start_time;
470
+ var startDate = dayjs("".concat(discount.start_date, " ").concat(discount.start_time));
471
+
472
+ // 开始时刻包含在有效期内。
473
+ if (isHasStart) {
474
+ if (!startDate.isValid()) return 'time_limit';
475
+ if (useTimeDayjs.isBefore(startDate)) return 'not_started';
471
476
  }
472
- });
477
+
478
+ // 到期时刻不再可用;expire_time 为空时为长期有效。
479
+ var expireTime = discount.expire_time ? dayjs(discount.expire_time) : null;
480
+ var isBeforeExpire = expireTime ? expireTime.isValid() && useTimeDayjs.isBefore(expireTime) : true;
481
+ if (!isBeforeExpire) return 'time_limit';
482
+ var validity = (_discount$extension_d = discount.extension_data) === null || _discount$extension_d === void 0 || (_discount$extension_d = _discount$extension_d.find(function (field) {
483
+ return field.field_key === 'valid_times';
484
+ })) === null || _discount$extension_d === void 0 ? void 0 : _discount$extension_d.value;
485
+ if ((validity === null || validity === void 0 ? void 0 : validity.restrict_use_by_schedule) === true && !getDateIsInSchedule(useTime, ((_discount$custom_sche2 = discount.custom_schedule_snapshot) === null || _discount$custom_sche2 === void 0 ? void 0 : _discount$custom_sche2.data) || [])) {
486
+ return 'outside_schedule';
487
+ }
488
+ return undefined;
489
+ } catch (error) {
490
+ return 'time_limit';
491
+ }
473
492
  };
474
493
  /**
475
494
  * 判断是否是订单级别的固定金额折扣卡
@@ -28,6 +28,8 @@ export declare class WalletPassEvaluator {
28
28
  /** 检查代金券/折扣卡是否可用 并返回config */
29
29
  checkVoucherAvailability(input: EvaluatorInput): {
30
30
  isAvailable: boolean;
31
+ reason: string | undefined;
32
+ reasonCode: string | undefined;
31
33
  config: {
32
34
  [x: string]: any;
33
35
  };
@@ -256,6 +256,8 @@ class WalletPassEvaluator {
256
256
  const isAvailable = results[0]?.isApplicable || false;
257
257
  return {
258
258
  isAvailable,
259
+ reason: results[0]?.reason,
260
+ reasonCode: results[0]?.reasonCode,
259
261
  config: {
260
262
  ...results[0]?.strategyResult?.config?.metadata?.custom
261
263
  }
@@ -150,6 +150,7 @@ class CustomerModule extends _BaseModule.BaseModule {
150
150
  with: ['latestWalletDetail.wallet', 'contactsInfo', 'formRecord'],
151
151
  search_wallet_flag: 1,
152
152
  search_wallet_pass_flag: 1,
153
+ filter_franchisor_permissions: ['use'],
153
154
  ...(search && {
154
155
  search
155
156
  }),
@@ -0,0 +1,7 @@
1
+ import type { Discount, DiscountFilterRejectKey } from './types';
2
+ /** 周期限制只影响当前使用;total_credits 才代表整张卡的次数耗尽。 */
3
+ export declare function getDiscountUsageLimitFailKey(discount: Discount): DiscountFilterRejectKey | null;
4
+ export declare function hasAvailableDiscountBalance(discount: Discount): boolean;
5
+ /** 按卡本身当前生命周期判断;预约时间或一次日程不匹配不代表已失效。 */
6
+ export declare function getDiscountLifecycleReason(discount: Discount): 'disabled' | 'exhausted' | 'expired' | undefined;
7
+ export declare function getDiscountUsageBlockReason(discount: Discount): string | undefined;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.getDiscountLifecycleReason = getDiscountLifecycleReason;
7
+ exports.getDiscountUsageBlockReason = getDiscountUsageBlockReason;
8
+ exports.getDiscountUsageLimitFailKey = getDiscountUsageLimitFailKey;
9
+ exports.hasAvailableDiscountBalance = hasAvailableDiscountBalance;
10
+ var _dayjs = _interopRequireDefault(require("dayjs"));
11
+ var _decimal = _interopRequireDefault(require("decimal.js"));
12
+ var _entitlementPass = require("./entitlementPass");
13
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
14
+ /** 周期限制只影响当前使用;total_credits 才代表整张卡的次数耗尽。 */
15
+ function getDiscountUsageLimitFailKey(discount) {
16
+ const value = discount.extension_data?.find(field => field.field_key === 'usage_credits')?.value;
17
+ if (!value) return null;
18
+ const limits = [['total_credits', discount.total_order_behavior_count], ['per_user_limit', discount.customer_order_behavior_count], ['max_per_day', discount.today_order_behavior_count], ['max_per_week', discount.week_order_behavior_count], ['max_per_month', discount.month_order_behavior_count]];
19
+ return limits.find(([key, count]) => Number(value[key]) > 0 && Number(count || 0) >= Number(value[key]))?.[0] || null;
20
+ }
21
+ function hasAvailableDiscountBalance(discount) {
22
+ if ((0, _entitlementPass.isEntitlementPass)(discount)) return (0, _entitlementPass.hasAvailableEntitlementUses)(discount);
23
+ return new _decimal.default(discount.par_value || 0).minus(discount.used_par_value || 0).greaterThan(0);
24
+ }
25
+
26
+ /** 按卡本身当前生命周期判断;预约时间或一次日程不匹配不代表已失效。 */
27
+ function getDiscountLifecycleReason(discount) {
28
+ if (discount.limit_status && discount.limit_status !== 'enable') {
29
+ return 'disabled';
30
+ }
31
+ if (((0, _entitlementPass.isEntitlementPass)(discount) || discount.par_value != null) && !hasAvailableDiscountBalance(discount) || getDiscountUsageLimitFailKey(discount) === 'total_credits') {
32
+ return 'exhausted';
33
+ }
34
+ // 自定义日程沿用快照语义,不使用遗留固定有效期字段推断过期。
35
+ if (discount.metadata?.validity_type !== 'custom_schedule_validity' && discount.expire_time) {
36
+ const expiry = (0, _dayjs.default)(discount.expire_time);
37
+ if (expiry.isValid() && !(0, _dayjs.default)().isBefore(expiry)) return 'expired';
38
+ }
39
+ return undefined;
40
+ }
41
+ function getDiscountUsageBlockReason(discount) {
42
+ return getDiscountLifecycleReason(discount) || getDiscountUsageLimitFailKey(discount) || (discount.unified_available_status != null && String(discount.unified_available_status) !== '' && Number(discount.unified_available_status) === 0 ? 'server_unavailable' : undefined);
43
+ }
@@ -27,6 +27,7 @@ export declare class DiscountModule extends BaseModule implements Module, Discou
27
27
  with_available_wallet_pass_flag?: 0 | 1;
28
28
  with_virtual_currency?: 0 | 1;
29
29
  with_entitlement_pass?: 0 | 1;
30
+ filter_order_wallet_pass_usage?: 0 | 1;
30
31
  request_timezone: string;
31
32
  order_id?: number | string;
32
33
  }): Promise<DiscountPrepareConfigResult>;
@@ -37,10 +38,7 @@ export declare class DiscountModule extends BaseModule implements Module, Discou
37
38
  }): Promise<DiscountWithReason>;
38
39
  batchSearchByProductIds(productIds: number[]): Promise<any>;
39
40
  filterEnabledDiscountList(discountList: Discount[]): Discount[];
40
- private hasAvailableBalance;
41
- private checkUsageCreditsLimit;
42
41
  filterEnabledDiscountListWithReason(discountList: Discount[]): DiscountWithReason;
43
- private checkUsageCreditsLimitWithFailKey;
44
42
  uniqueByProductId(discountList: Discount[]): Discount[];
45
43
  filterDiscountListByProductIds(discountList: Discount[], productIds: number[]): Discount[];
46
44
  calcDiscountApplicableProductTotalPrice(discount: Discount): number | undefined;
@@ -8,7 +8,7 @@ var _utils = require("../../solution/ShopDiscount/utils");
8
8
  var _BaseModule = require("../BaseModule");
9
9
  var _types = require("./types");
10
10
  var _decimal = _interopRequireDefault(require("decimal.js"));
11
- var _entitlementPass = require("./entitlementPass");
11
+ var _availability = require("./availability");
12
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
13
  class DiscountModule extends _BaseModule.BaseModule {
14
14
  defaultName = 'discount';
@@ -170,143 +170,22 @@ class DiscountModule extends _BaseModule.BaseModule {
170
170
  });
171
171
  return result?.data || [];
172
172
  }
173
- filterEnabledDiscountList(discountList) {
174
- return discountList.filter(discount => discount.limit_status === 'enable' && this.hasAvailableBalance(discount) && this.checkUsageCreditsLimit(discount));
175
- }
176
- hasAvailableBalance(discount) {
177
- if ((0, _entitlementPass.isEntitlementPass)(discount)) {
178
- return (0, _entitlementPass.hasAvailableEntitlementUses)(discount);
179
- }
180
-
181
- // good_pass 与折扣卡继续沿用原有金额余额判断。
182
- return new _decimal.default(discount?.par_value || 0).minus(new _decimal.default(discount?.used_par_value || 0)).greaterThan(0);
183
- }
184
-
185
- // 检查使用次数限制
186
- checkUsageCreditsLimit(discount) {
187
- // 如果没有 extension_data,则不需要限制
188
- if (!discount.extension_data || discount.extension_data.length === 0) {
189
- return true;
190
- }
191
-
192
- // 查找 field_key 为 usage_credits 的数据
193
- const usageCreditsData = discount.extension_data.find(data => data.field_key === 'usage_credits');
194
-
195
- // 如果没有找到 usage_credits 数据,则不需要限制
196
- if (!usageCreditsData) {
197
- return true;
198
- }
199
- const value = usageCreditsData.value;
200
-
201
- // 检查总次数限制
202
- if (value.total_credits && value.total_credits > 0) {
203
- if ((discount.total_order_behavior_count || 0) >= value.total_credits) {
204
- return false;
205
- }
206
- }
207
-
208
- // 检查单用户限制
209
- if (value.per_user_limit && value.per_user_limit > 0) {
210
- if ((discount.customer_order_behavior_count || 0) >= value.per_user_limit) {
211
- return false;
212
- }
213
- }
214
-
215
- // 检查单日限制
216
- if (value.max_per_day && value.max_per_day > 0) {
217
- if ((discount.today_order_behavior_count || 0) >= value.max_per_day) {
218
- return false;
219
- }
220
- }
221
-
222
- // 检查单周限制
223
- if (value.max_per_week && value.max_per_week > 0) {
224
- if ((discount.week_order_behavior_count || 0) >= value.max_per_week) {
225
- return false;
226
- }
227
- }
228
-
229
- // 检查单月限制
230
- if (value.max_per_month && value.max_per_month > 0) {
231
- if ((discount.month_order_behavior_count || 0) >= value.max_per_month) {
232
- return false;
233
- }
234
- }
235
173
 
236
- // 所有检查都通过
237
- return true;
174
+ // 展示列表只剔除已失效项,当前不可用项保留并携带原因。
175
+ filterEnabledDiscountList(discountList) {
176
+ return discountList.filter(discount => discount.limit_status === 'enable' && (0, _availability.hasAvailableDiscountBalance)(discount) && !(0, _availability.getDiscountLifecycleReason)(discount)).map(discount => ({
177
+ ...discount,
178
+ usageLimitFailKey: (0, _availability.getDiscountUsageLimitFailKey)(discount),
179
+ reasonCode: (0, _availability.getDiscountUsageBlockReason)(discount)
180
+ }));
238
181
  }
239
-
240
- // 过滤启用且可用的优惠券,并返回校验失败原因
241
182
  filterEnabledDiscountListWithReason(discountList) {
242
- let lists = discountList.filter(discount => discount.limit_status === 'enable').filter(discount => this.hasAvailableBalance(discount)).map(discount => {
243
- const limitFailKey = this.checkUsageCreditsLimitWithFailKey(discount);
244
- return {
245
- ...discount,
246
- usageLimitFailKey: limitFailKey
247
- };
248
- });
249
-
250
- // 可用的列表
251
- const availableLists = lists.filter(item => !item.usageLimitFailKey && item.unified_available_status !== 0);
252
- // 不可用数据
253
- const currentFailDiscount = lists.find(item => item.usageLimitFailKey);
254
183
  return {
255
- discountList: availableLists,
256
- unavailableReasonKey: currentFailDiscount?.usageLimitFailKey
184
+ discountList: this.filterEnabledDiscountList(discountList),
185
+ unavailableReasonKey: discountList.map(_availability.getDiscountUsageLimitFailKey).find(key => key !== null) || null
257
186
  };
258
187
  }
259
188
 
260
- // 检查使用次数限制,并返回校验失败原因
261
- checkUsageCreditsLimitWithFailKey(discount) {
262
- if (!discount.extension_data || discount.extension_data.length === 0) {
263
- return null;
264
- }
265
- const usageCreditsData = discount.extension_data.find(data => data.field_key === 'usage_credits');
266
- if (!usageCreditsData) {
267
- return null;
268
- }
269
- const value = usageCreditsData.value;
270
-
271
- // 总次数限制
272
- if (value.total_credits && value.total_credits > 0) {
273
- if ((discount.total_order_behavior_count || 0) >= value.total_credits) {
274
- return 'total_credits';
275
- }
276
- }
277
-
278
- // 单用户限制
279
- if (value.per_user_limit && value.per_user_limit > 0) {
280
- if ((discount.customer_order_behavior_count || 0) >= value.per_user_limit) {
281
- return 'per_user_limit';
282
- }
283
- }
284
-
285
- // 单日限制
286
- if (value.max_per_day && value.max_per_day > 0) {
287
- if ((discount.today_order_behavior_count || 0) >= value.max_per_day) {
288
- return 'max_per_day';
289
- }
290
- }
291
-
292
- // 单周限制
293
- if (value.max_per_week && value.max_per_week > 0) {
294
- if ((discount.week_order_behavior_count || 0) >= value.max_per_week) {
295
- return 'max_per_week';
296
- }
297
- }
298
-
299
- // 单月限制
300
- if (value.max_per_month && value.max_per_month > 0) {
301
- if ((discount.month_order_behavior_count || 0) >= value.max_per_month) {
302
- return 'max_per_month';
303
- }
304
- }
305
-
306
- // 全部通过
307
- return null;
308
- }
309
-
310
189
  // 根据productIds去重
311
190
  uniqueByProductId(discountList) {
312
191
  return (0, _utils.uniqueById)(discountList, 'product_id');
@@ -201,6 +201,12 @@ export interface Discount {
201
201
  start_time?: string;
202
202
  discount_rule_uncheck_flag?: boolean;
203
203
  usageLimitFailKey?: string | null;
204
+ /** 本次规则计算的不可用原因;可用性恢复后清除。 */
205
+ reason?: string;
206
+ reasonCode?: string;
207
+ unified_messages?: {
208
+ title?: string | Record<string, string>;
209
+ };
204
210
  unified_available_status?: number | undefined | null;
205
211
  }
206
212
  export interface DiscountState {
@@ -9,6 +9,7 @@ var _types = require("./types");
9
9
  var _utils = require("./utils");
10
10
  var _utils2 = require("../Product/utils");
11
11
  var _dayjs = _interopRequireDefault(require("dayjs"));
12
+ var _availability = require("../Discount/availability");
12
13
  var _osPrivatePayload = require("../../utils/os-private-payload");
13
14
  var _salesNotes = require("./salesNotes");
14
15
  var _paymentUtils = require("./payment-utils");
@@ -496,7 +497,7 @@ class OrderModule extends _BaseModule.BaseModule {
496
497
  this.store.rules = rules;
497
498
  }
498
499
  createDefaultRulesHooks() {
499
- return (0, _utils.createDefaultOrderRulesHooks)();
500
+ return (0, _utils.createDefaultOrderRulesHooks)(() => this.store.tempOrder?.bookings || []);
500
501
  }
501
502
 
502
503
  /**
@@ -524,6 +525,7 @@ class OrderModule extends _BaseModule.BaseModule {
524
525
  with_available_wallet_pass_flag: 1,
525
526
  with_virtual_currency: 1,
526
527
  with_entitlement_pass: 1,
528
+ filter_order_wallet_pass_usage: 0,
527
529
  request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
528
530
  ...(orderId ? {
529
531
  order_id: orderId
@@ -719,19 +721,31 @@ class OrderModule extends _BaseModule.BaseModule {
719
721
  isAvailable: false,
720
722
  discountList: this.getDiscountList()
721
723
  };
722
- const shouldRetainForLater = !isAvailable && unavailableReason === _types2.UnavailableReason.ProductNotApplicable && withScanList.some(isCustomerPromotionDiscount);
724
+ const scannedIds = new Set(withScanList.map(discount => String(discount.id)));
725
+ const evaluatedScannedDiscounts = (evaluatedDiscountList || withScanList).filter(discount => scannedIds.has(String(discount.id)) && isCustomerPromotionDiscount(discount) && !(0, _availability.getDiscountLifecycleReason)(discount));
726
+ const shouldRetainForLater = !isAvailable && evaluatedScannedDiscounts.length > 0;
723
727
  const normalizedNewDiscountList = newDiscountList ? markScannedDiscounts(newDiscountList, withScanList) : undefined;
724
- const retainedDiscountList = evaluatedDiscountList ? markScannedDiscounts(evaluatedDiscountList, withScanList) : normalizedNewDiscountList;
725
728
  let resolvedDiscountList = normalizedNewDiscountList || this.getDiscountList();
726
729
  if (isAvailable && normalizedNewDiscountList) {
727
730
  await this.store.discount?.setDiscountList(normalizedNewDiscountList);
728
731
  this.applyDiscount();
729
732
  await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
730
733
  resolvedDiscountList = this.getDiscountList();
731
- } else if (shouldRetainForLater && retainedDiscountList) {
732
- // 卡券有效但当前购物车没有适用商品:先保留在 Promotion 中,
733
- // 不执行 applyDiscount,避免修改商品价格或订单金额。
734
- await this.store.discount?.setDiscountList(retainedDiscountList);
734
+ } else if (shouldRetainForLater) {
735
+ // 只合并本次识别的卡券;不把试算产生的其他选券状态写回订单。
736
+ const retainedDiscounts = new Map(this.getDiscountList().map(discount => [String(discount.id), discount]));
737
+ evaluatedScannedDiscounts.forEach(discount => {
738
+ retainedDiscounts.set(String(discount.id), {
739
+ ...discount,
740
+ isScan: true,
741
+ isAvailable: false,
742
+ isSelected: false,
743
+ reasonCode: discount.reasonCode || 'product_not_applicable',
744
+ appliedProductDetails: [],
745
+ savedAmount: 0
746
+ });
747
+ });
748
+ await this.store.discount?.setDiscountList([...retainedDiscounts.values()]);
735
749
  await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
736
750
  resolvedDiscountList = this.getDiscountList();
737
751
  }
@@ -740,7 +754,7 @@ class OrderModule extends _BaseModule.BaseModule {
740
754
  isAvailable: isAvailable || false,
741
755
  isAccepted: Boolean(isAvailable || shouldRetainForLater),
742
756
  discountList: resolvedDiscountList,
743
- scannedDiscountList: resultDiscountList,
757
+ scannedDiscountList: evaluatedScannedDiscounts,
744
758
  unavailableReason
745
759
  };
746
760
  }
@@ -161,7 +161,7 @@ export declare function resolveManualOverrideLineOriginalPrice(params: {
161
161
  * // => -4
162
162
  */
163
163
  export declare function resolveEffectivePerUnitDiscount(product: Record<string, any>): number;
164
- export declare function createDefaultOrderRulesHooks(): RulesParamsHooks;
164
+ export declare function createDefaultOrderRulesHooks(getBookings?: () => OrderTempOrder['bookings']): RulesParamsHooks;
165
165
  /**
166
166
  * 通过 session 类商品的开始时间结束时间生成商品的时长
167
167
  * @param {CartItem} cartItem