@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.
- package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
- package/dist/modules/BookingContext/index.js +4 -4
- package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +58 -19
- package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +24 -12
- package/dist/modules/Customer/index.js +6 -3
- package/dist/modules/Customer/types.d.ts +2 -0
- package/dist/modules/Discount/availability.d.ts +7 -0
- package/dist/modules/Discount/availability.js +49 -0
- package/dist/modules/Discount/entitlementPass.d.ts +34 -0
- package/dist/modules/Discount/entitlementPass.js +147 -0
- package/dist/modules/Discount/index.d.ts +2 -2
- package/dist/modules/Discount/index.js +37 -151
- package/dist/modules/Discount/types.d.ts +36 -5
- package/dist/modules/OpenData/index.d.ts +1 -1
- package/dist/modules/OpenData/index.js +21 -12
- package/dist/modules/Order/index.d.ts +32 -4
- package/dist/modules/Order/index.js +733 -373
- package/dist/modules/Order/types.d.ts +17 -0
- package/dist/modules/Order/utils/orderCollectionIdentity.js +16 -12
- package/dist/modules/Order/utils.d.ts +14 -2
- package/dist/modules/Order/utils.js +275 -76
- package/dist/modules/Payment/index.d.ts +0 -5
- package/dist/modules/Payment/index.js +35 -85
- package/dist/modules/Payment/types.d.ts +21 -1
- package/dist/modules/Payment/types.js +2 -0
- package/dist/modules/Payment/walletpass.d.ts +1 -1
- package/dist/modules/Payment/walletpass.js +80 -47
- package/dist/modules/ProductList/index.js +18 -5
- package/dist/modules/Rules/index.d.ts +2 -2
- package/dist/modules/Rules/index.js +480 -186
- package/dist/modules/Rules/types.d.ts +4 -0
- package/dist/server/index.js +8 -5
- package/dist/server/modules/order/index.js +6 -0
- package/dist/server/modules/order/types.d.ts +12 -0
- package/dist/server/modules/payment/index.d.ts +25 -6
- package/dist/server/modules/payment/index.js +660 -133
- package/dist/server/utils/small-ticket.d.ts +8 -5
- package/dist/server/utils/small-ticket.js +1269 -199
- package/dist/solution/BaseSales/index.d.ts +11 -2
- package/dist/solution/BaseSales/index.js +1463 -1173
- package/dist/solution/BaseSales/types.d.ts +7 -3
- package/dist/solution/BaseSales/utils/cartPromotion.js +141 -126
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +12 -3
- package/dist/solution/BookingTicket/index.js +663 -542
- package/dist/solution/BookingTicket/types.d.ts +5 -1
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
- package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +338 -147
- package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.js +19 -6
- package/dist/solution/ShopDiscount/index.d.ts +2 -0
- package/dist/solution/ShopDiscount/index.js +99 -42
- package/dist/solution/ShopDiscount/utils.d.ts +4 -2
- package/dist/solution/ShopDiscount/utils.js +43 -23
- package/dist/solution/UnifiedBookingSales/index.d.ts +2 -2
- package/dist/solution/UnifiedBookingSales/index.js +5 -5
- package/dist/utils/os-private-payload.d.ts +5 -0
- package/dist/utils/os-private-payload.js +17 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
- package/lib/modules/BookingContext/index.js +3 -3
- package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +35 -6
- package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +13 -6
- package/lib/modules/Customer/index.js +5 -1
- package/lib/modules/Customer/types.d.ts +2 -0
- package/lib/modules/Discount/availability.d.ts +7 -0
- package/lib/modules/Discount/availability.js +43 -0
- package/lib/modules/Discount/entitlementPass.d.ts +34 -0
- package/lib/modules/Discount/entitlementPass.js +141 -0
- package/lib/modules/Discount/index.d.ts +2 -2
- package/lib/modules/Discount/index.js +23 -129
- package/lib/modules/Discount/types.d.ts +36 -5
- package/lib/modules/OpenData/index.d.ts +1 -1
- package/lib/modules/OpenData/index.js +11 -2
- package/lib/modules/Order/index.d.ts +32 -4
- package/lib/modules/Order/index.js +341 -71
- package/lib/modules/Order/types.d.ts +17 -0
- package/lib/modules/Order/utils/orderCollectionIdentity.js +14 -11
- package/lib/modules/Order/utils.d.ts +14 -2
- package/lib/modules/Order/utils.js +186 -3
- package/lib/modules/Payment/index.d.ts +0 -5
- package/lib/modules/Payment/index.js +3 -28
- package/lib/modules/Payment/types.d.ts +21 -1
- package/lib/modules/Payment/types.js +1 -0
- package/lib/modules/Payment/walletpass.d.ts +1 -1
- package/lib/modules/Payment/walletpass.js +29 -9
- package/lib/modules/ProductList/index.js +14 -1
- package/lib/modules/Rules/index.d.ts +2 -2
- package/lib/modules/Rules/index.js +357 -110
- package/lib/modules/Rules/types.d.ts +4 -0
- package/lib/server/index.js +8 -5
- package/lib/server/modules/order/index.js +6 -0
- package/lib/server/modules/order/types.d.ts +12 -0
- package/lib/server/modules/payment/index.d.ts +25 -6
- package/lib/server/modules/payment/index.js +350 -78
- package/lib/server/utils/small-ticket.d.ts +8 -5
- package/lib/server/utils/small-ticket.js +1009 -121
- package/lib/solution/BaseSales/index.d.ts +11 -2
- package/lib/solution/BaseSales/index.js +164 -30
- package/lib/solution/BaseSales/types.d.ts +7 -3
- package/lib/solution/BaseSales/utils/cartPromotion.js +12 -4
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +12 -3
- package/lib/solution/BookingTicket/index.js +103 -41
- package/lib/solution/BookingTicket/types.d.ts +5 -1
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.d.ts +2 -0
- package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +174 -42
- package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +8 -4
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +6 -0
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.js +11 -3
- package/lib/solution/ShopDiscount/index.d.ts +2 -0
- package/lib/solution/ShopDiscount/index.js +55 -9
- package/lib/solution/ShopDiscount/utils.d.ts +4 -2
- package/lib/solution/ShopDiscount/utils.js +42 -25
- package/lib/solution/UnifiedBookingSales/index.d.ts +2 -2
- package/lib/solution/UnifiedBookingSales/index.js +5 -5
- package/lib/utils/os-private-payload.d.ts +5 -0
- package/lib/utils/os-private-payload.js +22 -0
- 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
|
|
120
|
-
|
|
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 :
|
|
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
|
|
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
|
-
...(
|
|
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: {
|
|
@@ -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
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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:
|
|
240
|
-
unavailableReasonKey:
|
|
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');
|