@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
@@ -127,6 +127,7 @@ Object.defineProperty(exports, "syncManualProductDiscountProductNum", {
127
127
  }
128
128
  });
129
129
  var _dayjs = _interopRequireDefault(require("dayjs"));
130
+ var _customParseFormat = _interopRequireDefault(require("dayjs/plugin/customParseFormat"));
130
131
  var _decimal = _interopRequireDefault(require("decimal.js"));
131
132
  var _utils = require("../Holder/utils");
132
133
  var _utils2 = require("../../solution/ScanOrder/utils");
@@ -134,6 +135,7 @@ var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
134
135
  var _salesNotes = require("./salesNotes");
135
136
  var _manualProductDiscount = require("./utils/manualProductDiscount");
136
137
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
138
+ _dayjs.default.extend(_customParseFormat.default);
137
139
  const POINT_CARD_SNAPSHOT_TAG = 'point_card';
138
140
  const VIRTUAL_CURRENCY_SNAPSHOT_TAG = 'virtual_currency';
139
141
  function isPlainObject(value) {
@@ -772,7 +774,23 @@ function resolveEffectivePerUnitDiscount(product) {
772
774
  if (!Number.isFinite(originalPrice) || !Number.isFinite(finalPrice)) return fromList;
773
775
  return fromList + (originalPrice - finalPrice);
774
776
  }
775
- function createDefaultOrderRulesHooks() {
777
+ function createDefaultOrderRulesHooks(getBookings) {
778
+ const getProductStartDate = product => {
779
+ const productUid = String(product.metadata?.unique_identification_number ?? product.unique_identification_number ?? '');
780
+ const bookingUid = String(product.booking_uid ?? product.metadata?.booking_uid ?? '');
781
+ // 每次读取当前 bookings,覆盖改期、订单回显以及直接调用 Rules 的选券入口。
782
+ const bookings = getBookings?.() || [];
783
+ const booking = bookings.find(item => productUid && String(item?.product_uid ?? item?.metadata?.product_uid ?? '') === productUid) || bookings.find(item => bookingUid && String(item?.metadata?.unique_identification_number ?? item?.booking_uid ?? '') === bookingUid);
784
+ if (booking) {
785
+ // 严格解析并保留 Invalid Date,让公共时间校验拒绝缺失/非法时间,避免回退到当前时间。
786
+ return (0, _dayjs.default)(`${booking.start_date} ${booking.start_time}`, ['YYYY-MM-DD HH:mm', 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD H:mm', 'YYYY-MM-DD H:mm:ss'], true);
787
+ }
788
+ if (getBookings && (bookingUid || product.booking_id || product.metadata?.booking_id)) {
789
+ return (0, _dayjs.default)('');
790
+ }
791
+ const legacyStartDate = product.metadata?.start_date || product.metadata?.startDate;
792
+ return legacyStartDate ? (0, _dayjs.default)(legacyStartDate) : undefined;
793
+ };
776
794
  const toUnitPriceString = (totalLike, num) => {
777
795
  const effectiveNum = Number(num) > 0 ? Number(num) : 1;
778
796
  return new _decimal.default(Number(totalLike) || 0).dividedBy(effectiveNum).toDecimalPlaces(2).toString();
@@ -860,7 +878,7 @@ function createDefaultOrderRulesHooks() {
860
878
  // 无券时会把 bundle 价还原成 original_price(手动改价套餐会从 20 回到 30)。
861
879
  isManualDiscount: resolveRulesManualDiscountFlag(metadataAny),
862
880
  holder_id: product.metadata?.holder_id,
863
- startDate: product.metadata?.start_date || product.metadata?.startDate,
881
+ startDate: getProductStartDate(product),
864
882
  main_product_selling_price: metadataAny.main_product_selling_price !== undefined ? new _decimal.default(Number(metadataAny.main_product_selling_price) || 0).minus(optionSum).toDecimalPlaces(2).toString() : undefined,
865
883
  inPromotion: metadataAny._promotion?.inPromotion === true,
866
884
  _promotion: metadataAny._promotion
@@ -49,5 +49,5 @@ export declare class Product extends BaseModule implements Module {
49
49
  getCategories(): ProductCategory[];
50
50
  setOtherParams(key: string, value: any): void;
51
51
  getOtherParams(): any;
52
- getProductType(): "normal" | "duration" | "session";
52
+ getProductType(): "duration" | "session" | "normal";
53
53
  }
@@ -12,6 +12,7 @@ var _decimal = _interopRequireDefault(require("decimal.js"));
12
12
  var _lodashEs = require("lodash-es");
13
13
  var _dayjs = _interopRequireDefault(require("dayjs"));
14
14
  var _entitlementPass = require("../Discount/entitlementPass");
15
+ var _availability = require("../Discount/availability");
15
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
17
  class RulesModule extends _BaseModule.BaseModule {
17
18
  defaultName = 'rules';
@@ -240,7 +241,13 @@ class RulesModule extends _BaseModule.BaseModule {
240
241
  if (discount.isEditMode) {
241
242
  editModeDiscount.push(discount);
242
243
  } else {
243
- addModeDiscount.push(discount);
244
+ // 试算不能把 config / 不可用原因写回原有选券;每次重新计算以清除旧原因。
245
+ addModeDiscount.push({
246
+ ...discount,
247
+ usageLimitFailKey: (0, _availability.getDiscountUsageLimitFailKey)(discount),
248
+ reason: undefined,
249
+ reasonCode: (0, _availability.getDiscountUsageBlockReason)(discount)
250
+ });
244
251
  }
245
252
  });
246
253
 
@@ -481,8 +488,15 @@ class RulesModule extends _BaseModule.BaseModule {
481
488
 
482
489
  // 🔥 为每个折扣卡/商品券执行策略检查并附加config
483
490
  const evaluator = this.getWalletPassEvaluator();
491
+ addModeDiscount.forEach(discount => {
492
+ if (discount.reasonCode) discount.config = {
493
+ isAvailable: false
494
+ };
495
+ });
484
496
  if (evaluator) {
485
497
  addModeDiscount.forEach(discount => {
498
+ // 即使没有匹配的动态策略,服务端状态和次数限制也不能被覆盖为可用。
499
+ if (discount.reasonCode) return;
486
500
  const discountType = discount.tag || discount.type || '';
487
501
  // 对两类全额抵扣卡和折扣卡执行附加策略检查。
488
502
  if (['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discountType)) {
@@ -501,7 +515,7 @@ class RulesModule extends _BaseModule.BaseModule {
501
515
  balance: entitlementQuota?.type === 'unlimited' ? Number.MAX_SAFE_INTEGER : entitlementQuota?.type === 'limited' ? entitlementQuota.balance : Number(discount.balance || 0),
502
516
  type: discountType,
503
517
  product_id: discount.product_id,
504
- unified_available_status: 1,
518
+ unified_available_status: discount.unified_available_status ?? 1,
505
519
  available_product_type: discount.limited_relation_product_data?.type,
506
520
  available_product_ids: discount.limited_relation_product_data?.product_ids
507
521
  };
@@ -532,6 +546,8 @@ class RulesModule extends _BaseModule.BaseModule {
532
546
  discount.config = {
533
547
  isAvailable: false
534
548
  };
549
+ discount.reason = result.reason;
550
+ discount.reasonCode = result.reasonCode || 'not_meet_the_required_conditions';
535
551
  }
536
552
  }
537
553
  });
@@ -672,6 +688,8 @@ class RulesModule extends _BaseModule.BaseModule {
672
688
 
673
689
  // 记录每个优惠券适用的商品详细信息
674
690
  const discountApplicableProducts = new Map();
691
+ const discountTimeReasons = new Map();
692
+ const discountHolderMismatch = new Set();
675
693
 
676
694
  // 初始化每个优惠券的适用性和可抵扣商品
677
695
  addModeDiscount.forEach(discount => {
@@ -691,6 +709,7 @@ class RulesModule extends _BaseModule.BaseModule {
691
709
 
692
710
  // 辅助函数:检查商品是否对某个折扣卡可用
693
711
  const checkItemApplicableForDiscount = (flatItem, discount) => {
712
+ if (discount.config?.isAvailable === false) return false;
694
713
  let product;
695
714
  if (flatItem.type === 'main') {
696
715
  product = flatItem.product;
@@ -896,16 +915,23 @@ class RulesModule extends _BaseModule.BaseModule {
896
915
  isNeedHolder: resolveIsFormSubject(_tempVar) && !_tempVar?.isNormalProduct,
897
916
  holder_id: _tempVar?.holder_id || product.holder_id
898
917
  }, holders);
899
- let timeLimit = true;
900
- timeLimit = !!(0, _utils.filterDiscountListByBookingTime)([discount], (product?.startDate || (0, _dayjs.default)()).format('YYYY-MM-DD HH:mm:ss')).length;
918
+ const timeReason = (0, _utils.getDiscountTimeUnavailableReason)(discount, (product?.startDate || (0, _dayjs.default)()).format('YYYY-MM-DD HH:mm:ss'));
919
+ const timeLimit = !timeReason;
901
920
  // 是符合折扣的商品
902
- const isLimitedProduct = (limitedData.type === 'product_all' && limitedData.filter !== 1 || limitedData.type === 'product_all' && limitedData.filter === 1 && !limitedData.exclude_product_ids.includes(product.id) || limitedData.product_ids && limitedData.product_ids.includes(product.id)) && isHolderMatch;
921
+ const isProductInScope = limitedData.type === 'product_all' && limitedData.filter !== 1 || limitedData.type === 'product_all' && limitedData.filter === 1 && !limitedData.exclude_product_ids.includes(product.id) || limitedData.product_ids && limitedData.product_ids.includes(product.id);
922
+ const isLimitedProduct = isProductInScope && isHolderMatch;
903
923
 
904
924
  // 编辑的商品 使用了优惠券不可用
905
925
  const isAvailableProduct = flatItem.type === 'main' ? !(product?.booking_id && product?.discount_list?.length && product?.discount_list?.every(discount => discount.id && ['entitlement_pass', 'good_pass', 'discount_card', 'product_discount_card'].includes(discount.tag || discount.type))) : !(flatItem?.booking_id && !!flatItem?.bundleItem?.discount_list?.length && flatItem?.bundleItem?.discount_list?.every(discount => discount.id));
906
926
 
907
927
  // 套餐是否可用判断
908
928
  const isBundleAvailable = this.checkPackageSubItemUsageRules(discount, flatItem);
929
+ if (isProductInScope && isBundleAvailable && !isItemRewardProductDiscount) {
930
+ const timeReasons = discountTimeReasons.get(discount.id) || [];
931
+ timeReasons.push(timeReason);
932
+ discountTimeReasons.set(discount.id, timeReasons);
933
+ if (!isHolderMatch && timeLimit) discountHolderMismatch.add(discount.id);
934
+ }
909
935
 
910
936
  // 判断优惠券是否适用于该商品
911
937
  if (isAvailableProduct && !isItemRewardProductDiscount && isLimitedProduct && timeLimit && isBundleAvailable && discount.config?.isAvailable && (!(0, _entitlementPass.isEntitlementPass)(discount) || (0, _entitlementPass.getRemainingEntitlementUses)(discount, 0, 1) > 0)) {
@@ -2028,11 +2054,19 @@ class RulesModule extends _BaseModule.BaseModule {
2028
2054
 
2029
2055
  // 根据是否有适用商品决定isAvailable
2030
2056
  const isAvailable = applicableProducts.length > 0;
2057
+ const timeReasons = discountTimeReasons.get(discount.id) || [];
2058
+ const timeReason = timeReasons.length ? timeReasons.every(Boolean) ? timeReasons[0] : undefined : (0, _utils.getDiscountTimeUnavailableReason)(discount, (0, _dayjs.default)().format('YYYY-MM-DD HH:mm:ss'));
2059
+ const reasonCode = isAvailable ? undefined : (0, _availability.getDiscountUsageBlockReason)(discount) || timeReason || discount.reasonCode || (discountHolderMismatch.has(discount.id) ? 'holder_not_matched' : 'product_not_applicable');
2060
+ const availabilityReason = {
2061
+ reasonCode,
2062
+ reason: reasonCode && reasonCode === discount.reasonCode ? discount.reason : undefined
2063
+ };
2031
2064
 
2032
2065
  // 如果此优惠券被明确设置为不选中,保持其状态,但正确设置isAvailable
2033
2066
  if (typeof discount.isSelected === 'boolean' && discount.isSelected === false && discount.isManualSelect) {
2034
2067
  return {
2035
2068
  ...discount,
2069
+ ...availabilityReason,
2036
2070
  isSelected: false,
2037
2071
  isAvailable: isAvailable,
2038
2072
  // 正确设置可用性
@@ -2046,6 +2080,8 @@ class RulesModule extends _BaseModule.BaseModule {
2046
2080
  if (isDiscountUsed(discount)) {
2047
2081
  return {
2048
2082
  ...discount,
2083
+ reason: undefined,
2084
+ reasonCode: undefined,
2049
2085
  isSelected: true,
2050
2086
  isManualSelect: options?.scan ? false : discount.isManualSelect,
2051
2087
  // 标记为可用,因为它已被应用
@@ -2068,6 +2104,7 @@ class RulesModule extends _BaseModule.BaseModule {
2068
2104
  }
2069
2105
  return {
2070
2106
  ...discount,
2107
+ ...availabilityReason,
2071
2108
  // 如果有适用的商品,标记为可用
2072
2109
  isAvailable: isAvailable,
2073
2110
  isSelected,
@@ -441,10 +441,10 @@ export interface BaseSalesAddLogParams extends BaseSalesLogInput {
441
441
  /** BaseSales.scanPromotionCode 对外的轻量结果(UI 可用 getDiscountList 或 scannedDiscountList 补 Holder) */
442
442
  export interface BaseSalesScanCodeResult {
443
443
  isAvailable: boolean;
444
- /** 卡券已加入 Promotion;false 时可能是无效、过期或已使用。 */
444
+ /** 卡券已加入 Promotion;可能因次数、时间或当前订单条件而暂时无法应用。 */
445
445
  isAccepted?: boolean;
446
446
  type?: 'server' | string;
447
447
  unavailableReason?: 'time_limit' | string;
448
- /** batchSearch 原始结果;Holder 补全等流程可从这里读取原始卡券。 */
448
+ /** 扫码卡券资料及最新可用状态,供 Holder 补全等流程使用。 */
449
449
  scannedDiscountList?: Discount[];
450
450
  }
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 2 | 1 | 6 | 4 | 5 | 3;
329
+ weekNum: 0 | 1 | 2 | 3 | 4 | 5 | 6;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -43,6 +43,7 @@ export declare class ShopDiscountImpl extends BaseModule implements Module {
43
43
  batchSearchByProductIds(productIds: number[]): Promise<Discount[]>;
44
44
  scanCode(code: string, customerId?: number): Promise<{
45
45
  isAvailable: boolean;
46
+ isAccepted?: boolean;
46
47
  productList: Record<string, any>[];
47
48
  discountList: Discount[];
48
49
  type: "server" | "clientCalc";
@@ -10,6 +10,7 @@ var _Rules = require("../../modules/Rules");
10
10
  var _entitlementPass = require("../../modules/Discount/entitlementPass");
11
11
  var _utils = require("./utils");
12
12
  var _types = require("../../modules/Rules/types");
13
+ var _availability = require("../../modules/Discount/availability");
13
14
  var _decimal = _interopRequireDefault(require("decimal.js"));
14
15
  var _lodashEs = require("lodash-es");
15
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -349,6 +350,7 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
349
350
  const {
350
351
  productList: newProductList,
351
352
  discountList: newDiscountList,
353
+ evaluatedDiscountList,
352
354
  isAvailable,
353
355
  unavailableReason
354
356
  } = rulesModule.isDiscountListAvailable({
@@ -363,6 +365,7 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
363
365
  productList: this.store.productList || [],
364
366
  discountList: this.getDiscountList()
365
367
  };
368
+ let isAccepted = isAvailable;
366
369
  if (isAvailable) {
367
370
  this.setDiscountList(newDiscountList || []);
368
371
  this.store.originalDiscountList = newDiscountList || [];
@@ -370,12 +373,34 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
370
373
  if (this.isWalkIn() && resultDiscountList.length && this.options.otherParams?.platform === 'shop' && !((resultDiscountList[0].customer_id || 1) === 1)) {
371
374
  await this.getCustomerWallet(resultDiscountList[0].customer_id);
372
375
  }
376
+ } else {
377
+ const scannedIds = new Set(withScanList.map(discount => String(discount.id)));
378
+ const retainedScannedDiscounts = (evaluatedDiscountList || withScanList).filter(discount => scannedIds.has(String(discount.id)) && !(0, _availability.getDiscountLifecycleReason)(discount));
379
+ if (retainedScannedDiscounts.length) {
380
+ const retainedDiscounts = new Map(this.getDiscountList().map(discount => [String(discount.id), discount]));
381
+ retainedScannedDiscounts.forEach(discount => {
382
+ retainedDiscounts.set(String(discount.id), {
383
+ ...discount,
384
+ isScan: true,
385
+ isAvailable: false,
386
+ isSelected: false,
387
+ reasonCode: discount.reasonCode || 'product_not_applicable',
388
+ appliedProductDetails: [],
389
+ savedAmount: 0
390
+ });
391
+ });
392
+ this.setDiscountList([...retainedDiscounts.values()]);
393
+ this.store.originalDiscountList = this.getDiscountList();
394
+ await this.store.discount?.setOriginalDiscountList(this.getDiscountList());
395
+ isAccepted = true;
396
+ }
373
397
  }
374
398
  return {
375
399
  type: "clientCalc",
376
400
  isAvailable: isAvailable || false,
401
+ isAccepted: Boolean(isAccepted),
377
402
  productList: newProductList || this.store.productList || [],
378
- discountList: newDiscountList || this.getDiscountList(),
403
+ discountList: this.getDiscountList(),
379
404
  unavailableReason
380
405
  };
381
406
  } catch (error) {
@@ -525,6 +550,7 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
525
550
  with_discount_card: 1,
526
551
  with_wallet_pass_holder: 1,
527
552
  with_entitlement_pass: 1,
553
+ filter_order_wallet_pass_usage: 0,
528
554
  request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
529
555
  ...(orderId ? {
530
556
  order_id: orderId
@@ -559,8 +585,8 @@ class ShopDiscountImpl extends _BaseModule.BaseModule {
559
585
  this.store.originalDiscountList = newDiscountList;
560
586
  await this.store.discount?.setOriginalDiscountList(newDiscountList);
561
587
 
562
- // 根据当前预约时间过滤优惠券列表
563
- const filteredDiscountList = this.filterDiscountListByBookingTime(newDiscountList, this.store.currentBookingTime);
588
+ // 日程限制交给 Rules 计算可用状态,展示列表保留当前不可用的卡券。
589
+ const filteredDiscountList = newDiscountList;
564
590
  this.store.filteredDiscountList = filteredDiscountList;
565
591
  this.setDiscountList(filteredDiscountList || []);
566
592
  if (this.store.productList?.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 折扣
@@ -3,11 +3,15 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.uniqueById = exports.isOrderLevelFixedAmountDiscount = exports.isNormalProductByDurationSchedule = exports.isAllNormalProduct = exports.getDiscountListAmountTotal = exports.getDiscountListAmount = exports.getDiscountAmount = exports.getDateIsInSchedule = exports.filterDiscountListByBookingTime = exports.calculateOrderLevelDiscountAllocation = void 0;
6
+ exports.uniqueById = exports.isOrderLevelFixedAmountDiscount = exports.isNormalProductByDurationSchedule = exports.isAllNormalProduct = exports.getDiscountTimeUnavailableReason = exports.getDiscountListAmountTotal = exports.getDiscountListAmount = exports.getDiscountAmount = exports.getDateIsInSchedule = exports.filterDiscountListByBookingTime = exports.calculateOrderLevelDiscountAllocation = void 0;
7
7
  var _decimal = _interopRequireDefault(require("decimal.js"));
8
8
  var _dayjs = _interopRequireDefault(require("dayjs"));
9
+ var _isSameOrAfter = _interopRequireDefault(require("dayjs/plugin/isSameOrAfter"));
10
+ var _isSameOrBefore = _interopRequireDefault(require("dayjs/plugin/isSameOrBefore"));
9
11
  var _entitlementPass = require("../../modules/Discount/entitlementPass");
10
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+ _dayjs.default.extend(_isSameOrAfter.default);
14
+ _dayjs.default.extend(_isSameOrBefore.default);
11
15
  const uniqueById = (arr, key = 'id') => {
12
16
  const seen = new Set();
13
17
  return arr.filter(item => !seen.has(item[key]) && seen.add(item[key]));
@@ -377,41 +381,53 @@ const isInWeeklyRepeatForDate = (targetDate, startDate, repeatRule) => {
377
381
  };
378
382
 
379
383
  /**
380
- * 根据预约时间过滤优惠券列表
384
+ * 根据商品使用时间过滤优惠券列表(预约开始时间或普通商品的当前时间)
381
385
  * @param discountList 优惠券列表
382
- * @param bookingTime 预约时间 (格式: YYYY-MM-DD HH:mm:ss)
386
+ * @param bookingTime 使用时间 (格式: YYYY-MM-DD HH:mm:ss),未确定时不提前过滤
383
387
  * @returns 过滤后的优惠券列表
384
388
  */
385
389
  const filterDiscountListByBookingTime = (discountList, bookingTime) => {
386
- if (!bookingTime) {
387
- return discountList;
388
- }
389
- return discountList.filter(discount => {
390
+ if (!bookingTime) return discountList;
391
+ return discountList.filter(discount => !getDiscountTimeUnavailableReason(discount, bookingTime));
392
+ };
393
+
394
+ /** 与抵扣计算共用时间判断,区分未开始、有效期不匹配和日程限制。 */
395
+ exports.filterDiscountListByBookingTime = filterDiscountListByBookingTime;
396
+ const getDiscountTimeUnavailableReason = (discount, useTime) => {
397
+ const useTimeDayjs = (0, _dayjs.default)(useTime);
398
+ if (!useTimeDayjs.isValid()) return 'time_limit';
399
+ try {
390
400
  if (discount?.metadata?.validity_type === "custom_schedule_validity") {
391
- return getDateIsInSchedule(bookingTime, discount.custom_schedule_snapshot?.data || []);
401
+ return getDateIsInSchedule(useTime, discount.custom_schedule_snapshot?.data || []) ? undefined : 'outside_schedule';
392
402
  }
393
- try {
394
- const isHasStart = !!discount.start_date && !!discount.start_time;
395
- const startDate = (0, _dayjs.default)(`${discount.start_date} ${discount.start_time}`);
396
- const bookingTimeDayjs = (0, _dayjs.default)(bookingTime);
397
-
398
- // 判断预约时间是否在开始时间之后
399
- const isAfterStart = isHasStart ? bookingTimeDayjs.isSameOrAfter(startDate) : true;
400
-
401
- // 判断预约时间是否在过期时间之前(expire_time 为空时为长期有效)
402
- const isBeforeExpire = discount.expire_time ? bookingTimeDayjs.isSameOrBefore((0, _dayjs.default)(discount.expire_time)) : true;
403
- return isAfterStart && isBeforeExpire;
404
- } catch (error) {
405
- return true;
403
+ const isHasStart = !!discount.start_date && !!discount.start_time;
404
+ const startDate = (0, _dayjs.default)(`${discount.start_date} ${discount.start_time}`);
405
+
406
+ // 开始时刻包含在有效期内。
407
+ if (isHasStart) {
408
+ if (!startDate.isValid()) return 'time_limit';
409
+ if (useTimeDayjs.isBefore(startDate)) return 'not_started';
406
410
  }
407
- });
411
+
412
+ // 到期时刻不再可用;expire_time 为空时为长期有效。
413
+ const expireTime = discount.expire_time ? (0, _dayjs.default)(discount.expire_time) : null;
414
+ const isBeforeExpire = expireTime ? expireTime.isValid() && useTimeDayjs.isBefore(expireTime) : true;
415
+ if (!isBeforeExpire) return 'time_limit';
416
+ const validity = discount.extension_data?.find(field => field.field_key === 'valid_times')?.value;
417
+ if (validity?.restrict_use_by_schedule === true && !getDateIsInSchedule(useTime, discount.custom_schedule_snapshot?.data || [])) {
418
+ return 'outside_schedule';
419
+ }
420
+ return undefined;
421
+ } catch (error) {
422
+ return 'time_limit';
423
+ }
408
424
  };
409
425
  /**
410
426
  * 判断是否是订单级别的固定金额折扣卡
411
427
  * @param discount 折扣
412
428
  * @returns 是否是订单级别的固定金额折扣卡
413
429
  */
414
- exports.filterDiscountListByBookingTime = filterDiscountListByBookingTime;
430
+ exports.getDiscountTimeUnavailableReason = getDiscountTimeUnavailableReason;
415
431
  const isOrderLevelFixedAmountDiscount = discount => {
416
432
  return discount.tag === 'product_discount_card' && discount?.metadata?.discount_card_type === 'fixed_amount' && discount?.metadata?.discount_calculation_mode === 'order_level';
417
433
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "0.10.44",
4
+ "version": "0.10.45",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",