@pisell/pisellos 2.1.129 → 2.1.131
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/promotion/index.js +9 -0
- package/dist/modules/Order/index.d.ts +7 -6
- package/dist/modules/Order/index.js +137 -42
- package/dist/modules/Order/types.d.ts +32 -6
- package/dist/modules/Order/types.js +2 -0
- package/dist/modules/Order/utils.d.ts +73 -11
- package/dist/modules/Order/utils.js +304 -52
- package/dist/modules/SalesSummary/utils.js +33 -68
- package/dist/modules/ScanOrderLogger/providers/feishu.js +168 -60
- package/dist/modules/ScanOrderLogger/types.d.ts +6 -0
- package/dist/modules/Summary/utils.js +6 -21
- package/dist/solution/ScanOrder/index.d.ts +57 -8
- package/dist/solution/ScanOrder/index.js +1531 -583
- package/dist/solution/ScanOrder/types.d.ts +86 -26
- package/dist/solution/ScanOrder/types.js +20 -1
- package/dist/solution/ScanOrder/utils.d.ts +53 -5
- package/dist/solution/ScanOrder/utils.js +257 -37
- package/dist/solution/VenueBooking/index.d.ts +30 -10
- package/dist/solution/VenueBooking/index.js +460 -217
- package/dist/solution/VenueBooking/types.d.ts +23 -0
- package/dist/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
- package/dist/solution/VenueBooking/utils/dateSummary.js +1 -1
- package/dist/solution/VenueBooking/utils/resource.d.ts +11 -1
- package/dist/solution/VenueBooking/utils/resource.js +57 -21
- package/dist/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
- package/dist/solution/VenueBooking/utils/slotMerge.js +33 -12
- package/dist/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
- package/dist/solution/VenueBooking/utils/timeSlot.js +259 -62
- package/lib/modules/Order/index.d.ts +7 -6
- package/lib/modules/Order/index.js +123 -31
- package/lib/modules/Order/types.d.ts +32 -6
- package/lib/modules/Order/utils.d.ts +73 -11
- package/lib/modules/Order/utils.js +203 -28
- package/lib/modules/SalesSummary/utils.js +13 -47
- package/lib/modules/ScanOrderLogger/providers/feishu.js +100 -34
- package/lib/modules/ScanOrderLogger/types.d.ts +6 -0
- package/lib/modules/Summary/utils.js +4 -18
- package/lib/solution/ScanOrder/index.d.ts +57 -8
- package/lib/solution/ScanOrder/index.js +713 -117
- package/lib/solution/ScanOrder/types.d.ts +86 -26
- package/lib/solution/ScanOrder/utils.d.ts +53 -5
- package/lib/solution/ScanOrder/utils.js +186 -19
- package/lib/solution/VenueBooking/index.d.ts +30 -10
- package/lib/solution/VenueBooking/index.js +206 -51
- package/lib/solution/VenueBooking/types.d.ts +23 -0
- package/lib/solution/VenueBooking/utils/dateSummary.d.ts +1 -1
- package/lib/solution/VenueBooking/utils/dateSummary.js +1 -1
- package/lib/solution/VenueBooking/utils/resource.d.ts +11 -1
- package/lib/solution/VenueBooking/utils/resource.js +15 -4
- package/lib/solution/VenueBooking/utils/slotMerge.d.ts +5 -0
- package/lib/solution/VenueBooking/utils/slotMerge.js +29 -12
- package/lib/solution/VenueBooking/utils/timeSlot.d.ts +1 -1
- package/lib/solution/VenueBooking/utils/timeSlot.js +182 -43
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OrderModule, ProductList, SalesSummaryModule, ScanOrderLogInput, ScanOrderLoggerModule, ScanOrderLoggerProviderConfig, ScanOrderLoggerProviderType } from '../../modules';
|
|
1
|
+
import { OrderModule, ProductList, SalesSummaryModule, ScanOrderLogInput, ScanOrderLoggerModule, ScanOrderLoggerProviderConfig, ScanOrderLoggerProviderType, ScheduleModule } from '../../modules';
|
|
2
2
|
import type { QuantityCheckResult, QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
|
|
3
3
|
/**
|
|
4
4
|
* 扫码下单流程 hook
|
|
@@ -31,26 +31,70 @@ export interface ScanOrderOrderProductIdentity {
|
|
|
31
31
|
product_id: number | null;
|
|
32
32
|
product_variant_id: number;
|
|
33
33
|
identity_key?: string;
|
|
34
|
+
/** 参与合并/匹配;与 remove 通配语义见 isSkuOnlyDeleteIdentity */
|
|
35
|
+
product_option_item?: any[];
|
|
36
|
+
product_bundle?: any[];
|
|
34
37
|
}
|
|
35
38
|
export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity {
|
|
36
39
|
order_detail_id: number | null;
|
|
37
40
|
num: number;
|
|
38
41
|
product_option_item: any[];
|
|
39
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* 券后 **行 composite 单价** = `metadata.main_product_selling_price`
|
|
44
|
+
* + Σ((bundle.bundle_selling_price ?? bundle.price) × (bundle.num ?? 1))
|
|
45
|
+
*
|
|
46
|
+
* 新语义 v2 下 `metadata.main_product_selling_price` 已经**含 option**、含主商品折扣,
|
|
47
|
+
* 因此本字段不再叠加 option。未应用券时等同 `original_price`。
|
|
48
|
+
* Rules 钩子、主商品计税、Summary 统一读 metadata,不以此字段反推。
|
|
49
|
+
*/
|
|
40
50
|
selling_price: string;
|
|
41
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* 券前 **行 composite 单价** = `metadata.main_product_original_price`
|
|
53
|
+
* + Σ(bundle 原价 × num)
|
|
54
|
+
*
|
|
55
|
+
* 新语义 v2 下 `metadata.main_product_original_price` 已经**含 option**、不含折扣。
|
|
56
|
+
* 不承载后台划线价语义。
|
|
57
|
+
*/
|
|
42
58
|
original_price: string;
|
|
43
59
|
tax_fee: string;
|
|
44
60
|
is_charge_tax: number;
|
|
45
61
|
discount_list: any[];
|
|
46
62
|
product_bundle: any[];
|
|
63
|
+
/**
|
|
64
|
+
* 行级扩展 metadata。价格相关的权威字段:
|
|
65
|
+
*
|
|
66
|
+
* - `source_product_price`:主商品/variant 基础价(已应用报价单),**不含 option、不含折扣**。
|
|
67
|
+
* 是派生 `main_product_*` 的唯一源。variant 分支优先读 `metadata.origin.variant[vid].price`。
|
|
68
|
+
* - `main_product_original_price` = `source_product_price + Σ(option.price × option.num)`,
|
|
69
|
+
* **含 option、不含折扣**。
|
|
70
|
+
* - `main_product_selling_price` = `main_product_original_price − 主商品券 per-unit amount`,
|
|
71
|
+
* **含 option、含主商品折扣**。Rules 钩子 / Summary / 计税均以此为主商品权威源。
|
|
72
|
+
* - `price_schema_version`(当前 = 2):schema 版本 sentinel,用于跨端协商价格口径、
|
|
73
|
+
* 区分 v1 旧缓存以便 `normalizeOrderProduct` 触发迁移。
|
|
74
|
+
*/
|
|
47
75
|
metadata: Record<string, any>;
|
|
76
|
+
/** 商品行备注(如顾客对单品的特殊要求) */
|
|
77
|
+
note?: string;
|
|
48
78
|
_origin?: Record<string, any>;
|
|
49
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* 出站 payload 版本的商品行。
|
|
82
|
+
*
|
|
83
|
+
* 与 tempOrder 内部 `ScanOrderOrderProduct` 的字段差异:
|
|
84
|
+
* - `product_option_item[*]`:内部为 `{ product_option_item_id, option_group_id, num, price, ... }`,
|
|
85
|
+
* 出站由 `normalizeSubmitProduct` 重命名为 `{ option_group_item_id, option_group_id, num }`
|
|
86
|
+
* (后端 checkout 协议;丢弃 `price` 等运行时辅助字段)。
|
|
87
|
+
* - `product_bundle[*].option[*]`:同上重命名规则。
|
|
88
|
+
*
|
|
89
|
+
* 运行时(UI 显示、加购合并、指纹、持久化)全部消费内部字段名 `product_option_item_id`,
|
|
90
|
+
* 仅在 SDK 提交边界做一次出站映射,不影响 opaque identity 契约。
|
|
91
|
+
*/
|
|
50
92
|
export interface ScanOrderSubmitProduct extends Omit<ScanOrderOrderProduct, '_origin' | 'identity_key'> {
|
|
51
93
|
/**
|
|
52
94
|
* 出站兼容字段:SDK 内部不再消费 payment_price,
|
|
53
95
|
* 仅在提交后端时由 selling_price 派生,保持原有后端契约。
|
|
96
|
+
* 新语义 v2 下 selling_price 是 composite(含 option、含 bundle、含主商品折扣),
|
|
97
|
+
* 因此 payment_price 同样是 composite。
|
|
54
98
|
*/
|
|
55
99
|
payment_price: string;
|
|
56
100
|
}
|
|
@@ -114,6 +158,9 @@ export interface ScanOrderTempOrder {
|
|
|
114
158
|
shop_discount: string;
|
|
115
159
|
surcharge_fee: string;
|
|
116
160
|
note: string;
|
|
161
|
+
buzzer?: string;
|
|
162
|
+
delivery_type?: string;
|
|
163
|
+
table_number?: Record<string, any>;
|
|
117
164
|
schedule_date: string;
|
|
118
165
|
created_at: string;
|
|
119
166
|
products: ScanOrderOrderProduct[];
|
|
@@ -134,14 +181,15 @@ export interface ScanOrderSubmitPayload extends Omit<ScanOrderTempOrder, 'platfo
|
|
|
134
181
|
request_unique_idempotency_token?: string;
|
|
135
182
|
form_record_ids?: Array<{
|
|
136
183
|
form_id: number | string;
|
|
137
|
-
|
|
184
|
+
form_record_id: number | string;
|
|
138
185
|
}>;
|
|
139
186
|
products: ScanOrderSubmitProduct[];
|
|
140
187
|
}
|
|
141
|
-
export type ScanOrderAvailabilityMode = 'idle' | 'shop_closed' | '
|
|
188
|
+
export type ScanOrderAvailabilityMode = 'idle' | 'shop_closed' | 'submit_disabled' | 'resource_busy' | 'additional_order_with_code' | 'additional_order';
|
|
142
189
|
export interface ScanOrderTableFormRecord {
|
|
143
190
|
policy?: string | null;
|
|
144
191
|
partyroom_booking?: string | null;
|
|
192
|
+
capacity?: number | string | null;
|
|
145
193
|
[key: string]: any;
|
|
146
194
|
}
|
|
147
195
|
export interface ScanOrderAvailabilityInfo {
|
|
@@ -151,7 +199,9 @@ export interface ScanOrderAvailabilityInfo {
|
|
|
151
199
|
table_form_id?: string;
|
|
152
200
|
deskmate_valid?: boolean;
|
|
153
201
|
errorTips?: string;
|
|
154
|
-
/**
|
|
202
|
+
/** 透传 `availability.closed_behavior`,便于 UI 识别拦截类型(如 show_menu_disabled) */
|
|
203
|
+
closed_behavior?: string;
|
|
204
|
+
/** `/order/resource/occupy-detail` 返回的 `form_record` 原样透出 */
|
|
155
205
|
table_form_record?: ScanOrderTableFormRecord | null;
|
|
156
206
|
policy?: string | null;
|
|
157
207
|
partyroom_booking?: string | null;
|
|
@@ -162,34 +212,36 @@ export interface ScanOrderAvailabilityInfo {
|
|
|
162
212
|
/** 首个 `capacity.type === 'custom'` 商品里 `custom[0]` 的 max(人数上限) */
|
|
163
213
|
requestPaxMax?: number;
|
|
164
214
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
215
|
+
/** `resource_capacity[i].capacity_list[j]` */
|
|
216
|
+
export interface ScanOrderResourceCapacitySlot {
|
|
217
|
+
start_at?: string;
|
|
218
|
+
end_at?: string;
|
|
219
|
+
pax?: number | string;
|
|
168
220
|
}
|
|
169
|
-
export interface
|
|
170
|
-
|
|
171
|
-
|
|
221
|
+
export interface ScanOrderResourceCapacity {
|
|
222
|
+
capacity?: number | string;
|
|
223
|
+
capacity_list?: ScanOrderResourceCapacitySlot[];
|
|
172
224
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
225
|
+
/** `/order/resource/occupy-detail` 单条 `occupy_details[i]` */
|
|
226
|
+
export interface ScanOrderResourceOccupyDetail {
|
|
227
|
+
form_record_id?: number | string | null;
|
|
228
|
+
form_id?: number | string | null;
|
|
176
229
|
order_id?: number | string | null;
|
|
177
230
|
last_order_id?: number | string | null;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
table_snack?: ScanOrderTableSnackConfig[] | null;
|
|
181
|
-
order_number_prefix?: ScanOrderOrderNumberPrefixConfig[] | null;
|
|
231
|
+
resource_capacity?: ScanOrderResourceCapacity[] | null;
|
|
232
|
+
form_record?: ScanOrderTableFormRecord | null;
|
|
182
233
|
[key: string]: any;
|
|
183
234
|
}
|
|
184
|
-
export interface
|
|
235
|
+
export interface ScanOrderResourceOccupyDetailApiResponse {
|
|
185
236
|
status?: boolean;
|
|
186
237
|
code?: number;
|
|
187
238
|
message?: string;
|
|
188
|
-
data?:
|
|
239
|
+
data?: {
|
|
240
|
+
occupy_details?: ScanOrderResourceOccupyDetail[] | null;
|
|
241
|
+
} | null;
|
|
189
242
|
}
|
|
243
|
+
export type ScanOrderResourceSelectType = 'single' | 'multiple' | 'capacity';
|
|
190
244
|
export interface ScanOrderResourceState extends ScanOrderAvailabilityInfo {
|
|
191
|
-
tableMaxNumber: number;
|
|
192
|
-
orderCount: number;
|
|
193
245
|
relationId?: string;
|
|
194
246
|
tableFormId?: string;
|
|
195
247
|
currentOrderId?: string;
|
|
@@ -198,13 +250,14 @@ export interface ScanOrderResourceState extends ScanOrderAvailabilityInfo {
|
|
|
198
250
|
deskmateValid: boolean;
|
|
199
251
|
isExclusive: boolean;
|
|
200
252
|
isFull: boolean;
|
|
201
|
-
|
|
202
|
-
|
|
253
|
+
/** 来自首个预约规则商品 product_resource.resources 中与 form_id 匹配的 resource type */
|
|
254
|
+
resourceSelectType?: ScanOrderResourceSelectType;
|
|
255
|
+
raw: ScanOrderResourceOccupyDetail | null;
|
|
203
256
|
}
|
|
204
257
|
export interface ScanOrderState {
|
|
205
258
|
entryContext: ScanOrderEntryContext | null;
|
|
206
259
|
status: ScanOrderStatus;
|
|
207
|
-
config:
|
|
260
|
+
config: Record<string, any> | null;
|
|
208
261
|
resource: ScanOrderResourceState | null;
|
|
209
262
|
flow: Record<string, any>;
|
|
210
263
|
error: string | null;
|
|
@@ -212,6 +265,7 @@ export interface ScanOrderState {
|
|
|
212
265
|
order?: OrderModule;
|
|
213
266
|
salesSummary?: SalesSummaryModule;
|
|
214
267
|
scanOrderLogger?: ScanOrderLoggerModule;
|
|
268
|
+
schedule?: ScheduleModule;
|
|
215
269
|
itemRuleQuantityLimits: QuantityLimitResult[];
|
|
216
270
|
cartValidation: {
|
|
217
271
|
passed: boolean | null;
|
|
@@ -225,3 +279,9 @@ export interface ScanOrderLoggerRuntimeConfig {
|
|
|
225
279
|
}
|
|
226
280
|
export interface ScanOrderAddLogParams extends ScanOrderLogInput {
|
|
227
281
|
}
|
|
282
|
+
/** ScanOrder.scanCode 对外的轻量结果(不含 discountList,UI 在 resolve 后调 getDiscountList) */
|
|
283
|
+
export interface ScanOrderScanCodeResult {
|
|
284
|
+
isAvailable: boolean;
|
|
285
|
+
type?: 'server' | string;
|
|
286
|
+
unavailableReason?: 'time_limit' | string;
|
|
287
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ScanOrderOrderProduct, ScanOrderOrderProductIdentity, ScanOrderSummary, ScanOrderTempOrder } from './types';
|
|
1
|
+
import { ScanOrderOrderProduct, ScanOrderOrderProductIdentity, ScanOrderResourceCapacitySlot, ScanOrderResourceSelectType, ScanOrderSummary, ScanOrderTempOrder } from './types';
|
|
2
2
|
import type { CartItemSummary, ItemRuleBusinessData, PaxInfo, QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
|
|
3
3
|
import type { StrategyConfig } from '../../model/strategy/type';
|
|
4
4
|
import type { ProductData } from '../../modules/Product/types';
|
|
@@ -82,9 +82,24 @@ export declare function buildItemRuleBusinessData(params: {
|
|
|
82
82
|
}): ItemRuleBusinessData;
|
|
83
83
|
export declare function attachItemRuleLimitsToTopLevelProducts<T>(productList: T, limits: QuantityLimitResult[]): T;
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
|
|
85
|
+
* 将选项行 / 套餐行归一成稳定 JSON,用于「同 SKU 是否合并」判断。
|
|
86
|
+
* 数组元素先按主键排序再序列化,避免顺序差异导致误判为不同行。
|
|
87
|
+
*/
|
|
88
|
+
export declare function buildProductLineFingerprint(productOptionItem?: unknown, productBundle?: unknown): string;
|
|
89
|
+
/**
|
|
90
|
+
* removeProductFromOrder 仅传 SKU 时的通配 identity(对象上未声明选项键)。
|
|
91
|
+
* 与显式 `product_option_item: []` 区分:后者只匹配「无选项」行。
|
|
92
|
+
*/
|
|
93
|
+
export declare function isSkuOnlyDeleteIdentity(x: ScanOrderOrderProductIdentity): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* 判断两个商品 identity 是否匹配(不透明 identity 契约)。
|
|
96
|
+
*
|
|
97
|
+
* 调用约定:始终 `isIdentityMatch(line, callerIdentity)`。
|
|
98
|
+
* - 若 SKU(product_id + product_variant_id)不一致 → 不匹配。
|
|
99
|
+
* - **双方都带** `identity_key` → 严格字符串相等比较(不再猜测合成 key / metadata 桥接)。
|
|
100
|
+
* 这是 opaque identity 契约的标准路径:UI 删改时透传 SDK 回灌的 identity_key。
|
|
101
|
+
* - 否则(即至少一侧未声明 identity_key)→ 进入「SKU 通配 / 显式空选项 / 指纹」回退路径,
|
|
102
|
+
* 忽略线侧 identity_key,按内容语义匹配。这给「行已 opaque、调用方却想按 SKU 通配 / 选项指纹删除」留兼容入口。
|
|
88
103
|
*/
|
|
89
104
|
export declare function isIdentityMatch(a: ScanOrderOrderProductIdentity, b: ScanOrderOrderProductIdentity): boolean;
|
|
90
105
|
/**
|
|
@@ -93,10 +108,26 @@ export declare function isIdentityMatch(a: ScanOrderOrderProductIdentity, b: Sca
|
|
|
93
108
|
*/
|
|
94
109
|
export declare function getProductIdentityIndex(products: ScanOrderOrderProduct[], identity: ScanOrderOrderProductIdentity): number;
|
|
95
110
|
/**
|
|
96
|
-
*
|
|
111
|
+
* 对外部传入的商品对象做归一化(v2 composite 语义):
|
|
97
112
|
* - 补全可选字段默认值(未传则使用兜底值,避免后续计算时因 undefined 导致异常)
|
|
98
113
|
* - 对 num 调用 getSafeProductNum 做安全处理
|
|
99
114
|
* - 保留 _origin 供后续业务流程(如促销规则)使用
|
|
115
|
+
*
|
|
116
|
+
* 价格字段语义(metadata 权威源 + composite 派生):
|
|
117
|
+
* - `metadata.source_product_price`:主商品/variant 基础价(已应用报价单),**不含 option**、
|
|
118
|
+
* **不含折扣**。是推导 main_product_* 的起点。variant 分支优先读 `metadata.origin.variant[vid].price`。
|
|
119
|
+
* - `metadata.main_product_original_price`:`source + Σ(option.price × option.num)`,**含 option**、
|
|
120
|
+
* **不含折扣**。
|
|
121
|
+
* - `metadata.main_product_selling_price`:`main_product_original_price - 主商品券 per-unit amount`,
|
|
122
|
+
* **含 option**、**含折扣**。
|
|
123
|
+
* - 行级 `selling_price` = `main_product_selling_price + Σ(bundle_selling_price × num)`。
|
|
124
|
+
* - 行级 `original_price` = `main_product_original_price + Σ(bundle 原价 × num)`。
|
|
125
|
+
*
|
|
126
|
+
* 迁移与幂等:
|
|
127
|
+
* - `metadata.price_schema_version === 2` → 已新语义归一化,保留 main_product_* 原值(保留折扣)。
|
|
128
|
+
* - 其它情况(v1 / 缺字段 / 无 metadata)→ 按"main_product_selling_price 曾是 main-only"的旧约定
|
|
129
|
+
* 反推 legacyDiscount,再以新 source + options 基准重算。最终统一打上 `price_schema_version: 2`。
|
|
130
|
+
* - 因此多次 normalize 不会重复叠加 option/bundle。
|
|
100
131
|
*/
|
|
101
132
|
export declare function normalizeOrderProduct(product: Partial<ScanOrderOrderProduct> & ScanOrderOrderProductIdentity): ScanOrderOrderProduct;
|
|
102
133
|
/**
|
|
@@ -109,6 +140,18 @@ export declare function collectLinkProductIdsFromReservationRules(rules: unknown
|
|
|
109
140
|
export declare function pickFirstDurationMinutesFromProducts(products: ProductData[]): number | undefined;
|
|
110
141
|
/** 是否存在 capacity.type === 'custom' 的商品 */
|
|
111
142
|
export declare function hasCustomCapacityProduct(products: ProductData[]): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* 根据预约规则商品的 resource.type 计算桌台是否已被"占满"。
|
|
145
|
+
* - single:只要有 `lastOrderId` 即视为占用
|
|
146
|
+
* - multiple:当前时间落在 `capacity_list[i]` 的 `[start_at, end_at]`(inclusive)内的 pax 之和 >= 总容量
|
|
147
|
+
* - 其他('capacity' / undefined):返回 false,不施加限制
|
|
148
|
+
*/
|
|
149
|
+
export declare function computeResourceIsFull(params: {
|
|
150
|
+
resourceSelectType?: ScanOrderResourceSelectType;
|
|
151
|
+
lastOrderId?: string;
|
|
152
|
+
capacityList?: ScanOrderResourceCapacitySlot[];
|
|
153
|
+
capacity?: number | string | null;
|
|
154
|
+
}): boolean;
|
|
112
155
|
/**
|
|
113
156
|
* 在商品列表中找到第一个 `capacity.type === 'custom'` 的商品,取其 `custom` 数组第一项的 min/max。
|
|
114
157
|
* 仅返回有限数字字段;若均无法解析则返回 `undefined`。
|
|
@@ -117,3 +160,8 @@ export declare function pickFirstCustomCapacityPaxBounds(products: ProductData[]
|
|
|
117
160
|
min?: number;
|
|
118
161
|
max?: number;
|
|
119
162
|
} | undefined;
|
|
163
|
+
/**
|
|
164
|
+
* 第一个 `capacity.type === 'custom'` 的商品,取其 `custom[0].id`(提交 booking metadata.capacity 维度 id)。
|
|
165
|
+
* 无匹配时返回 `undefined`,调用方应回退为 `0`。
|
|
166
|
+
*/
|
|
167
|
+
export declare function pickFirstCustomCapacityDimensionId(products: ProductData[]): string | number | undefined;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
1
2
|
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
7
|
var __export = (target, all) => {
|
|
6
8
|
for (var name in all)
|
|
@@ -14,6 +16,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
14
16
|
}
|
|
15
17
|
return to;
|
|
16
18
|
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
17
27
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
28
|
|
|
19
29
|
// src/solution/ScanOrder/utils.ts
|
|
@@ -23,8 +33,10 @@ __export(utils_exports, {
|
|
|
23
33
|
attachItemRuleLimitsToTopLevelProducts: () => attachItemRuleLimitsToTopLevelProducts,
|
|
24
34
|
buildItemRuleBusinessData: () => buildItemRuleBusinessData,
|
|
25
35
|
buildProductKey: () => buildProductKey,
|
|
36
|
+
buildProductLineFingerprint: () => buildProductLineFingerprint,
|
|
26
37
|
buildQuantityLimitIndex: () => buildQuantityLimitIndex,
|
|
27
38
|
collectLinkProductIdsFromReservationRules: () => collectLinkProductIdsFromReservationRules,
|
|
39
|
+
computeResourceIsFull: () => computeResourceIsFull,
|
|
28
40
|
createEmptySummary: () => createEmptySummary,
|
|
29
41
|
extractStrategyModelIdsFromTableConfig: () => extractStrategyModelIdsFromTableConfig,
|
|
30
42
|
getProductIdentityIndex: () => getProductIdentityIndex,
|
|
@@ -33,9 +45,11 @@ __export(utils_exports, {
|
|
|
33
45
|
getTopLevelVariantId: () => getTopLevelVariantId,
|
|
34
46
|
hasCustomCapacityProduct: () => hasCustomCapacityProduct,
|
|
35
47
|
isIdentityMatch: () => isIdentityMatch,
|
|
48
|
+
isSkuOnlyDeleteIdentity: () => isSkuOnlyDeleteIdentity,
|
|
36
49
|
normalizeEnabledItemRuleIds: () => normalizeEnabledItemRuleIds,
|
|
37
50
|
normalizeItemRuleStrategies: () => normalizeItemRuleStrategies,
|
|
38
51
|
normalizeOrderProduct: () => normalizeOrderProduct,
|
|
52
|
+
pickFirstCustomCapacityDimensionId: () => pickFirstCustomCapacityDimensionId,
|
|
39
53
|
pickFirstCustomCapacityPaxBounds: () => pickFirstCustomCapacityPaxBounds,
|
|
40
54
|
pickFirstDurationMinutesFromProducts: () => pickFirstDurationMinutesFromProducts,
|
|
41
55
|
resolveSkuMatchedQuantityLimits: () => resolveSkuMatchedQuantityLimits,
|
|
@@ -46,6 +60,9 @@ __export(utils_exports, {
|
|
|
46
60
|
toPriceString: () => toPriceString
|
|
47
61
|
});
|
|
48
62
|
module.exports = __toCommonJS(utils_exports);
|
|
63
|
+
var import_dayjs = __toESM(require("dayjs"));
|
|
64
|
+
var import_decimal = __toESM(require("decimal.js"));
|
|
65
|
+
var import_utils = require("../../modules/Order/utils");
|
|
49
66
|
function createEmptySummary() {
|
|
50
67
|
return {
|
|
51
68
|
product_quantity: 0,
|
|
@@ -326,12 +343,58 @@ function attachItemRuleLimitsToTopLevelProducts(productList, limits) {
|
|
|
326
343
|
}
|
|
327
344
|
return productList;
|
|
328
345
|
}
|
|
346
|
+
function buildProductLineFingerprint(productOptionItem, productBundle) {
|
|
347
|
+
const optArr = Array.isArray(productOptionItem) ? productOptionItem : [];
|
|
348
|
+
const normalizedOpts = optArr.map((item) => ({
|
|
349
|
+
product_option_item_id: Number(item == null ? void 0 : item.product_option_item_id) || 0,
|
|
350
|
+
option_group_id: Number(item == null ? void 0 : item.option_group_id) || 0,
|
|
351
|
+
num: typeof (item == null ? void 0 : item.num) === "number" && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1,
|
|
352
|
+
price: String((item == null ? void 0 : item.price) ?? "")
|
|
353
|
+
})).sort((p, q) => {
|
|
354
|
+
if (p.product_option_item_id !== q.product_option_item_id) {
|
|
355
|
+
return p.product_option_item_id - q.product_option_item_id;
|
|
356
|
+
}
|
|
357
|
+
if (p.option_group_id !== q.option_group_id)
|
|
358
|
+
return p.option_group_id - q.option_group_id;
|
|
359
|
+
if (p.num !== q.num)
|
|
360
|
+
return p.num - q.num;
|
|
361
|
+
return p.price.localeCompare(q.price);
|
|
362
|
+
});
|
|
363
|
+
const bundleArr = Array.isArray(productBundle) ? productBundle : [];
|
|
364
|
+
const normalizedBundles = bundleArr.map((item) => ({
|
|
365
|
+
bundle_id: Number((item == null ? void 0 : item.bundle_id) ?? (item == null ? void 0 : item.id) ?? 0) || 0,
|
|
366
|
+
product_id: Number(item == null ? void 0 : item.product_id) || 0,
|
|
367
|
+
num: typeof (item == null ? void 0 : item.num) === "number" && !Number.isNaN(item.num) ? Math.max(1, Math.floor(item.num)) : 1
|
|
368
|
+
})).sort((p, q) => {
|
|
369
|
+
if (p.bundle_id !== q.bundle_id)
|
|
370
|
+
return p.bundle_id - q.bundle_id;
|
|
371
|
+
if (p.product_id !== q.product_id)
|
|
372
|
+
return p.product_id - q.product_id;
|
|
373
|
+
return p.num - q.num;
|
|
374
|
+
});
|
|
375
|
+
return JSON.stringify([normalizedOpts, normalizedBundles]);
|
|
376
|
+
}
|
|
377
|
+
function isSkuOnlyDeleteIdentity(x) {
|
|
378
|
+
if (x.identity_key)
|
|
379
|
+
return false;
|
|
380
|
+
return !("product_option_item" in x) && !("product_bundle" in x);
|
|
381
|
+
}
|
|
382
|
+
function fingerprintForIdentityWithOptionKeys(x) {
|
|
383
|
+
const row = x;
|
|
384
|
+
const opts = "product_option_item" in row ? Array.isArray(row.product_option_item) ? row.product_option_item : [] : [];
|
|
385
|
+
const buds = "product_bundle" in row ? Array.isArray(row.product_bundle) ? row.product_bundle : [] : [];
|
|
386
|
+
return buildProductLineFingerprint(opts, buds);
|
|
387
|
+
}
|
|
329
388
|
function isIdentityMatch(a, b) {
|
|
330
389
|
if (a.product_id !== b.product_id || a.product_variant_id !== b.product_variant_id)
|
|
331
390
|
return false;
|
|
332
|
-
|
|
391
|
+
const aHasKey = typeof a.identity_key === "string" && a.identity_key.length > 0;
|
|
392
|
+
const bHasKey = typeof b.identity_key === "string" && b.identity_key.length > 0;
|
|
393
|
+
if (aHasKey && bHasKey)
|
|
394
|
+
return a.identity_key === b.identity_key;
|
|
395
|
+
if (isSkuOnlyDeleteIdentity(a) || isSkuOnlyDeleteIdentity(b))
|
|
333
396
|
return true;
|
|
334
|
-
return a
|
|
397
|
+
return fingerprintForIdentityWithOptionKeys(a) === fingerprintForIdentityWithOptionKeys(b);
|
|
335
398
|
}
|
|
336
399
|
function getProductIdentityIndex(products, identity) {
|
|
337
400
|
return products.findIndex((item) => isIdentityMatch(item, identity));
|
|
@@ -339,39 +402,91 @@ function getProductIdentityIndex(products, identity) {
|
|
|
339
402
|
function normalizeOrderProduct(product) {
|
|
340
403
|
var _a, _b;
|
|
341
404
|
const metadata = { ...product.metadata || {} };
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const resolvedSellingPrice = product.selling_price || "0.00";
|
|
346
|
-
const resolvedOriginalPrice = product.original_price || resolvedSellingPrice;
|
|
347
|
-
if (metadata.main_product_original_price === void 0) {
|
|
348
|
-
metadata.main_product_original_price = resolvedOriginalPrice;
|
|
349
|
-
}
|
|
350
|
-
if (metadata.main_product_selling_price === void 0) {
|
|
351
|
-
metadata.main_product_selling_price = resolvedSellingPrice;
|
|
352
|
-
}
|
|
353
|
-
if (metadata.source_product_price === void 0) {
|
|
354
|
-
metadata.source_product_price = ((_a = product._origin) == null ? void 0 : _a.price) ?? ((_b = product._origin) == null ? void 0 : _b.base_price) ?? resolvedSellingPrice;
|
|
405
|
+
const resolvedIdentityKey = product.identity_key && String(product.identity_key).length > 0 ? String(product.identity_key) : (0, import_utils.createUuidV4)();
|
|
406
|
+
if (!metadata.unique_identification_number) {
|
|
407
|
+
metadata.unique_identification_number = resolvedIdentityKey;
|
|
355
408
|
}
|
|
356
409
|
const normalizedBundle = (product.product_bundle || []).map((item) => ({
|
|
357
410
|
...item,
|
|
358
411
|
bundle_selling_price: item.bundle_selling_price ?? item.price ?? "0.00",
|
|
359
412
|
custom_price: item.custom_price ?? item.bundle_selling_price ?? item.price ?? "0.00"
|
|
360
413
|
}));
|
|
414
|
+
const normalizedOptions = product.product_option_item || [];
|
|
415
|
+
const optionSum = (0, import_utils.sumOptionUnitPrice)(normalizedOptions);
|
|
416
|
+
const isV2 = metadata.price_schema_version === 2 || metadata.price_schema_version === "2";
|
|
417
|
+
const variantId = Number(product.product_variant_id || 0);
|
|
418
|
+
const variantList = variantId ? (_a = metadata == null ? void 0 : metadata.origin) == null ? void 0 : _a.variant : null;
|
|
419
|
+
const variantPrice = Array.isArray(variantList) ? (_b = variantList.find((v) => Number(v == null ? void 0 : v.id) === variantId)) == null ? void 0 : _b.price : void 0;
|
|
420
|
+
const resolvedSource = (() => {
|
|
421
|
+
var _a2, _b2;
|
|
422
|
+
if (metadata.source_product_price !== void 0) {
|
|
423
|
+
return String(metadata.source_product_price);
|
|
424
|
+
}
|
|
425
|
+
if (variantPrice !== void 0 && variantPrice !== null) {
|
|
426
|
+
return String(variantPrice);
|
|
427
|
+
}
|
|
428
|
+
const originPrice = (_a2 = product._origin) == null ? void 0 : _a2.price;
|
|
429
|
+
if (originPrice !== void 0 && originPrice !== null) {
|
|
430
|
+
return String(originPrice);
|
|
431
|
+
}
|
|
432
|
+
const originBasePrice = (_b2 = product._origin) == null ? void 0 : _b2.base_price;
|
|
433
|
+
if (originBasePrice !== void 0 && originBasePrice !== null) {
|
|
434
|
+
return String(originBasePrice);
|
|
435
|
+
}
|
|
436
|
+
if (!isV2 && metadata.main_product_original_price !== void 0) {
|
|
437
|
+
return String(metadata.main_product_original_price);
|
|
438
|
+
}
|
|
439
|
+
return product.original_price ?? product.selling_price ?? "0.00";
|
|
440
|
+
})();
|
|
441
|
+
const mainOriginalDec = new import_decimal.default(Number(resolvedSource) || 0).plus(optionSum);
|
|
442
|
+
const mainOriginalStr = mainOriginalDec.toDecimalPlaces(2).toFixed(2);
|
|
443
|
+
let mainSellingDec;
|
|
444
|
+
if (isV2 && metadata.main_product_selling_price != null) {
|
|
445
|
+
mainSellingDec = new import_decimal.default(Number(metadata.main_product_selling_price) || 0);
|
|
446
|
+
} else if (metadata.main_product_selling_price != null && metadata.main_product_original_price != null) {
|
|
447
|
+
const legacyOriginal = new import_decimal.default(Number(metadata.main_product_original_price) || 0);
|
|
448
|
+
const legacySelling = new import_decimal.default(Number(metadata.main_product_selling_price) || 0);
|
|
449
|
+
const legacyDiscount = legacyOriginal.minus(legacySelling);
|
|
450
|
+
mainSellingDec = mainOriginalDec.minus(legacyDiscount);
|
|
451
|
+
} else if (product.original_price != null && product.selling_price != null && new import_decimal.default(Number(product.original_price) || 0).greaterThan(
|
|
452
|
+
new import_decimal.default(Number(product.selling_price) || 0)
|
|
453
|
+
)) {
|
|
454
|
+
const topOriginal = new import_decimal.default(Number(product.original_price) || 0);
|
|
455
|
+
const topSelling = new import_decimal.default(Number(product.selling_price) || 0);
|
|
456
|
+
const legacyDiscount = topOriginal.minus(topSelling);
|
|
457
|
+
mainSellingDec = mainOriginalDec.minus(legacyDiscount);
|
|
458
|
+
} else {
|
|
459
|
+
mainSellingDec = mainOriginalDec;
|
|
460
|
+
}
|
|
461
|
+
const mainSellingStr = mainSellingDec.toDecimalPlaces(2).toFixed(2);
|
|
462
|
+
metadata.source_product_price = resolvedSource;
|
|
463
|
+
metadata.main_product_original_price = mainOriginalStr;
|
|
464
|
+
metadata.main_product_selling_price = mainSellingStr;
|
|
465
|
+
metadata.price_schema_version = 2;
|
|
466
|
+
const composedSellingPrice = (0, import_utils.composeLinePrice)({
|
|
467
|
+
mainPrice: mainSellingStr,
|
|
468
|
+
bundle: normalizedBundle
|
|
469
|
+
});
|
|
470
|
+
const composedOriginalPrice = (0, import_utils.composeLinePrice)({
|
|
471
|
+
mainPrice: mainOriginalStr,
|
|
472
|
+
bundle: normalizedBundle,
|
|
473
|
+
useOriginalBundle: true
|
|
474
|
+
});
|
|
361
475
|
return {
|
|
362
476
|
order_detail_id: product.order_detail_id || null,
|
|
363
477
|
product_id: product.product_id,
|
|
364
478
|
num: getSafeProductNum(product.num),
|
|
365
479
|
product_variant_id: product.product_variant_id,
|
|
366
|
-
identity_key:
|
|
367
|
-
product_option_item:
|
|
368
|
-
selling_price:
|
|
369
|
-
original_price:
|
|
480
|
+
identity_key: resolvedIdentityKey,
|
|
481
|
+
product_option_item: normalizedOptions,
|
|
482
|
+
selling_price: composedSellingPrice,
|
|
483
|
+
original_price: composedOriginalPrice,
|
|
370
484
|
tax_fee: product.tax_fee || "0.00",
|
|
371
485
|
is_charge_tax: product.is_charge_tax ?? 0,
|
|
372
486
|
discount_list: product.discount_list || [],
|
|
373
487
|
product_bundle: normalizedBundle,
|
|
374
488
|
metadata,
|
|
489
|
+
note: product.note != null ? String(product.note) : "",
|
|
375
490
|
_origin: product._origin
|
|
376
491
|
};
|
|
377
492
|
}
|
|
@@ -411,6 +526,30 @@ function hasCustomCapacityProduct(products) {
|
|
|
411
526
|
return ((_a = p == null ? void 0 : p.capacity) == null ? void 0 : _a.type) === "custom";
|
|
412
527
|
});
|
|
413
528
|
}
|
|
529
|
+
function computeResourceIsFull(params) {
|
|
530
|
+
const { resourceSelectType, lastOrderId, capacityList, capacity } = params;
|
|
531
|
+
if (resourceSelectType === "single")
|
|
532
|
+
return Boolean(lastOrderId);
|
|
533
|
+
if (resourceSelectType !== "multiple")
|
|
534
|
+
return false;
|
|
535
|
+
const totalCapacity = Number(capacity);
|
|
536
|
+
if (!Number.isFinite(totalCapacity) || totalCapacity <= 0)
|
|
537
|
+
return false;
|
|
538
|
+
const now = (0, import_dayjs.default)();
|
|
539
|
+
let occupied = 0;
|
|
540
|
+
for (const slot of capacityList || []) {
|
|
541
|
+
const start = (0, import_dayjs.default)(slot == null ? void 0 : slot.start_at);
|
|
542
|
+
const end = (0, import_dayjs.default)(slot == null ? void 0 : slot.end_at);
|
|
543
|
+
if (!start.isValid() || !end.isValid())
|
|
544
|
+
continue;
|
|
545
|
+
if ((now.isAfter(start) || now.isSame(start)) && (now.isBefore(end) || now.isSame(end))) {
|
|
546
|
+
const pax = Number(slot == null ? void 0 : slot.pax);
|
|
547
|
+
if (Number.isFinite(pax) && pax > 0)
|
|
548
|
+
occupied += pax;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return occupied >= totalCapacity;
|
|
552
|
+
}
|
|
414
553
|
function pickFirstCustomCapacityPaxBounds(products) {
|
|
415
554
|
for (const p of products) {
|
|
416
555
|
const cap = p == null ? void 0 : p.capacity;
|
|
@@ -438,14 +577,40 @@ function pickFirstCustomCapacityPaxBounds(products) {
|
|
|
438
577
|
}
|
|
439
578
|
return void 0;
|
|
440
579
|
}
|
|
580
|
+
function pickFirstCustomCapacityDimensionId(products) {
|
|
581
|
+
for (const p of products) {
|
|
582
|
+
const cap = p == null ? void 0 : p.capacity;
|
|
583
|
+
if (!cap || cap.type !== "custom")
|
|
584
|
+
continue;
|
|
585
|
+
if (!Array.isArray(cap.custom) || cap.custom.length === 0)
|
|
586
|
+
continue;
|
|
587
|
+
const row = cap.custom[0];
|
|
588
|
+
if (!row || typeof row !== "object")
|
|
589
|
+
continue;
|
|
590
|
+
const id = row.id;
|
|
591
|
+
if (id === null || id === void 0 || id === "")
|
|
592
|
+
continue;
|
|
593
|
+
if (typeof id === "number" && Number.isFinite(id))
|
|
594
|
+
return id;
|
|
595
|
+
if (typeof id === "string") {
|
|
596
|
+
const trimmed = id.trim();
|
|
597
|
+
if (!trimmed)
|
|
598
|
+
continue;
|
|
599
|
+
return trimmed;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return void 0;
|
|
603
|
+
}
|
|
441
604
|
// Annotate the CommonJS export names for ESM import in node:
|
|
442
605
|
0 && (module.exports = {
|
|
443
606
|
aggregateItemRuleLimit,
|
|
444
607
|
attachItemRuleLimitsToTopLevelProducts,
|
|
445
608
|
buildItemRuleBusinessData,
|
|
446
609
|
buildProductKey,
|
|
610
|
+
buildProductLineFingerprint,
|
|
447
611
|
buildQuantityLimitIndex,
|
|
448
612
|
collectLinkProductIdsFromReservationRules,
|
|
613
|
+
computeResourceIsFull,
|
|
449
614
|
createEmptySummary,
|
|
450
615
|
extractStrategyModelIdsFromTableConfig,
|
|
451
616
|
getProductIdentityIndex,
|
|
@@ -454,9 +619,11 @@ function pickFirstCustomCapacityPaxBounds(products) {
|
|
|
454
619
|
getTopLevelVariantId,
|
|
455
620
|
hasCustomCapacityProduct,
|
|
456
621
|
isIdentityMatch,
|
|
622
|
+
isSkuOnlyDeleteIdentity,
|
|
457
623
|
normalizeEnabledItemRuleIds,
|
|
458
624
|
normalizeItemRuleStrategies,
|
|
459
625
|
normalizeOrderProduct,
|
|
626
|
+
pickFirstCustomCapacityDimensionId,
|
|
460
627
|
pickFirstCustomCapacityPaxBounds,
|
|
461
628
|
pickFirstDurationMinutesFromProducts,
|
|
462
629
|
resolveSkuMatchedQuantityLimits,
|