@pisell/pisellos 2.2.309 → 2.2.310

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 (121) hide show
  1. package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  2. package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
  3. package/dist/modules/BookingContext/index.js +4 -4
  4. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
  5. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
  6. package/dist/modules/Customer/index.js +6 -3
  7. package/dist/modules/Customer/types.d.ts +2 -0
  8. package/dist/modules/Discount/availability.d.ts +7 -0
  9. package/dist/modules/Discount/availability.js +49 -0
  10. package/dist/modules/Discount/entitlementPass.d.ts +34 -0
  11. package/dist/modules/Discount/entitlementPass.js +147 -0
  12. package/dist/modules/Discount/index.d.ts +2 -2
  13. package/dist/modules/Discount/index.js +37 -151
  14. package/dist/modules/Discount/types.d.ts +36 -5
  15. package/dist/modules/OpenData/index.d.ts +1 -1
  16. package/dist/modules/OpenData/index.js +21 -12
  17. package/dist/modules/Order/index.d.ts +32 -4
  18. package/dist/modules/Order/index.js +733 -373
  19. package/dist/modules/Order/types.d.ts +17 -0
  20. package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
  21. package/dist/modules/Order/utils.d.ts +14 -2
  22. package/dist/modules/Order/utils.js +275 -76
  23. package/dist/modules/Payment/index.d.ts +0 -5
  24. package/dist/modules/Payment/index.js +35 -85
  25. package/dist/modules/Payment/types.d.ts +21 -1
  26. package/dist/modules/Payment/types.js +2 -0
  27. package/dist/modules/Payment/walletpass.d.ts +1 -1
  28. package/dist/modules/Payment/walletpass.js +80 -47
  29. package/dist/modules/ProductList/index.js +18 -5
  30. package/dist/modules/Rules/index.d.ts +2 -2
  31. package/dist/modules/Rules/index.js +480 -186
  32. package/dist/modules/Rules/types.d.ts +4 -0
  33. package/dist/server/index.js +8 -5
  34. package/dist/server/modules/order/index.js +6 -0
  35. package/dist/server/modules/order/types.d.ts +12 -0
  36. package/dist/server/modules/payment/index.d.ts +25 -6
  37. package/dist/server/modules/payment/index.js +660 -133
  38. package/dist/server/utils/small-ticket.d.ts +8 -5
  39. package/dist/server/utils/small-ticket.js +1269 -199
  40. package/dist/solution/BaseSales/index.d.ts +11 -2
  41. package/dist/solution/BaseSales/index.js +1463 -1173
  42. package/dist/solution/BaseSales/types.d.ts +7 -3
  43. package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
  44. package/dist/solution/BookingByStep/index.d.ts +1 -1
  45. package/dist/solution/BookingTicket/index.d.ts +12 -3
  46. package/dist/solution/BookingTicket/index.js +663 -542
  47. package/dist/solution/BookingTicket/types.d.ts +5 -1
  48. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  49. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +338 -147
  50. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
  51. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
  52. package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +19 -6
  53. package/dist/solution/ShopDiscount/index.d.ts +2 -0
  54. package/dist/solution/ShopDiscount/index.js +99 -42
  55. package/dist/solution/ShopDiscount/utils.d.ts +4 -2
  56. package/dist/solution/ShopDiscount/utils.js +43 -23
  57. package/dist/solution/UnifiedBookingSales/index.d.ts +2 -2
  58. package/dist/solution/UnifiedBookingSales/index.js +5 -5
  59. package/dist/utils/os-private-payload.d.ts +5 -0
  60. package/dist/utils/os-private-payload.js +17 -0
  61. package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  62. package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
  63. package/lib/modules/BookingContext/index.js +3 -3
  64. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
  65. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
  66. package/lib/modules/Customer/index.js +5 -1
  67. package/lib/modules/Customer/types.d.ts +2 -0
  68. package/lib/modules/Discount/availability.d.ts +7 -0
  69. package/lib/modules/Discount/availability.js +43 -0
  70. package/lib/modules/Discount/entitlementPass.d.ts +34 -0
  71. package/lib/modules/Discount/entitlementPass.js +141 -0
  72. package/lib/modules/Discount/index.d.ts +2 -2
  73. package/lib/modules/Discount/index.js +23 -129
  74. package/lib/modules/Discount/types.d.ts +36 -5
  75. package/lib/modules/OpenData/index.d.ts +1 -1
  76. package/lib/modules/OpenData/index.js +11 -2
  77. package/lib/modules/Order/index.d.ts +32 -4
  78. package/lib/modules/Order/index.js +341 -71
  79. package/lib/modules/Order/types.d.ts +17 -0
  80. package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
  81. package/lib/modules/Order/utils.d.ts +14 -2
  82. package/lib/modules/Order/utils.js +186 -3
  83. package/lib/modules/Payment/index.d.ts +0 -5
  84. package/lib/modules/Payment/index.js +3 -28
  85. package/lib/modules/Payment/types.d.ts +21 -1
  86. package/lib/modules/Payment/types.js +1 -0
  87. package/lib/modules/Payment/walletpass.d.ts +1 -1
  88. package/lib/modules/Payment/walletpass.js +29 -9
  89. package/lib/modules/ProductList/index.js +14 -1
  90. package/lib/modules/Rules/index.d.ts +2 -2
  91. package/lib/modules/Rules/index.js +357 -110
  92. package/lib/modules/Rules/types.d.ts +4 -0
  93. package/lib/server/index.js +8 -5
  94. package/lib/server/modules/order/index.js +6 -0
  95. package/lib/server/modules/order/types.d.ts +12 -0
  96. package/lib/server/modules/payment/index.d.ts +25 -6
  97. package/lib/server/modules/payment/index.js +350 -78
  98. package/lib/server/utils/small-ticket.d.ts +8 -5
  99. package/lib/server/utils/small-ticket.js +1009 -121
  100. package/lib/solution/BaseSales/index.d.ts +11 -2
  101. package/lib/solution/BaseSales/index.js +164 -30
  102. package/lib/solution/BaseSales/types.d.ts +7 -3
  103. package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
  104. package/lib/solution/BookingByStep/index.d.ts +1 -1
  105. package/lib/solution/BookingTicket/index.d.ts +12 -3
  106. package/lib/solution/BookingTicket/index.js +103 -41
  107. package/lib/solution/BookingTicket/types.d.ts +5 -1
  108. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
  109. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +174 -42
  110. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
  111. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
  112. package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +11 -3
  113. package/lib/solution/ShopDiscount/index.d.ts +2 -0
  114. package/lib/solution/ShopDiscount/index.js +55 -9
  115. package/lib/solution/ShopDiscount/utils.d.ts +4 -2
  116. package/lib/solution/ShopDiscount/utils.js +42 -25
  117. package/lib/solution/UnifiedBookingSales/index.d.ts +2 -2
  118. package/lib/solution/UnifiedBookingSales/index.js +5 -5
  119. package/lib/utils/os-private-payload.d.ts +5 -0
  120. package/lib/utils/os-private-payload.js +22 -0
  121. package/package.json +1 -1
@@ -7,6 +7,7 @@ exports.buildCacheItemFromOrderLine = buildCacheItemFromOrderLine;
7
7
  var _dayjs = _interopRequireDefault(require("dayjs"));
8
8
  var _utils = require("../../Order/utils");
9
9
  var _manualProductDiscount = require("../../Order/utils/manualProductDiscount");
10
+ var _entitlementPass = require("../../Discount/entitlementPass");
10
11
  var _orderLineDisplay = require("./orderLineDisplay");
11
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
13
  function safeFormat(value, fmt) {
@@ -116,11 +117,38 @@ function resolveProductTitle(product, booking, sourceProduct) {
116
117
  function resolveProductCover(product, booking, sourceProduct) {
117
118
  return sourceProduct?.cover ?? product.product_cover ?? '';
118
119
  }
119
- function resolveMainProductPrice(product, fallback) {
120
- const metadata = product.metadata || {};
121
- const value = metadata.main_product_original_price ?? metadata.source_product_price;
120
+ function toFinitePrice(value) {
121
+ if (value === undefined || value === null || value === '') return null;
122
122
  const parsed = Number(value);
123
- return Number.isFinite(parsed) ? parsed : fallback;
123
+ return Number.isFinite(parsed) ? parsed : null;
124
+ }
125
+ function hasEntitlementPassDiscount(product) {
126
+ const hasEntitlementDiscount = discountList => Array.isArray(discountList) && discountList.some(_entitlementPass.isEntitlementPass);
127
+ return hasEntitlementDiscount(product.discount_list) || Array.isArray(product.product_bundle) && product.product_bundle.some(bundle => hasEntitlementDiscount(bundle?.discount_list));
128
+ }
129
+ function resolveMainProductPrice(product, fallback, sourceProduct, restoreEntitlementPrice = false) {
130
+ const metadata = product.metadata || {};
131
+ if (!restoreEntitlementPrice) {
132
+ const value = metadata.main_product_original_price ?? metadata.source_product_price;
133
+ const parsed = Number(value);
134
+ return Number.isFinite(parsed) ? parsed : fallback;
135
+ }
136
+ const sourcePrice = toFinitePrice(metadata.source_product_price);
137
+ if (sourcePrice !== null) return sourcePrice;
138
+ const variantId = Number(product.product_variant_id || 0);
139
+ const variantList = sourceProduct?.variant ?? metadata.origin?.variant;
140
+ if (variantId > 0 && Array.isArray(variantList)) {
141
+ const matched = variantList.find(variant => Number(variant?.id) === variantId);
142
+ const variantPrice = toFinitePrice(matched?.price ?? matched?.base_price);
143
+ if (variantPrice !== null) return variantPrice;
144
+ }
145
+ const catalogPrice = toFinitePrice(sourceProduct?.price ?? sourceProduct?.base_price ?? metadata.origin?.price ?? metadata.origin?.base_price);
146
+ if (catalogPrice !== null) return catalogPrice;
147
+
148
+ // legacy 数据没有 source_product_price 时,main_product_original_price
149
+ // 仍比已被月卡改写为 0 的 selling_price 更可靠。
150
+ const legacyMainPrice = toFinitePrice(metadata.main_product_original_price);
151
+ return legacyMainPrice ?? fallback;
124
152
  }
125
153
  function mapOrderBundleToOtherBundle(bundle) {
126
154
  return bundle.map(item => {
@@ -185,7 +213,8 @@ function buildCacheItemFromOrderLine(input) {
185
213
  const resolvedId = product.product_id ?? sourceProduct?.id;
186
214
  const selling = Number(product.selling_price ?? 0);
187
215
  const original = Number(product.original_price ?? selling);
188
- const mainProductPrice = resolveMainProductPrice(product, selling);
216
+ const shouldRestoreEntitlementPrice = hasEntitlementPassDiscount(product);
217
+ const mainProductPrice = resolveMainProductPrice(product, selling, sourceProduct, shouldRestoreEntitlementPrice);
189
218
  const startDate = safeFormat(booking.start_date, 'YYYY-MM-DD');
190
219
  const endDate = safeFormat(booking.end_date, 'YYYY-MM-DD');
191
220
  const startTime = normalizeTimeString(booking.start_time);
@@ -240,7 +269,7 @@ function buildCacheItemFromOrderLine(input) {
240
269
  }
241
270
  }
242
271
  if (product.metadata?.price_override != null || manualDiscount) {
243
- extend.priceOverride = selling;
272
+ extend.priceOverride = shouldRestoreEntitlementPrice ? product.metadata?.price_override ?? selling : selling;
244
273
  }
245
274
  if (booking.holder !== undefined) {
246
275
  extend.holder = booking.holder;
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.buildNormalProductCacheItemFromOrderLine = buildNormalProductCacheItemFromOrderLine;
7
7
  var _utils = require("../../Order/utils");
8
+ var _entitlementPass = require("../../Discount/entitlementPass");
8
9
  var _orderLineDisplay = require("./orderLineDisplay");
9
10
  /**
10
11
  * 从已加车的普通商品 order line 反查 SkuDetailModal edit 所需的 cacheItem。
@@ -44,12 +45,16 @@ function resolveSourceProductPrice(product, sourceProduct) {
44
45
  }
45
46
  return Number(product.selling_price ?? 0);
46
47
  }
48
+ function hasEntitlementPassDiscount(product) {
49
+ const hasEntitlementDiscount = discountList => Array.isArray(discountList) && discountList.some(_entitlementPass.isEntitlementPass);
50
+ return hasEntitlementDiscount(product.discount_list) || Array.isArray(product.product_bundle) && product.product_bundle.some(bundle => hasEntitlementDiscount(bundle?.discount_list));
51
+ }
47
52
 
48
53
  /** 订单行 bundle → booking other.bundle(保留 original_price 供 setProductPrice 按比例分摊) */
49
- function mapOrderBundleToOtherBundle(bundle) {
54
+ function mapOrderBundleToOtherBundle(bundle, preferManualPrice = false) {
50
55
  return bundle.map(item => {
51
56
  const catalogOriginal = item.original_price ?? item.product_price ?? item.price ?? item.bundle_selling_price;
52
- const selling = item.bundle_selling_price ?? item.price ?? catalogOriginal;
57
+ const selling = preferManualPrice ? item.price ?? item.bundle_selling_price ?? catalogOriginal : item.bundle_selling_price ?? item.price ?? catalogOriginal;
53
58
  const {
54
59
  bundle_selling_price: _omitBundleSelling,
55
60
  ...rest
@@ -78,9 +83,11 @@ function buildNormalProductCacheItemFromOrderLine(input) {
78
83
  const selling = Number(product.selling_price ?? 0);
79
84
  const original = Number(product.original_price ?? selling);
80
85
  const uid = product.metadata?.unique_identification_number;
81
- const option = (0, _utils.getProductSkuOptions)(product);
82
- const bundle = mapOrderBundleToOtherBundle(Array.isArray(product.product_bundle) ? product.product_bundle : []);
83
86
  const originExtend = product.metadata?.origin?._extend;
87
+ const shouldPreserveEntitlementPriceOverride = hasEntitlementPassDiscount(product) && (originExtend?.priceOverride !== undefined || product.metadata?.price_override != null);
88
+ const hasPriceOverride = originExtend?.priceOverride !== undefined || shouldPreserveEntitlementPriceOverride;
89
+ const option = (0, _utils.getProductSkuOptions)(product);
90
+ const bundle = mapOrderBundleToOtherBundle(Array.isArray(product.product_bundle) ? product.product_bundle : [], shouldPreserveEntitlementPriceOverride);
84
91
  const productOptionString = (0, _orderLineDisplay.buildProductOptionStringFromOrderLine)(product);
85
92
  const discountList = product.discount_list || [];
86
93
  const other = {
@@ -114,8 +121,8 @@ function buildNormalProductCacheItemFromOrderLine(input) {
114
121
  ...(productOptionString ? {
115
122
  product_option_string: productOptionString
116
123
  } : {}),
117
- ...(originExtend?.priceOverride !== undefined ? {
118
- priceOverride: originExtend.priceOverride
124
+ ...(hasPriceOverride ? {
125
+ priceOverride: shouldPreserveEntitlementPriceOverride ? originExtend?.priceOverride ?? product.metadata?.price_override : originExtend?.priceOverride
119
126
  } : {}),
120
127
  ...(originExtend?.bundle_edit !== undefined ? {
121
128
  bundle_edit: originExtend.bundle_edit
@@ -150,10 +150,14 @@ 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
  }),
156
- ..._otherParams
157
+ ..._otherParams,
158
+ ...(url === '/customer/select-list' ? {
159
+ with_franchisor_data: 1
160
+ } : {})
157
161
  };
158
162
  const res = await this.request.get(url, queryParams, {
159
163
  cache: {
@@ -50,6 +50,8 @@ export interface ShopGetCustomerListParams {
50
50
  search_wallet_flag?: 0 | 1;
51
51
  /** wallet pass搜索标识 */
52
52
  search_wallet_pass_flag?: 0 | 1;
53
+ /** 是否查询连锁加盟组合店铺数据 */
54
+ with_franchisor_data?: boolean | 0 | 1;
53
55
  /** 其他筛选参数 */
54
56
  [key: string]: any;
55
57
  }
@@ -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
+ }
@@ -0,0 +1,34 @@
1
+ export declare const ENTITLEMENT_PASS_TYPE = "entitlement_pass";
2
+ export declare const GOOD_PASS_TYPE = "good_pass";
3
+ type DiscountLike = {
4
+ id?: number | string | null;
5
+ tag?: unknown;
6
+ type?: unknown;
7
+ discount?: object | null;
8
+ metadata?: object | null;
9
+ extension_data?: unknown;
10
+ balance?: unknown;
11
+ expire_time?: unknown;
12
+ };
13
+ export type EntitlementQuota = {
14
+ type: 'limited';
15
+ value: number;
16
+ balance: number;
17
+ } | {
18
+ type: 'unlimited';
19
+ value: null;
20
+ balance: null;
21
+ };
22
+ export declare function isEntitlementPass(discount: DiscountLike | null | undefined): boolean;
23
+ export declare function isFullCoveragePass(discount: DiscountLike | null | undefined): boolean;
24
+ /**
25
+ * Resolve the authoritative total-use contract for an Entitlement Pass.
26
+ * Invalid or missing configuration fails closed. Legacy mode/total_uses fields
27
+ * are intentionally unsupported.
28
+ */
29
+ export declare function resolveEntitlementQuota(discount: DiscountLike | null | undefined): EntitlementQuota | null;
30
+ export declare function getRemainingEntitlementUses(discount: DiscountLike | null | undefined, usedCount: number, requestedCount: number): number;
31
+ export declare function hasAvailableEntitlementUses(discount: DiscountLike | null | undefined): boolean;
32
+ /** Sort Entitlement Passes by expiry, finite balance, then card instance id. */
33
+ export declare function compareEntitlementPassPriority(left: DiscountLike, right: DiscountLike): number;
34
+ export {};
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.GOOD_PASS_TYPE = exports.ENTITLEMENT_PASS_TYPE = void 0;
7
+ exports.compareEntitlementPassPriority = compareEntitlementPassPriority;
8
+ exports.getRemainingEntitlementUses = getRemainingEntitlementUses;
9
+ exports.hasAvailableEntitlementUses = hasAvailableEntitlementUses;
10
+ exports.isEntitlementPass = isEntitlementPass;
11
+ exports.isFullCoveragePass = isFullCoveragePass;
12
+ exports.resolveEntitlementQuota = resolveEntitlementQuota;
13
+ var _dayjs = _interopRequireDefault(require("dayjs"));
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+ const ENTITLEMENT_PASS_TYPE = exports.ENTITLEMENT_PASS_TYPE = 'entitlement_pass';
16
+ const GOOD_PASS_TYPE = exports.GOOD_PASS_TYPE = 'good_pass';
17
+ function getDiscountType(discount) {
18
+ const nestedDiscount = discount?.discount;
19
+ return String(discount?.tag || discount?.type || nestedDiscount?.tag || nestedDiscount?.type || '');
20
+ }
21
+ function isEntitlementPass(discount) {
22
+ return getDiscountType(discount) === ENTITLEMENT_PASS_TYPE;
23
+ }
24
+ function isFullCoveragePass(discount) {
25
+ const type = getDiscountType(discount);
26
+ return type === GOOD_PASS_TYPE || type === ENTITLEMENT_PASS_TYPE;
27
+ }
28
+ function resolveIncludedUses(discount) {
29
+ const metadata = discount.metadata;
30
+ const hasMetadataIncludedUses = !!metadata && typeof metadata === 'object' && Object.prototype.hasOwnProperty.call(metadata, 'included_uses');
31
+ const rawValue = hasMetadataIncludedUses ? metadata?.included_uses : Array.isArray(discount.extension_data) ? discount.extension_data.find(item => item?.field_key === 'included_uses')?.value : undefined;
32
+ if (!rawValue || typeof rawValue !== 'object' || Array.isArray(rawValue)) {
33
+ return null;
34
+ }
35
+ const includedUses = rawValue;
36
+ if (includedUses.type === 'unlimited' && includedUses.value === null) {
37
+ return {
38
+ type: 'unlimited',
39
+ value: null
40
+ };
41
+ }
42
+ if (includedUses.type === 'limited' && typeof includedUses.value === 'number' && Number.isInteger(includedUses.value) && includedUses.value >= 1 && includedUses.value <= 9999) {
43
+ return {
44
+ type: 'limited',
45
+ value: includedUses.value
46
+ };
47
+ }
48
+ return null;
49
+ }
50
+ function resolveNonNegativeInteger(value) {
51
+ if (typeof value !== 'number' && typeof value !== 'string' || typeof value === 'string' && value.trim() === '') {
52
+ return null;
53
+ }
54
+ const normalizedValue = Number(value);
55
+ if (!Number.isInteger(normalizedValue) || normalizedValue < 0) return null;
56
+ return normalizedValue;
57
+ }
58
+
59
+ /**
60
+ * Resolve the authoritative total-use contract for an Entitlement Pass.
61
+ * Invalid or missing configuration fails closed. Legacy mode/total_uses fields
62
+ * are intentionally unsupported.
63
+ */
64
+ function resolveEntitlementQuota(discount) {
65
+ if (!isEntitlementPass(discount)) return null;
66
+ const includedUses = resolveIncludedUses(discount);
67
+ if (!includedUses) return null;
68
+ if (includedUses.type === 'unlimited') {
69
+ return {
70
+ type: 'unlimited',
71
+ value: null,
72
+ balance: null
73
+ };
74
+ }
75
+ const balance = resolveNonNegativeInteger(discount?.balance);
76
+ if (balance === null) return null;
77
+ return {
78
+ type: 'limited',
79
+ value: includedUses.value,
80
+ balance
81
+ };
82
+ }
83
+ function getRemainingEntitlementUses(discount, usedCount, requestedCount) {
84
+ const quota = resolveEntitlementQuota(discount);
85
+ const normalizedRequestedCount = Math.max(Math.floor(Number(requestedCount) || 0), 0);
86
+ if (!quota || normalizedRequestedCount <= 0) return 0;
87
+ if (quota.type === 'unlimited') return normalizedRequestedCount;
88
+ const normalizedUsedCount = Math.max(Math.floor(Number(usedCount) || 0), 0);
89
+ return Math.min(Math.max(quota.balance - normalizedUsedCount, 0), normalizedRequestedCount);
90
+ }
91
+ function hasAvailableEntitlementUses(discount) {
92
+ const quota = resolveEntitlementQuota(discount);
93
+ return !!quota && (quota.type === 'unlimited' || quota.balance > 0);
94
+ }
95
+ function resolveExpireTimestamp(value) {
96
+ if (typeof value !== 'string' || !value.trim()) return Number.POSITIVE_INFINITY;
97
+ const normalizedValue = value.trim();
98
+ const calendarParts = normalizedValue.match(/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2}))?)?/);
99
+ if (calendarParts) {
100
+ const [, year, month, day, hour = '0', minute = '0', second = '0'] = calendarParts;
101
+ const yearNumber = Number(year);
102
+ const monthNumber = Number(month);
103
+ const dayNumber = Number(day);
104
+ const hourNumber = Number(hour);
105
+ const minuteNumber = Number(minute);
106
+ const secondNumber = Number(second);
107
+ const daysInMonth = new Date(Date.UTC(yearNumber, monthNumber, 0)).getUTCDate();
108
+ if (yearNumber <= 0 || monthNumber < 1 || monthNumber > 12 || dayNumber < 1 || dayNumber > daysInMonth || hourNumber < 0 || hourNumber > 23 || minuteNumber < 0 || minuteNumber > 59 || secondNumber < 0 || secondNumber > 59) {
109
+ return Number.POSITIVE_INFINITY;
110
+ }
111
+ }
112
+ const date = (0, _dayjs.default)(normalizedValue);
113
+ return date.isValid() ? date.valueOf() : Number.POSITIVE_INFINITY;
114
+ }
115
+ function compareIds(left, right) {
116
+ const leftNumber = Number(left);
117
+ const rightNumber = Number(right);
118
+ if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
119
+ return leftNumber - rightNumber;
120
+ }
121
+ return String(left ?? '').localeCompare(String(right ?? ''), undefined, {
122
+ numeric: true
123
+ });
124
+ }
125
+
126
+ /** Sort Entitlement Passes by expiry, finite balance, then card instance id. */
127
+ function compareEntitlementPassPriority(left, right) {
128
+ const expireDifference = resolveExpireTimestamp(left.expire_time) - resolveExpireTimestamp(right.expire_time);
129
+ if (!Number.isNaN(expireDifference) && expireDifference !== 0) {
130
+ return expireDifference;
131
+ }
132
+ const leftQuota = resolveEntitlementQuota(left);
133
+ const rightQuota = resolveEntitlementQuota(right);
134
+ const leftBalance = leftQuota?.type === 'limited' ? leftQuota.balance : Number.POSITIVE_INFINITY;
135
+ const rightBalance = rightQuota?.type === 'limited' ? rightQuota.balance : Number.POSITIVE_INFINITY;
136
+ const balanceDifference = leftBalance - rightBalance;
137
+ if (!Number.isNaN(balanceDifference) && balanceDifference !== 0) {
138
+ return balanceDifference;
139
+ }
140
+ return compareIds(left.id, right.id);
141
+ }
@@ -25,6 +25,8 @@ export declare class DiscountModule extends BaseModule implements Module, Discou
25
25
  with_wallet_pass_holder: 0 | 1;
26
26
  with_available_wallet_flag?: 0 | 1;
27
27
  with_available_wallet_pass_flag?: 0 | 1;
28
+ with_entitlement_pass?: 0 | 1;
29
+ filter_order_wallet_pass_usage?: 0 | 1;
28
30
  with_virtual_currency?: 0 | 1;
29
31
  request_timezone: string;
30
32
  order_id?: number | string;
@@ -36,9 +38,7 @@ export declare class DiscountModule extends BaseModule implements Module, Discou
36
38
  }): Promise<DiscountWithReason>;
37
39
  batchSearchByProductIds(productIds: number[]): Promise<any>;
38
40
  filterEnabledDiscountList(discountList: Discount[]): Discount[];
39
- private checkUsageCreditsLimit;
40
41
  filterEnabledDiscountListWithReason(discountList: Discount[]): DiscountWithReason;
41
- private checkUsageCreditsLimitWithFailKey;
42
42
  uniqueByProductId(discountList: Discount[]): Discount[];
43
43
  filterDiscountListByProductIds(discountList: Discount[], productIds: number[]): Discount[];
44
44
  calcDiscountApplicableProductTotalPrice(discount: Discount): number | undefined;
@@ -8,6 +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 _availability = require("./availability");
11
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
12
13
  class DiscountModule extends _BaseModule.BaseModule {
13
14
  defaultName = 'discount';
@@ -83,7 +84,13 @@ class DiscountModule extends _BaseModule.BaseModule {
83
84
  with_available_wallet_pass_flag: params.with_available_wallet_pass_flag ?? 1,
84
85
  with_virtual_currency: params.with_virtual_currency ?? 1
85
86
  });
86
- const rawDiscountList = [...(prepareConfig?.data?.good_pass_list || []), ...(prepareConfig?.data?.discount_card_list || [])];
87
+ const entitlementPassList = prepareConfig?.data?.entitlement_pass_list || [];
88
+ const entitlementPassIds = new Set(entitlementPassList.map(discount => discount?.id).filter(id => id !== undefined && id !== null).map(id => String(id)));
89
+ const excludeEntitlementDuplicates = discount => {
90
+ const id = discount?.id;
91
+ return id === undefined || id === null || !entitlementPassIds.has(String(id));
92
+ };
93
+ const rawDiscountList = [...entitlementPassList, ...(prepareConfig?.data?.good_pass_list || []).filter(excludeEntitlementDuplicates), ...(prepareConfig?.data?.discount_card_list || []).filter(excludeEntitlementDuplicates)];
87
94
  const availableWalletList = [...(prepareConfig?.data?.avaiable_wallet_list || []), ...(prepareConfig?.data?.available_wallet_list || [])];
88
95
  const availableWalletPassList = prepareConfig?.data?.available_wallet_pass_list || [];
89
96
  const availableWalletIds = this.normalizePositiveNumberIds([...availableWalletList.map(item => item?.wallet_id), ...availableWalletPassList.map(item => item?.product_id)]);
@@ -135,7 +142,7 @@ class DiscountModule extends _BaseModule.BaseModule {
135
142
  const result = await this.request.get(`/machinecode/batch-search`, {
136
143
  code,
137
144
  translate_flag: 1,
138
- tags: ['good_pass', 'product_discount_card'],
145
+ tags: ['entitlement_pass', 'good_pass', 'product_discount_card'],
139
146
  // available: 1,
140
147
  relation_product: 1,
141
148
  with: ['extensionData', 'customScheduleSnapshot', 'holder.detail'],
@@ -154,143 +161,30 @@ class DiscountModule extends _BaseModule.BaseModule {
154
161
  const result = await this.request.get(`/machinecode/batch-search`, {
155
162
  ids: productIds,
156
163
  translate_flag: 1,
157
- tags: ['good_pass', 'product_discount_card'],
158
- relation_product: 1
164
+ tags: ['entitlement_pass', 'good_pass', 'product_discount_card'],
165
+ relation_product: 1,
166
+ with: ['extensionData', 'customScheduleSnapshot', 'holder.detail'],
167
+ unified_status_info_flag: 1,
168
+ request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
159
169
  });
160
170
  return result?.data || [];
161
171
  }
162
- filterEnabledDiscountList(discountList) {
163
- return discountList.filter(discount => discount.limit_status === 'enable' && new _decimal.default(discount?.par_value || 0).minus(new _decimal.default(discount?.used_par_value || 0)).greaterThan(0) && this.checkUsageCreditsLimit(discount));
164
- }
165
-
166
- // 检查使用次数限制
167
- checkUsageCreditsLimit(discount) {
168
- // 如果没有 extension_data,则不需要限制
169
- if (!discount.extension_data || discount.extension_data.length === 0) {
170
- return true;
171
- }
172
-
173
- // 查找 field_key 为 usage_credits 的数据
174
- const usageCreditsData = discount.extension_data.find(data => data.field_key === 'usage_credits');
175
-
176
- // 如果没有找到 usage_credits 数据,则不需要限制
177
- if (!usageCreditsData) {
178
- return true;
179
- }
180
- const value = usageCreditsData.value;
181
-
182
- // 检查总次数限制
183
- if (value.total_credits && value.total_credits > 0) {
184
- if ((discount.total_order_behavior_count || 0) >= value.total_credits) {
185
- return false;
186
- }
187
- }
188
172
 
189
- // 检查单用户限制
190
- if (value.per_user_limit && value.per_user_limit > 0) {
191
- if ((discount.customer_order_behavior_count || 0) >= value.per_user_limit) {
192
- return false;
193
- }
194
- }
195
-
196
- // 检查单日限制
197
- if (value.max_per_day && value.max_per_day > 0) {
198
- if ((discount.today_order_behavior_count || 0) >= value.max_per_day) {
199
- return false;
200
- }
201
- }
202
-
203
- // 检查单周限制
204
- if (value.max_per_week && value.max_per_week > 0) {
205
- if ((discount.week_order_behavior_count || 0) >= value.max_per_week) {
206
- return false;
207
- }
208
- }
209
-
210
- // 检查单月限制
211
- if (value.max_per_month && value.max_per_month > 0) {
212
- if ((discount.month_order_behavior_count || 0) >= value.max_per_month) {
213
- return false;
214
- }
215
- }
216
-
217
- // 所有检查都通过
218
- return true;
173
+ // 展示列表只剔除已失效项,当前不可用项保留并携带原因。
174
+ filterEnabledDiscountList(discountList) {
175
+ return discountList.filter(discount => discount.limit_status === 'enable' && (0, _availability.hasAvailableDiscountBalance)(discount) && !(0, _availability.getDiscountLifecycleReason)(discount)).map(discount => ({
176
+ ...discount,
177
+ usageLimitFailKey: (0, _availability.getDiscountUsageLimitFailKey)(discount),
178
+ reasonCode: (0, _availability.getDiscountUsageBlockReason)(discount)
179
+ }));
219
180
  }
220
-
221
- // 过滤启用且可用的优惠券,并返回校验失败原因
222
181
  filterEnabledDiscountListWithReason(discountList) {
223
- let lists = discountList.filter(discount => discount.limit_status === 'enable').filter(discount => {
224
- const remaining = new _decimal.default(discount?.par_value || 0).minus(new _decimal.default(discount?.used_par_value || 0)).greaterThan(0);
225
- return remaining;
226
- }).map(discount => {
227
- const limitFailKey = this.checkUsageCreditsLimitWithFailKey(discount);
228
- return {
229
- ...discount,
230
- usageLimitFailKey: limitFailKey
231
- };
232
- });
233
-
234
- // 可用的列表
235
- const availableLists = lists.filter(item => !item.usageLimitFailKey && item.unified_available_status !== 0);
236
- // 不可用数据
237
- const currentFailDiscount = lists.find(item => item.usageLimitFailKey);
238
182
  return {
239
- discountList: availableLists,
240
- unavailableReasonKey: currentFailDiscount?.usageLimitFailKey
183
+ discountList: this.filterEnabledDiscountList(discountList),
184
+ unavailableReasonKey: discountList.map(_availability.getDiscountUsageLimitFailKey).find(key => key !== null) || null
241
185
  };
242
186
  }
243
187
 
244
- // 检查使用次数限制,并返回校验失败原因
245
- checkUsageCreditsLimitWithFailKey(discount) {
246
- if (!discount.extension_data || discount.extension_data.length === 0) {
247
- return null;
248
- }
249
- const usageCreditsData = discount.extension_data.find(data => data.field_key === 'usage_credits');
250
- if (!usageCreditsData) {
251
- return null;
252
- }
253
- const value = usageCreditsData.value;
254
-
255
- // 总次数限制
256
- if (value.total_credits && value.total_credits > 0) {
257
- if ((discount.total_order_behavior_count || 0) >= value.total_credits) {
258
- return 'total_credits';
259
- }
260
- }
261
-
262
- // 单用户限制
263
- if (value.per_user_limit && value.per_user_limit > 0) {
264
- if ((discount.customer_order_behavior_count || 0) >= value.per_user_limit) {
265
- return 'per_user_limit';
266
- }
267
- }
268
-
269
- // 单日限制
270
- if (value.max_per_day && value.max_per_day > 0) {
271
- if ((discount.today_order_behavior_count || 0) >= value.max_per_day) {
272
- return 'max_per_day';
273
- }
274
- }
275
-
276
- // 单周限制
277
- if (value.max_per_week && value.max_per_week > 0) {
278
- if ((discount.week_order_behavior_count || 0) >= value.max_per_week) {
279
- return 'max_per_week';
280
- }
281
- }
282
-
283
- // 单月限制
284
- if (value.max_per_month && value.max_per_month > 0) {
285
- if ((discount.month_order_behavior_count || 0) >= value.max_per_month) {
286
- return 'max_per_month';
287
- }
288
- }
289
-
290
- // 全部通过
291
- return null;
292
- }
293
-
294
188
  // 根据productIds去重
295
189
  uniqueByProductId(discountList) {
296
190
  return (0, _utils.uniqueById)(discountList, 'product_id');