@pisell/pisellos 0.0.571 → 0.0.572
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/effects/index.d.ts +2 -2
- package/dist/model/strategy/adapter/dataVariant/type.d.ts +3 -3
- package/dist/model/strategy/adapter/itemRule/type.d.ts +6 -6
- package/dist/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/dist/model/strategy/adapter/promotion/type.d.ts +5 -5
- package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
- package/dist/model/strategy/type.d.ts +1 -1
- package/dist/modules/BookingContext/types.d.ts +4 -4
- package/dist/modules/BookingContext/utils/resourceErrors.d.ts +1 -1
- package/dist/modules/Customer/types.d.ts +1 -1
- package/dist/modules/Discount/availability.d.ts +7 -0
- package/dist/modules/Discount/availability.js +52 -0
- package/dist/modules/Discount/entitlementPass.d.ts +34 -0
- package/dist/modules/Discount/entitlementPass.js +147 -0
- package/dist/modules/Discount/entitlementState.d.ts +8 -0
- package/dist/modules/Discount/entitlementState.js +205 -0
- package/dist/modules/Discount/index.d.ts +5 -2
- package/dist/modules/Discount/index.js +124 -25
- package/dist/modules/Discount/types.d.ts +29 -6
- package/dist/modules/Holder/types.d.ts +1 -1
- package/dist/modules/OpenData/types.d.ts +1 -1
- package/dist/modules/Order/types.d.ts +5 -5
- package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +1 -1
- package/dist/modules/Order/utils/orderCollectionIdentity.d.ts +1 -1
- package/dist/modules/Order/utils.d.ts +1 -1
- package/dist/modules/Payment/types.d.ts +7 -7
- package/dist/modules/Product/index.d.ts +1 -1
- package/dist/modules/ResourcePlanner/types.d.ts +4 -4
- package/dist/modules/Rules/entitlementLines.d.ts +8 -0
- package/dist/modules/Rules/entitlementLines.js +159 -0
- package/dist/modules/Rules/index.d.ts +13 -3
- package/dist/modules/Rules/index.js +617 -342
- package/dist/modules/Rules/types.d.ts +4 -4
- package/dist/modules/ScanOrderLogger/types.d.ts +2 -2
- package/dist/modules/Schedule/getDateIsInSchedule.d.ts +1 -1
- package/dist/modules/Schedule/type.d.ts +7 -7
- package/dist/modules/Schedule/types.d.ts +9 -9
- package/dist/plugins/app.d.ts +1 -1
- package/dist/plugins/request.d.ts +2 -2
- package/dist/plugins/window.d.ts +2 -2
- package/dist/server/modules/floor-plan/types.d.ts +1 -1
- package/dist/server/modules/menu/types.d.ts +1 -1
- package/dist/server/modules/order/types.d.ts +16 -16
- package/dist/server/modules/products/types.d.ts +1 -1
- package/dist/server/modules/resource/types.d.ts +1 -1
- package/dist/server/modules/schedule/utils.d.ts +1 -1
- package/dist/server/types.d.ts +4 -4
- package/dist/server/utils/small-ticket.d.ts +1 -1
- package/dist/solution/BaseSales/index.d.ts +1 -1
- package/dist/solution/BaseSales/types.d.ts +7 -7
- package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -1
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingByStep/utils/resources.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +13 -1
- package/dist/solution/BookingTicket/types.d.ts +3 -3
- package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +1 -1
- package/dist/solution/BookingTicket/utils/bookingStatus.d.ts +1 -1
- package/dist/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +1 -1
- package/dist/solution/Sales/types.d.ts +4 -4
- package/dist/solution/ScanOrder/types.d.ts +5 -5
- package/dist/solution/ShopDiscount/index.d.ts +3 -0
- package/dist/solution/ShopDiscount/index.js +161 -78
- package/dist/solution/ShopDiscount/types.d.ts +1 -1
- package/dist/solution/ShopDiscount/utils.d.ts +3 -2
- package/dist/solution/ShopDiscount/utils.js +40 -1
- package/dist/solution/UnifiedBookingSales/types.d.ts +1 -1
- package/dist/solution/VenueBooking/types.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/utils/task.d.ts +2 -2
- package/dist/utils/watch.d.ts +2 -2
- package/lib/effects/index.d.ts +2 -2
- package/lib/model/strategy/adapter/dataVariant/type.d.ts +3 -3
- package/lib/model/strategy/adapter/itemRule/type.d.ts +6 -6
- package/lib/model/strategy/adapter/promotion/index.d.ts +4 -0
- package/lib/model/strategy/adapter/promotion/type.d.ts +5 -5
- package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
- package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
- package/lib/model/strategy/type.d.ts +1 -1
- package/lib/modules/BookingContext/types.d.ts +4 -4
- package/lib/modules/BookingContext/utils/resourceErrors.d.ts +1 -1
- package/lib/modules/Customer/types.d.ts +1 -1
- package/lib/modules/Discount/availability.d.ts +7 -0
- package/lib/modules/Discount/availability.js +46 -0
- package/lib/modules/Discount/entitlementPass.d.ts +34 -0
- package/lib/modules/Discount/entitlementPass.js +141 -0
- package/lib/modules/Discount/entitlementState.d.ts +8 -0
- package/lib/modules/Discount/entitlementState.js +150 -0
- package/lib/modules/Discount/index.d.ts +5 -2
- package/lib/modules/Discount/index.js +59 -14
- package/lib/modules/Discount/types.d.ts +29 -6
- package/lib/modules/Holder/types.d.ts +1 -1
- package/lib/modules/OpenData/types.d.ts +1 -1
- package/lib/modules/Order/types.d.ts +5 -5
- package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +1 -1
- package/lib/modules/Order/utils/orderCollectionIdentity.d.ts +1 -1
- package/lib/modules/Order/utils.d.ts +1 -1
- package/lib/modules/Payment/types.d.ts +7 -7
- package/lib/modules/Product/index.d.ts +1 -1
- package/lib/modules/ResourcePlanner/types.d.ts +4 -4
- package/lib/modules/Rules/entitlementLines.d.ts +8 -0
- package/lib/modules/Rules/entitlementLines.js +128 -0
- package/lib/modules/Rules/index.d.ts +13 -3
- package/lib/modules/Rules/index.js +284 -61
- package/lib/modules/Rules/types.d.ts +4 -4
- package/lib/modules/ScanOrderLogger/types.d.ts +2 -2
- package/lib/modules/Schedule/getDateIsInSchedule.d.ts +1 -1
- package/lib/modules/Schedule/type.d.ts +7 -7
- package/lib/modules/Schedule/types.d.ts +9 -9
- package/lib/plugins/app.d.ts +1 -1
- package/lib/plugins/request.d.ts +2 -2
- package/lib/plugins/window.d.ts +2 -2
- package/lib/server/modules/floor-plan/types.d.ts +1 -1
- package/lib/server/modules/menu/types.d.ts +1 -1
- package/lib/server/modules/order/types.d.ts +16 -16
- package/lib/server/modules/products/types.d.ts +1 -1
- package/lib/server/modules/resource/types.d.ts +1 -1
- package/lib/server/modules/schedule/utils.d.ts +1 -1
- package/lib/server/types.d.ts +4 -4
- package/lib/server/utils/small-ticket.d.ts +1 -1
- package/lib/solution/BaseSales/index.d.ts +1 -1
- package/lib/solution/BaseSales/types.d.ts +7 -7
- package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -1
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingByStep/utils/resources.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +13 -1
- package/lib/solution/BookingTicket/types.d.ts +3 -3
- package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +1 -1
- package/lib/solution/BookingTicket/utils/bookingStatus.d.ts +1 -1
- package/lib/solution/BookingTicket/utils/scan/formatGlobalScan.d.ts +1 -1
- package/lib/solution/Sales/types.d.ts +4 -4
- package/lib/solution/ScanOrder/types.d.ts +5 -5
- package/lib/solution/ShopDiscount/index.d.ts +3 -0
- package/lib/solution/ShopDiscount/index.js +55 -18
- package/lib/solution/ShopDiscount/types.d.ts +1 -1
- package/lib/solution/ShopDiscount/utils.d.ts +3 -2
- package/lib/solution/ShopDiscount/utils.js +39 -3
- package/lib/solution/UnifiedBookingSales/types.d.ts +1 -1
- package/lib/solution/VenueBooking/types.d.ts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/utils/task.d.ts +2 -2
- package/lib/utils/watch.d.ts +2 -2
- package/package.json +19 -18
|
@@ -8,7 +8,7 @@ import type { ScheduleItem } from '../../../../modules/Schedule/types';
|
|
|
8
8
|
export declare const DATA_VARIANT_ACTION_TYPES: {
|
|
9
9
|
readonly APPLY: "DATA_VARIANT_APPLY";
|
|
10
10
|
};
|
|
11
|
-
export
|
|
11
|
+
export type DataVariantActionType = (typeof DATA_VARIANT_ACTION_TYPES)[keyof typeof DATA_VARIANT_ACTION_TYPES];
|
|
12
12
|
/**
|
|
13
13
|
* Data Variant 规则元数据。
|
|
14
14
|
*
|
|
@@ -116,7 +116,7 @@ export interface EffectiveRuleInfo {
|
|
|
116
116
|
productRules: ConditionRule[];
|
|
117
117
|
globalResult: EvaluationResult;
|
|
118
118
|
}
|
|
119
|
-
export
|
|
119
|
+
export type GlobalEffectiveRuleMap = Map<string, EffectiveRuleInfo>;
|
|
120
120
|
/**
|
|
121
121
|
* 单条商品命中的变体覆盖信息。
|
|
122
122
|
*/
|
|
@@ -145,4 +145,4 @@ export interface ResolvedDataVariantResult {
|
|
|
145
145
|
scheduleTimePoints: string[];
|
|
146
146
|
rawResults: EvaluationResult[];
|
|
147
147
|
}
|
|
148
|
-
export
|
|
148
|
+
export type ConditionScope = 'global' | 'product';
|
|
@@ -5,7 +5,7 @@ export declare const ITEM_RULE_ACTION_TYPES: {
|
|
|
5
5
|
/** 预填购物车 */
|
|
6
6
|
readonly PREFILL_CART: "PREFILL_CART";
|
|
7
7
|
};
|
|
8
|
-
export
|
|
8
|
+
export type ItemRuleActionType = (typeof ITEM_RULE_ACTION_TYPES)[keyof typeof ITEM_RULE_ACTION_TYPES];
|
|
9
9
|
export declare const ITEM_RULE_TEMPLATES: {
|
|
10
10
|
/** 空白规则 */
|
|
11
11
|
readonly BLANK: "blank";
|
|
@@ -16,15 +16,15 @@ export declare const ITEM_RULE_TEMPLATES: {
|
|
|
16
16
|
/** 必选商品 */
|
|
17
17
|
readonly MUST_INCLUDE: "must_include";
|
|
18
18
|
};
|
|
19
|
-
export
|
|
19
|
+
export type ItemRuleTemplate = (typeof ITEM_RULE_TEMPLATES)[keyof typeof ITEM_RULE_TEMPLATES];
|
|
20
20
|
/** 数量比较类型 */
|
|
21
|
-
export
|
|
21
|
+
export type QuantityComparison = 'minimum' | 'maximum' | 'exact' | 'must_include';
|
|
22
22
|
/** 数量来源 */
|
|
23
|
-
export
|
|
23
|
+
export type QuantitySource = 'total_pax' | 'adult_pax' | 'fixed';
|
|
24
24
|
/** 目标类型(当前仅支持按商品) */
|
|
25
|
-
export
|
|
25
|
+
export type TargetType = 'product';
|
|
26
26
|
/** 作用范围 */
|
|
27
|
-
export
|
|
27
|
+
export type RuleScope = 'each_submission' | 'first_submission_only' | 'subsequent_only' | 'cumulative';
|
|
28
28
|
/**
|
|
29
29
|
* 单条数量规则
|
|
30
30
|
*
|
|
@@ -17,7 +17,7 @@ export declare const PROMOTION_ACTION_TYPES: {
|
|
|
17
17
|
/** 固定减价 */
|
|
18
18
|
readonly DEDUCT_AMOUNT: "DEDUCT_AMOUNT";
|
|
19
19
|
};
|
|
20
|
-
export
|
|
20
|
+
export type PromotionActionType = (typeof PROMOTION_ACTION_TYPES)[keyof typeof PROMOTION_ACTION_TYPES];
|
|
21
21
|
/**
|
|
22
22
|
* X件Y元 - Action Value
|
|
23
23
|
*
|
|
@@ -54,7 +54,7 @@ export interface BuyXGetYFreeValue {
|
|
|
54
54
|
/**
|
|
55
55
|
* 赠品选择模式
|
|
56
56
|
*/
|
|
57
|
-
export
|
|
57
|
+
export type GiftSelectionMode = 'user_select' | 'auto' | 'cheapest';
|
|
58
58
|
/**
|
|
59
59
|
* 买X送Y - Action Config
|
|
60
60
|
*/
|
|
@@ -105,7 +105,7 @@ export interface BuyXGetYFreeAction extends Omit<ActionEffect, 'type' | 'value'
|
|
|
105
105
|
/**
|
|
106
106
|
* 促销活动 Action 联合类型
|
|
107
107
|
*/
|
|
108
|
-
export
|
|
108
|
+
export type PromotionAction = XItemsForYPriceAction | BuyXGetYFreeAction;
|
|
109
109
|
/**
|
|
110
110
|
* 促销相关运算符
|
|
111
111
|
*/
|
|
@@ -135,7 +135,7 @@ export interface PromotionBusinessData {
|
|
|
135
135
|
/** 自定义属性 */
|
|
136
136
|
custom?: Record<string, any>;
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
type ProductBundleItem = {
|
|
139
139
|
/** 主商品ID */
|
|
140
140
|
product_id: number;
|
|
141
141
|
/** 变体ID(0 表示无变体) */
|
|
@@ -195,7 +195,7 @@ export interface PromotionTransformResult {
|
|
|
195
195
|
/**
|
|
196
196
|
* Action 详情联合类型
|
|
197
197
|
*/
|
|
198
|
-
export
|
|
198
|
+
export type PromotionActionDetail = XItemsForYPriceActionDetail | BuyXGetYFreeActionDetail;
|
|
199
199
|
/**
|
|
200
200
|
* X件Y元 Action 详情
|
|
201
201
|
*/
|
|
@@ -28,6 +28,8 @@ export declare class WalletPassEvaluator {
|
|
|
28
28
|
/** 检查代金券/折扣卡是否可用 并返回config */
|
|
29
29
|
checkVoucherAvailability(input: EvaluatorInput): {
|
|
30
30
|
isAvailable: boolean;
|
|
31
|
+
reason: string | undefined;
|
|
32
|
+
reasonCode: string | undefined;
|
|
31
33
|
config: {
|
|
32
34
|
[x: string]: any;
|
|
33
35
|
};
|
|
@@ -256,6 +256,8 @@ class WalletPassEvaluator {
|
|
|
256
256
|
const isAvailable = results[0]?.isApplicable || false;
|
|
257
257
|
return {
|
|
258
258
|
isAvailable,
|
|
259
|
+
reason: results[0]?.reason,
|
|
260
|
+
reasonCode: results[0]?.reasonCode,
|
|
259
261
|
config: {
|
|
260
262
|
...results[0]?.strategyResult?.config?.metadata?.custom
|
|
261
263
|
}
|
|
@@ -242,4 +242,4 @@ export interface EngineOptions {
|
|
|
242
242
|
/**
|
|
243
243
|
* 运算符处理器
|
|
244
244
|
*/
|
|
245
|
-
export
|
|
245
|
+
export type OperatorHandler = (fieldValue: any, compareValue: any, rule: ConditionRule) => boolean;
|
|
@@ -39,17 +39,17 @@ export interface InitBookingContextParams {
|
|
|
39
39
|
* 资源全集 map:key 为资源 id(数字),value 为资源原始对象,含 times / event_list 等运行态。
|
|
40
40
|
* 这里只声明 Record 形态;具体字段保留 `any`,与 ticketBooking 现网传入的数据形状兼容。
|
|
41
41
|
*/
|
|
42
|
-
export
|
|
43
|
-
export
|
|
42
|
+
export type BookingContextResource = Record<string, any>;
|
|
43
|
+
export type BookingContextResourceMap = Record<string, BookingContextResource>;
|
|
44
44
|
/**
|
|
45
45
|
* 形参 booking_config(结构来自 `/core/board/management/config` 接口)。
|
|
46
46
|
* OS 不强制 schema,保留 `Record<string, any>` 兼容现网;常用路径文档化在 SDK docs。
|
|
47
47
|
*/
|
|
48
|
-
export
|
|
48
|
+
export type BookingContextConfig = Record<string, any>;
|
|
49
49
|
/**
|
|
50
50
|
* 日期入参:接受 dayjs 实例、ISO 字符串、Date 实例;内部统一 normalize 为 ISO 字符串保存。
|
|
51
51
|
*/
|
|
52
|
-
export
|
|
52
|
+
export type BookingContextDateInput = string | Date | {
|
|
53
53
|
format?: (fmt?: string) => string;
|
|
54
54
|
} | null | undefined;
|
|
55
55
|
/**
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* OS 这边不耦合 i18n 文案,UI 拿到 reason + params 后自行渲染(参考 docs/SDK 中给出的 i18n key 对照表)。
|
|
11
11
|
*/
|
|
12
|
-
export
|
|
12
|
+
export type ResourceUnavailableReason = 'no_product' | 'time_out_of_range' | 'count_out_of_range' | 'resource_unbound';
|
|
13
13
|
export interface ResourceError {
|
|
14
14
|
reason: ResourceUnavailableReason;
|
|
15
15
|
params: {
|
|
@@ -33,7 +33,7 @@ export interface ShopCustomer extends ICustomer {
|
|
|
33
33
|
* walletDetails.wallet: 用户所有wallet
|
|
34
34
|
* latestWalletDetail.wallet: 用户最新wallet
|
|
35
35
|
*/
|
|
36
|
-
|
|
36
|
+
type CustomerWith = 'formRecord' | 'walletDetails.wallet' | 'latestWalletDetail.wallet';
|
|
37
37
|
/**
|
|
38
38
|
* 获取客户列表参数
|
|
39
39
|
*/
|
|
@@ -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' | 'not_shared_at_store' | undefined;
|
|
7
|
+
export declare function getDiscountUsageBlockReason(discount: Discount): string | undefined;
|
|
@@ -0,0 +1,46 @@
|
|
|
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.is_franchisor_pass === 1 && discount.franchisee_wallet_pass?.use !== 'enabled') {
|
|
29
|
+
return 'not_shared_at_store';
|
|
30
|
+
}
|
|
31
|
+
if (discount.limit_status && discount.limit_status !== 'enable') {
|
|
32
|
+
return 'disabled';
|
|
33
|
+
}
|
|
34
|
+
if (((0, _entitlementPass.isEntitlementPass)(discount) || discount.par_value != null) && !hasAvailableDiscountBalance(discount) || getDiscountUsageLimitFailKey(discount) === 'total_credits') {
|
|
35
|
+
return 'exhausted';
|
|
36
|
+
}
|
|
37
|
+
// 自定义日程沿用快照语义,不使用遗留固定有效期字段推断过期。
|
|
38
|
+
if (discount.metadata?.validity_type !== 'custom_schedule_validity' && discount.expire_time) {
|
|
39
|
+
const expiry = (0, _dayjs.default)(discount.expire_time);
|
|
40
|
+
if (expiry.isValid() && !(0, _dayjs.default)().isBefore(expiry)) return 'expired';
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function getDiscountUsageBlockReason(discount) {
|
|
45
|
+
return getDiscountLifecycleReason(discount) || getDiscountUsageLimitFailKey(discount) || (discount.unified_available_status != null && String(discount.unified_available_status) !== '' && Number(discount.unified_available_status) === 0 ? 'server_unavailable' : undefined);
|
|
46
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Discount } from './types';
|
|
2
|
+
export declare function hasEntitlementDiscount(product: any): boolean;
|
|
3
|
+
export declare function isPersistedEntitlementLine(product: any, mappedProduct?: any): boolean;
|
|
4
|
+
/** Refresh authoritative balances while retaining the user's scan/selection state. */
|
|
5
|
+
export declare function mergeEntitlementDiscountLists(current: Discount[], incoming: Discount[], observed?: Discount[]): Discount[];
|
|
6
|
+
export declare function retainScannedEntitlements(current: Discount[], scanned: Discount[]): Discount[];
|
|
7
|
+
/** Historical uses are display records; backend balance already excludes them. */
|
|
8
|
+
export declare function withCommittedEntitlementDetails(discounts: Discount[], products: any[], getProduct: (item: any) => any): Discount[];
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.hasEntitlementDiscount = hasEntitlementDiscount;
|
|
7
|
+
exports.isPersistedEntitlementLine = isPersistedEntitlementLine;
|
|
8
|
+
exports.mergeEntitlementDiscountLists = mergeEntitlementDiscountLists;
|
|
9
|
+
exports.retainScannedEntitlements = retainScannedEntitlements;
|
|
10
|
+
exports.withCommittedEntitlementDetails = withCommittedEntitlementDetails;
|
|
11
|
+
var _lodashEs = require("lodash-es");
|
|
12
|
+
var _decimal = _interopRequireDefault(require("decimal.js"));
|
|
13
|
+
var _availability = require("./availability");
|
|
14
|
+
var _entitlementPass = require("./entitlementPass");
|
|
15
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
16
|
+
function getEntitlementDiscountLines(product) {
|
|
17
|
+
const nested = product?._origin?.product ?? product?._originData?.product;
|
|
18
|
+
return [{
|
|
19
|
+
discount_list: product?.discount_list ?? nested?.discount_list ?? []
|
|
20
|
+
}, ...(product?.product_bundle ?? product?.bundle ?? product?._extend?.other?.bundle ?? nested?.product_bundle ?? [])];
|
|
21
|
+
}
|
|
22
|
+
function hasEntitlementDiscount(product) {
|
|
23
|
+
return getEntitlementDiscountLines(product).some(line => (line.discount_list || []).some(_entitlementPass.isEntitlementPass));
|
|
24
|
+
}
|
|
25
|
+
function isPersistedEntitlementLine(product, mappedProduct) {
|
|
26
|
+
const nested = product?._origin?.product ?? product?._originData?.product;
|
|
27
|
+
const persisted = [product?.order_detail_id, product?.booking_id, product?._origin?.booking_id, product?._originData?.booking_id, product?._extend?.order_detail_id, nested?.order_detail_id, nested?.booking_id, mappedProduct?.order_detail_id, mappedProduct?.booking_id].some(value => value !== undefined && value !== null && value !== '' && value !== 0 && value !== '0');
|
|
28
|
+
const lines = getEntitlementDiscountLines(mappedProduct ?? product);
|
|
29
|
+
// 已有预约上刚选的卡仍属本次使用,只有接口回传的明细才计入历史。
|
|
30
|
+
return lines.some(line => (line.discount_list || []).some(_entitlementPass.isEntitlementPass)) && (persisted && product?.metadata?.entitlement_source_identity == null || lines.some(line => (line.discount_list || []).some(item => (0, _entitlementPass.isEntitlementPass)(item) && item.id)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Refresh authoritative balances while retaining the user's scan/selection state. */
|
|
34
|
+
function mergeEntitlementDiscountLists(current, incoming, observed = incoming) {
|
|
35
|
+
const previous = new Map(current.map(item => [String(item.id), item]));
|
|
36
|
+
const observedById = new Map(observed.map(item => [String(item.id), item]));
|
|
37
|
+
const next = new Map();
|
|
38
|
+
current.filter(item => item.isScan).forEach(item => {
|
|
39
|
+
const fresh = observedById.get(String(item.id));
|
|
40
|
+
if ((0, _entitlementPass.isEntitlementPass)(item) && (0, _availability.getDiscountLifecycleReason)(fresh || item)) return;
|
|
41
|
+
next.set(String(item.id), item);
|
|
42
|
+
});
|
|
43
|
+
incoming.forEach(item => {
|
|
44
|
+
const old = previous.get(String(item.id));
|
|
45
|
+
if (old?.isScan && !(0, _entitlementPass.isEntitlementPass)(item)) return;
|
|
46
|
+
next.set(String(item.id), old && (0, _entitlementPass.isEntitlementPass)(item) ? {
|
|
47
|
+
...item,
|
|
48
|
+
isScan: old.isScan,
|
|
49
|
+
isSelected: old.isSelected,
|
|
50
|
+
isManualSelect: old.isManualSelect
|
|
51
|
+
} : item);
|
|
52
|
+
});
|
|
53
|
+
return [...next.values()];
|
|
54
|
+
}
|
|
55
|
+
function retainScannedEntitlements(current, scanned) {
|
|
56
|
+
const result = new Map(current.map(item => [String(item.id), item]));
|
|
57
|
+
scanned.filter(item => (0, _entitlementPass.isEntitlementPass)(item) && !(0, _availability.getDiscountLifecycleReason)(item)).forEach(item => {
|
|
58
|
+
const previous = result.get(String(item.id));
|
|
59
|
+
const committed = item.committedAppliedProductDetails || previous?.committedAppliedProductDetails || [];
|
|
60
|
+
const isAvailable = !!item.isAvailable;
|
|
61
|
+
result.set(String(item.id), {
|
|
62
|
+
...item,
|
|
63
|
+
isScan: true,
|
|
64
|
+
isAvailable,
|
|
65
|
+
isSelected: committed.length > 0,
|
|
66
|
+
reasonCode: isAvailable ? undefined : item.reasonCode || 'product_not_applicable',
|
|
67
|
+
committedAppliedProductDetails: committed,
|
|
68
|
+
appliedProductDetails: committed,
|
|
69
|
+
savedAmount: committed.length ? previous?.savedAmount : 0
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
return [...result.values()];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Historical uses are display records; backend balance already excludes them. */
|
|
76
|
+
function withCommittedEntitlementDetails(discounts, products, getProduct) {
|
|
77
|
+
const savedAmount = details => details.reduce((sum, detail) => sum.plus(new _decimal.default(detail.discount?.fixed_amount || 0).times(detail._num)), new _decimal.default(0)).toNumber();
|
|
78
|
+
const committed = new Map();
|
|
79
|
+
for (const origin of products) {
|
|
80
|
+
const product = getProduct(origin);
|
|
81
|
+
const persisted = isPersistedEntitlementLine(origin, product);
|
|
82
|
+
const lines = [product, ...(product.bundle || [])];
|
|
83
|
+
const bundleKeys = new Set((product.bundle || []).flatMap(line => (line.discount_list || []).map(detail => `${detail.discount?.resource_id ?? detail.id}:${detail.metadata?.custom_product_bundle_map_id}`)));
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
for (const detail of line.discount_list || []) {
|
|
86
|
+
const bundleId = detail.metadata?.custom_product_bundle_map_id;
|
|
87
|
+
if (!(0, _entitlementPass.isEntitlementPass)(detail) || !persisted && !detail.id || line === product && bundleId && bundleKeys.has(`${detail.discount?.resource_id ?? detail.id}:${bundleId}`)) continue;
|
|
88
|
+
const id = detail.discount?.resource_id ?? detail.resource_id ?? detail.id;
|
|
89
|
+
if (id == null) continue;
|
|
90
|
+
const lineUses = Number(detail.metadata?.num ?? line.num ?? line.quantity ?? 1);
|
|
91
|
+
if (!Number.isFinite(lineUses) || lineUses <= 0) continue;
|
|
92
|
+
const count = lineUses * (line === product && !bundleId ? 1 : Number(product.quantity ?? product.num ?? 1));
|
|
93
|
+
const list = committed.get(String(id)) || [];
|
|
94
|
+
list.push({
|
|
95
|
+
...(0, _lodashEs.cloneDeep)(detail),
|
|
96
|
+
_num: count,
|
|
97
|
+
metadata: {
|
|
98
|
+
...detail.metadata,
|
|
99
|
+
num: count
|
|
100
|
+
},
|
|
101
|
+
discount: {
|
|
102
|
+
...detail.discount,
|
|
103
|
+
fixed_amount: detail.discount?.fixed_amount ?? new _decimal.default(detail.amount || 0).div(bundleId ? lineUses : 1).toNumber()
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
committed.set(String(id), list);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const next = discounts.map(item => {
|
|
111
|
+
if (!(0, _entitlementPass.isEntitlementPass)(item)) return item;
|
|
112
|
+
const details = committed.get(String(item.id)) || [];
|
|
113
|
+
committed.delete(String(item.id));
|
|
114
|
+
return {
|
|
115
|
+
...item,
|
|
116
|
+
committedAppliedProductDetails: details,
|
|
117
|
+
isEditMode: details.length > 0 && !(0, _entitlementPass.resolveEntitlementQuota)(item),
|
|
118
|
+
...(item.isEditMode && (0, _entitlementPass.resolveEntitlementQuota)(item) ? {
|
|
119
|
+
isDisabled: false
|
|
120
|
+
} : {}),
|
|
121
|
+
...(details.length && !(0, _entitlementPass.resolveEntitlementQuota)(item) ? {
|
|
122
|
+
isSelected: true,
|
|
123
|
+
isDisabled: true,
|
|
124
|
+
isUsed: true,
|
|
125
|
+
appliedProductDetails: details,
|
|
126
|
+
savedAmount: savedAmount(details)
|
|
127
|
+
} : {})
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
committed.forEach((details, id) => {
|
|
131
|
+
const first = details[0];
|
|
132
|
+
next.push({
|
|
133
|
+
...first,
|
|
134
|
+
id: Number(id),
|
|
135
|
+
tag: 'entitlement_pass',
|
|
136
|
+
type: 'entitlement_pass',
|
|
137
|
+
format_title: first.discount?.title,
|
|
138
|
+
isEditMode: true,
|
|
139
|
+
isSelected: true,
|
|
140
|
+
isDisabled: true,
|
|
141
|
+
isUsed: true,
|
|
142
|
+
isAvailable: true,
|
|
143
|
+
committedAppliedProductDetails: details,
|
|
144
|
+
appliedProductDetails: details,
|
|
145
|
+
applicableProductDetails: [],
|
|
146
|
+
savedAmount: savedAmount(details)
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
return next;
|
|
150
|
+
}
|
|
@@ -27,11 +27,14 @@ export declare class DiscountModule extends BaseModule implements Module, Discou
|
|
|
27
27
|
with_available_wallet_pass_flag?: 0 | 1;
|
|
28
28
|
request_timezone: string;
|
|
29
29
|
order_id?: number | string;
|
|
30
|
+
with_entitlement_pass?: 0 | 1;
|
|
31
|
+
filter_order_wallet_pass_usage?: 0 | 1;
|
|
30
32
|
}): Promise<DiscountPrepareConfigResult>;
|
|
31
33
|
private extractAvailableWalletIds;
|
|
32
|
-
batchSearch(code: string,
|
|
34
|
+
batchSearch(code: string, customerIdOrOptions?: number | {
|
|
33
35
|
customerId?: number;
|
|
34
|
-
orderId?: number;
|
|
36
|
+
orderId?: number | string;
|
|
37
|
+
withEntitlementPass?: boolean;
|
|
35
38
|
}): Promise<DiscountWithReason>;
|
|
36
39
|
batchSearchByProductIds(productIds: number[]): Promise<any>;
|
|
37
40
|
filterEnabledDiscountList(discountList: Discount[]): Discount[];
|