@pisell/pisellos 2.3.81 → 2.3.83
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 +0 -9
- package/dist/modules/BookingContext/index.d.ts +3 -0
- package/dist/modules/BookingContext/index.js +42 -13
- package/dist/modules/BookingContext/types.d.ts +4 -4
- package/dist/modules/BookingContext/types.js +3 -3
- package/dist/modules/ProductList/clientDataVariants.d.ts +23 -0
- package/dist/modules/ProductList/clientDataVariants.js +165 -11
- package/dist/modules/ProductList/index.d.ts +14 -0
- package/dist/modules/ProductList/index.js +78 -11
- package/dist/solution/BaseSales/index.d.ts +16 -1
- package/dist/solution/BaseSales/index.js +531 -273
- package/dist/solution/BookingByStep/index.d.ts +1 -1
- package/dist/solution/BookingTicket/index.d.ts +1 -1
- package/dist/solution/UnifiedBookingSales/index.js +2 -2
- package/dist/utils/platform.d.ts +8 -0
- package/dist/utils/platform.js +15 -0
- package/lib/modules/BookingContext/index.d.ts +3 -0
- package/lib/modules/BookingContext/index.js +35 -8
- package/lib/modules/BookingContext/types.d.ts +4 -4
- package/lib/modules/BookingContext/types.js +3 -3
- package/lib/modules/ProductList/clientDataVariants.d.ts +23 -0
- package/lib/modules/ProductList/clientDataVariants.js +147 -11
- package/lib/modules/ProductList/index.d.ts +14 -0
- package/lib/modules/ProductList/index.js +59 -1
- package/lib/solution/BaseSales/index.d.ts +16 -1
- package/lib/solution/BaseSales/index.js +258 -66
- package/lib/solution/BookingByStep/index.d.ts +1 -1
- package/lib/solution/BookingTicket/index.d.ts +1 -1
- package/lib/solution/UnifiedBookingSales/index.js +2 -2
- package/lib/utils/platform.d.ts +8 -0
- package/lib/utils/platform.js +22 -0
- package/package.json +1 -1
|
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
|
|
|
326
326
|
date: string;
|
|
327
327
|
status: string;
|
|
328
328
|
week: string;
|
|
329
|
-
weekNum: 0 | 2 | 1 |
|
|
329
|
+
weekNum: 0 | 2 | 1 | 5 | 3 | 6 | 4;
|
|
330
330
|
}[]>;
|
|
331
331
|
submitTimeSlot(timeSlots: TimeSliceItem): void;
|
|
332
332
|
private getScheduleDataByIds;
|
|
@@ -334,7 +334,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
|
|
|
334
334
|
* 获取当前的客户搜索条件
|
|
335
335
|
* @returns 当前搜索条件
|
|
336
336
|
*/
|
|
337
|
-
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "
|
|
337
|
+
getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "skip" | "num">;
|
|
338
338
|
/**
|
|
339
339
|
* 获取客户列表状态(包含滚动加载相关状态)
|
|
340
340
|
* @returns 客户状态
|
|
@@ -42,6 +42,7 @@ import { UnifiedBookingSalesHooks } from "./types";
|
|
|
42
42
|
import { AvailabilityRemoteOccupancyCache } from "./remoteOccupancyCache";
|
|
43
43
|
import { BookingTicket } from "../BookingTicket";
|
|
44
44
|
import { addLocalCalendarDays, captureRuntimeOffsetMinutes, formatLocalDate, formatLocalDateTime, formatLocalTime, localDateTimeToScalar, localDateToScalar, localDateToScalarStart, parseLocalDate, parseLocalDateTime } from "../../modules/ResourcePlanner/localClock";
|
|
45
|
+
import { isCustomerUserPlatform } from "../../utils/platform";
|
|
45
46
|
export * from "./types";
|
|
46
47
|
export { AvailabilityError } from "../../modules/ResourcePlanner";
|
|
47
48
|
var OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'basic', 'checkout', 'availability'];
|
|
@@ -1950,8 +1951,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
|
|
|
1950
1951
|
key: "isAuthenticatedCustomerUserPlatform",
|
|
1951
1952
|
value: function isAuthenticatedCustomerUserPlatform() {
|
|
1952
1953
|
var _this$otherParams14;
|
|
1953
|
-
|
|
1954
|
-
return platform === 'pc' || platform === 'h5';
|
|
1954
|
+
return isCustomerUserPlatform((_this$otherParams14 = this.otherParams) === null || _this$otherParams14 === void 0 ? void 0 : _this$otherParams14.platform);
|
|
1955
1955
|
}
|
|
1956
1956
|
}, {
|
|
1957
1957
|
key: "resolveDiscountOrderIdentity",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize the runtime platform passed through solution/module otherParams.
|
|
3
|
+
*/
|
|
4
|
+
export declare function normalizeRuntimePlatform(platform: unknown): string;
|
|
5
|
+
/**
|
|
6
|
+
* PC and H5 are authenticated-customer clients rather than staff terminals.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isCustomerUserPlatform(platform: unknown): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
var CUSTOMER_USER_PLATFORMS = new Set(['pc', 'h5']);
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Normalize the runtime platform passed through solution/module otherParams.
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeRuntimePlatform(platform) {
|
|
7
|
+
return String(platform !== null && platform !== void 0 ? platform : '').trim().toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* PC and H5 are authenticated-customer clients rather than staff terminals.
|
|
12
|
+
*/
|
|
13
|
+
export function isCustomerUserPlatform(platform) {
|
|
14
|
+
return CUSTOMER_USER_PLATFORMS.has(normalizeRuntimePlatform(platform));
|
|
15
|
+
}
|
|
@@ -19,12 +19,15 @@ export declare class BookingContextModule extends BaseModule implements Module,
|
|
|
19
19
|
protected defaultVersion: string;
|
|
20
20
|
private request;
|
|
21
21
|
private store;
|
|
22
|
+
private otherParams;
|
|
22
23
|
/** 上次成功 loadBookingConfig 的 query key;命中则不再请求。 */
|
|
23
24
|
private lastConfigQueryKey;
|
|
24
25
|
/** 上次成功 loadResources 的 query key(date|bookingIds);命中则不再请求。 */
|
|
25
26
|
private lastResourcesQueryKey;
|
|
26
27
|
constructor(name?: string, version?: string);
|
|
27
28
|
initialize(core: PisellCore, options?: ModuleOptions): Promise<void>;
|
|
29
|
+
/** 同步父级解决方案的运行态参数;platform 变化后会自然切换 config endpoint/cache key。 */
|
|
30
|
+
updateOtherParams(params: Record<string, any>): void;
|
|
28
31
|
loadBookingConfig(params?: LoadBookingConfigParams, options?: {
|
|
29
32
|
forceRefresh?: boolean;
|
|
30
33
|
}): Promise<BookingContextConfig | null>;
|
|
@@ -16,6 +16,7 @@ exports.buildBookingResourcesQueryKey = buildBookingResourcesQueryKey;
|
|
|
16
16
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
17
17
|
var _BaseModule = require("../BaseModule");
|
|
18
18
|
var _request = require("../../plugins/request");
|
|
19
|
+
var _platform = require("../../utils/platform");
|
|
19
20
|
var _types = require("./types");
|
|
20
21
|
Object.keys(_types).forEach(function (key) {
|
|
21
22
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -52,6 +53,8 @@ const DEFAULT_BOOKING_CONFIG_PARAMS = {
|
|
|
52
53
|
item_type: 'appointment_booking',
|
|
53
54
|
sub_type: 'ticket'
|
|
54
55
|
};
|
|
56
|
+
const BOOKING_CONFIG_ENDPOINT = '/core/board/management/config';
|
|
57
|
+
const H5_BOOKING_CONFIG_ENDPOINT = '/core/board/config';
|
|
55
58
|
const BOARD_CONFIG_CACHE = {
|
|
56
59
|
useCache: true,
|
|
57
60
|
cache: {
|
|
@@ -167,6 +170,26 @@ function buildBookingConfigQueryKey(params = {}) {
|
|
|
167
170
|
});
|
|
168
171
|
return JSON.stringify(sorted);
|
|
169
172
|
}
|
|
173
|
+
function resolveBookingConfigRequest(platform, params = {}) {
|
|
174
|
+
const isH5 = (0, _platform.normalizeRuntimePlatform)(platform) === 'h5';
|
|
175
|
+
const endpoint = isH5 ? H5_BOOKING_CONFIG_ENDPOINT : BOOKING_CONFIG_ENDPOINT;
|
|
176
|
+
const query = isH5 ? {
|
|
177
|
+
item_type: params.item_type || DEFAULT_BOOKING_CONFIG_PARAMS.item_type
|
|
178
|
+
} : {
|
|
179
|
+
...DEFAULT_BOOKING_CONFIG_PARAMS,
|
|
180
|
+
...params
|
|
181
|
+
};
|
|
182
|
+
const keys = Object.keys(query).sort();
|
|
183
|
+
const sortedQuery = {};
|
|
184
|
+
keys.forEach(key => {
|
|
185
|
+
sortedQuery[key] = query[key];
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
endpoint,
|
|
189
|
+
query,
|
|
190
|
+
key: `${endpoint}:${JSON.stringify(sortedQuery)}`
|
|
191
|
+
};
|
|
192
|
+
}
|
|
170
193
|
|
|
171
194
|
/** 稳定序列化资源列表查询(date + bookingIds),用于跳过重复 loadResources。 */
|
|
172
195
|
function buildBookingResourcesQueryKey(params = {}) {
|
|
@@ -181,6 +204,7 @@ class BookingContextModule extends _BaseModule.BaseModule {
|
|
|
181
204
|
store = {
|
|
182
205
|
...EMPTY_STATE
|
|
183
206
|
};
|
|
207
|
+
otherParams = {};
|
|
184
208
|
/** 上次成功 loadBookingConfig 的 query key;命中则不再请求。 */
|
|
185
209
|
lastConfigQueryKey = null;
|
|
186
210
|
/** 上次成功 loadResources 的 query key(date|bookingIds);命中则不再请求。 */
|
|
@@ -194,6 +218,7 @@ class BookingContextModule extends _BaseModule.BaseModule {
|
|
|
194
218
|
if (!this.request) {
|
|
195
219
|
throw new Error('BookingContextModule 需要 request 插件支持');
|
|
196
220
|
}
|
|
221
|
+
this.otherParams = options.otherParams || {};
|
|
197
222
|
this.store = {
|
|
198
223
|
bookingConfig: null,
|
|
199
224
|
resourcesOrigin: [],
|
|
@@ -212,15 +237,17 @@ class BookingContextModule extends _BaseModule.BaseModule {
|
|
|
212
237
|
this.store.date = initialState.date;
|
|
213
238
|
}
|
|
214
239
|
}
|
|
240
|
+
|
|
241
|
+
/** 同步父级解决方案的运行态参数;platform 变化后会自然切换 config endpoint/cache key。 */
|
|
242
|
+
updateOtherParams(params) {
|
|
243
|
+
this.otherParams = params || {};
|
|
244
|
+
}
|
|
215
245
|
async loadBookingConfig(params = {}, options = {}) {
|
|
216
|
-
const
|
|
246
|
+
const requestConfig = resolveBookingConfigRequest(this.otherParams?.platform, params);
|
|
247
|
+
const configKey = requestConfig.key;
|
|
217
248
|
if (!options.forceRefresh && this.store.bookingConfig != null && this.lastConfigQueryKey === configKey) {
|
|
218
249
|
return this.store.bookingConfig;
|
|
219
250
|
}
|
|
220
|
-
const query = {
|
|
221
|
-
...DEFAULT_BOOKING_CONFIG_PARAMS,
|
|
222
|
-
...params
|
|
223
|
-
};
|
|
224
251
|
const sharedKey = buildSharedCacheKey(this.request, configKey);
|
|
225
252
|
if (!options.forceRefresh && sharedBookingConfigCache.has(sharedKey)) {
|
|
226
253
|
const cached = sharedBookingConfigCache.get(sharedKey) ?? null;
|
|
@@ -232,7 +259,7 @@ class BookingContextModule extends _BaseModule.BaseModule {
|
|
|
232
259
|
if (!pending) {
|
|
233
260
|
const requestPromise = (async () => {
|
|
234
261
|
try {
|
|
235
|
-
const res = await this.request.get(
|
|
262
|
+
const res = await this.request.get(requestConfig.endpoint, requestConfig.query, BOARD_CONFIG_CACHE);
|
|
236
263
|
const config = res?.data ?? null;
|
|
237
264
|
sharedBookingConfigCache.set(sharedKey, config);
|
|
238
265
|
return config;
|
|
@@ -293,7 +320,7 @@ class BookingContextModule extends _BaseModule.BaseModule {
|
|
|
293
320
|
* store 内是否已具备与本次 init 入参等价的 config / resources(不再打接口)。
|
|
294
321
|
*/
|
|
295
322
|
isBookingContextReady(params = {}) {
|
|
296
|
-
const configKey =
|
|
323
|
+
const configKey = resolveBookingConfigRequest(this.otherParams?.platform, params.bookingConfigParams).key;
|
|
297
324
|
const dateInput = params.date ?? this.store.date ?? (0, _dayjs.default)();
|
|
298
325
|
const queryDate = toScheduleQueryDate(dateInput);
|
|
299
326
|
const resourcesKey = buildBookingResourcesQueryKey({
|
|
@@ -311,7 +338,7 @@ class BookingContextModule extends _BaseModule.BaseModule {
|
|
|
311
338
|
if (!params.forceRefresh && this.isBookingContextReady(params)) {
|
|
312
339
|
return this.getState();
|
|
313
340
|
}
|
|
314
|
-
const configKey =
|
|
341
|
+
const configKey = resolveBookingConfigRequest(this.otherParams?.platform, params.bookingConfigParams).key;
|
|
315
342
|
const resourcesKey = buildBookingResourcesQueryKey({
|
|
316
343
|
date: queryDate,
|
|
317
344
|
bookingIds: params.bookingIds
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BookingContext 子模块:承载「预约 UI 上下文」三件套。
|
|
3
3
|
*
|
|
4
|
-
* - bookingConfig:来自 `/core/board/management/config` 的整套预约配置(service_time / resource_tab /
|
|
4
|
+
* - bookingConfig:来自 staff `/core/board/management/config` 或 H5 `/core/board/config` 的整套预约配置(service_time / resource_tab /
|
|
5
5
|
* appointment_subject / subject_require / stock_setting / basic / capacity 等)。
|
|
6
6
|
* - resourcesOrigin / resourcesOriginMap:来自 `/schedule/resource/list` 的资源全集,含 times / event_list /
|
|
7
7
|
* combined_resource 等运行态信息,是 getResourceByIds / getResourceTimeIsUsable 的输入。
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* - 默认由 `loadBookingConfig` / `loadResources` 拉取并写入 store;也支持消费方 `set*` 直接灌入(复用 ticketBooking 已拉到的数据)。
|
|
12
12
|
* - 所有读 API 返回浅克隆(数组)或原引用(map / config),避免 UI 写穿。
|
|
13
13
|
*/
|
|
14
|
-
/** `loadBookingConfig`
|
|
14
|
+
/** `loadBookingConfig` 入参;H5 公共接口只消费 `item_type`。 */
|
|
15
15
|
export interface LoadBookingConfigParams {
|
|
16
16
|
item_type?: string;
|
|
17
17
|
sub_type?: string;
|
|
@@ -42,7 +42,7 @@ export interface InitBookingContextParams {
|
|
|
42
42
|
export type BookingContextResource = Record<string, any>;
|
|
43
43
|
export type BookingContextResourceMap = Record<string, BookingContextResource>;
|
|
44
44
|
/**
|
|
45
|
-
* 形参 booking_config(结构来自
|
|
45
|
+
* 形参 booking_config(结构来自 staff management 或 H5 board config 接口的 `data`)。
|
|
46
46
|
* OS 不强制 schema,保留 `Record<string, any>` 兼容现网;常用路径文档化在 SDK docs。
|
|
47
47
|
*/
|
|
48
48
|
export type BookingContextConfig = Record<string, any>;
|
|
@@ -80,7 +80,7 @@ export interface BookingContextModuleAPI {
|
|
|
80
80
|
getState(): BookingContextState;
|
|
81
81
|
/** 一键清空,destroy / 切租户场景用。 */
|
|
82
82
|
clear(): void;
|
|
83
|
-
/**
|
|
83
|
+
/** 按运行平台拉取 board config 并写入 store。 */
|
|
84
84
|
loadBookingConfig(params?: LoadBookingConfigParams): Promise<BookingContextConfig | null>;
|
|
85
85
|
/** 拉取资源全集并写入 store(`/schedule/resource/list`)。 */
|
|
86
86
|
loadResources(params?: LoadBookingResourcesParams): Promise<BookingContextResource[]>;
|
|
@@ -7,7 +7,7 @@ exports.BookingContextEvent = void 0;
|
|
|
7
7
|
/**
|
|
8
8
|
* BookingContext 子模块:承载「预约 UI 上下文」三件套。
|
|
9
9
|
*
|
|
10
|
-
* - bookingConfig:来自 `/core/board/management/config` 的整套预约配置(service_time / resource_tab /
|
|
10
|
+
* - bookingConfig:来自 staff `/core/board/management/config` 或 H5 `/core/board/config` 的整套预约配置(service_time / resource_tab /
|
|
11
11
|
* appointment_subject / subject_require / stock_setting / basic / capacity 等)。
|
|
12
12
|
* - resourcesOrigin / resourcesOriginMap:来自 `/schedule/resource/list` 的资源全集,含 times / event_list /
|
|
13
13
|
* combined_resource 等运行态信息,是 getResourceByIds / getResourceTimeIsUsable 的输入。
|
|
@@ -17,7 +17,7 @@ exports.BookingContextEvent = void 0;
|
|
|
17
17
|
* - 默认由 `loadBookingConfig` / `loadResources` 拉取并写入 store;也支持消费方 `set*` 直接灌入(复用 ticketBooking 已拉到的数据)。
|
|
18
18
|
* - 所有读 API 返回浅克隆(数组)或原引用(map / config),避免 UI 写穿。
|
|
19
19
|
*/
|
|
20
|
-
/** `loadBookingConfig`
|
|
20
|
+
/** `loadBookingConfig` 入参;H5 公共接口只消费 `item_type`。 */
|
|
21
21
|
/** `loadResources` 入参,与 info2 `getResourceList` 一致。 */
|
|
22
22
|
/** `initBookingContext` 入参(BookingTicket 聚合入口)。 */
|
|
23
23
|
/**
|
|
@@ -25,7 +25,7 @@ exports.BookingContextEvent = void 0;
|
|
|
25
25
|
* 这里只声明 Record 形态;具体字段保留 `any`,与 ticketBooking 现网传入的数据形状兼容。
|
|
26
26
|
*/
|
|
27
27
|
/**
|
|
28
|
-
* 形参 booking_config(结构来自
|
|
28
|
+
* 形参 booking_config(结构来自 staff management 或 H5 board config 接口的 `data`)。
|
|
29
29
|
* OS 不强制 schema,保留 `Record<string, any>` 兼容现网;常用路径文档化在 SDK docs。
|
|
30
30
|
*/
|
|
31
31
|
/**
|
|
@@ -17,7 +17,30 @@ export interface ResolveClientDataVariantsParams {
|
|
|
17
17
|
handledByOsServer: boolean;
|
|
18
18
|
scheduleList?: unknown;
|
|
19
19
|
menuList?: unknown;
|
|
20
|
+
/** Quote fast paths require hydrated relations; legacy list resolution does not. */
|
|
21
|
+
requireCompleteDataVariantRelations?: boolean;
|
|
22
|
+
/** Quote fast paths require every referenced rule; legacy list resolution is best-effort. */
|
|
23
|
+
requireEvaluatorReady?: boolean;
|
|
20
24
|
}
|
|
25
|
+
export type ResolveClientDataVariantsResult = {
|
|
26
|
+
status: 'handled-by-os-server' | 'empty' | 'data-variants-unavailable' | 'evaluator-unavailable' | 'evaluator-not-ready';
|
|
27
|
+
products: ProductData[];
|
|
28
|
+
} | {
|
|
29
|
+
status: 'resolved';
|
|
30
|
+
products: ProductData[];
|
|
31
|
+
} | {
|
|
32
|
+
status: 'error';
|
|
33
|
+
products: ProductData[];
|
|
34
|
+
error: unknown;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Strict client-side Data Variant resolution for quote fast paths.
|
|
38
|
+
*
|
|
39
|
+
* Unlike resolveClientDataVariants, callers can distinguish a complete local
|
|
40
|
+
* result from a capability/readiness/error fallback and decide to query the
|
|
41
|
+
* remote product endpoint instead.
|
|
42
|
+
*/
|
|
43
|
+
export declare function tryResolveClientDataVariants({ core, otherParams, products, queryPayload, handledByOsServer, scheduleList: explicitScheduleList, menuList: explicitMenuList, requireCompleteDataVariantRelations, requireEvaluatorReady, }: ResolveClientDataVariantsParams): ResolveClientDataVariantsResult;
|
|
21
44
|
/**
|
|
22
45
|
* 未启用商品 OS 路由时,在客户端补做 Data Variant 解析。
|
|
23
46
|
*
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.PRODUCT_QUERY_DATA_VARIANT_RELATIONS = void 0;
|
|
7
7
|
exports.isProductQueryHandledByOsServer = isProductQueryHandledByOsServer;
|
|
8
8
|
exports.resolveClientDataVariants = resolveClientDataVariants;
|
|
9
|
+
exports.tryResolveClientDataVariants = tryResolveClientDataVariants;
|
|
9
10
|
const PRODUCT_QUERY_DATA_VARIANT_RELATIONS = exports.PRODUCT_QUERY_DATA_VARIANT_RELATIONS = ['category', 'collection', 'resourceRelation', 'bundleGroup.bundleItem', 'optionGroup.optionItem', 'variantGroup.variantItem', 'dataVariants'];
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -20,25 +21,115 @@ function isProductQueryHandledByOsServer(core, request) {
|
|
|
20
21
|
const requestBaseUrl = String(request?.baseUrl || '/shop').replace(/\/+$/, '');
|
|
21
22
|
return server.hasRoute('post', `${requestBaseUrl}/product/query`) === true;
|
|
22
23
|
}
|
|
24
|
+
function getBundleItems(product) {
|
|
25
|
+
if (!Array.isArray(product.bundle_group)) return [];
|
|
26
|
+
return product.bundle_group.flatMap(group => Array.isArray(group?.bundle_item) ? group.bundle_item : []);
|
|
27
|
+
}
|
|
28
|
+
function hasCompleteDataVariantRelations(products) {
|
|
29
|
+
return products.every(product => Array.isArray(product?.data_variants));
|
|
30
|
+
}
|
|
31
|
+
function hasAnyDataVariants(products) {
|
|
32
|
+
return products.some(product => (product.data_variants?.length || 0) > 0 || getBundleItems(product).some(item => Array.isArray(item?.data_variants) && item.data_variants.length > 0));
|
|
33
|
+
}
|
|
34
|
+
function collectReferencedRuleIds(products) {
|
|
35
|
+
const ids = new Set();
|
|
36
|
+
const remember = variants => {
|
|
37
|
+
if (!Array.isArray(variants)) return;
|
|
38
|
+
variants.forEach(variant => {
|
|
39
|
+
const id = String(variant?.data_variant_rule_id ?? '').trim();
|
|
40
|
+
if (id) ids.add(id);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
products.forEach(product => {
|
|
44
|
+
remember(product.data_variants);
|
|
45
|
+
getBundleItems(product).forEach(item => remember(item?.data_variants));
|
|
46
|
+
});
|
|
47
|
+
return ids;
|
|
48
|
+
}
|
|
49
|
+
function isEvaluatorReady(evaluator, products) {
|
|
50
|
+
if (typeof evaluator.isReady === 'function') {
|
|
51
|
+
if (evaluator.isReady() === false) return false;
|
|
52
|
+
}
|
|
53
|
+
if (evaluator.isReady === false || evaluator.ready === false) return false;
|
|
54
|
+
|
|
55
|
+
// The built-in evaluator is installed before its strategy request completes.
|
|
56
|
+
// A referenced Data Variant cannot be evaluated authoritatively until those
|
|
57
|
+
// configs have arrived. Evaluators without this capability remain compatible.
|
|
58
|
+
if (typeof evaluator.getStrategyConfigs === 'function') {
|
|
59
|
+
const configs = evaluator.getStrategyConfigs();
|
|
60
|
+
if (!Array.isArray(configs) || configs.length === 0) return false;
|
|
61
|
+
const availableRuleIds = new Set(configs.map(config => String(config?.metadata?.id ?? '').trim()).filter(Boolean));
|
|
62
|
+
return Array.from(collectReferencedRuleIds(products)).every(id => availableRuleIds.has(id));
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
23
67
|
/**
|
|
24
|
-
*
|
|
68
|
+
* Strict client-side Data Variant resolution for quote fast paths.
|
|
25
69
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
70
|
+
* Unlike resolveClientDataVariants, callers can distinguish a complete local
|
|
71
|
+
* result from a capability/readiness/error fallback and decide to query the
|
|
72
|
+
* remote product endpoint instead.
|
|
28
73
|
*/
|
|
29
|
-
function
|
|
74
|
+
function tryResolveClientDataVariants({
|
|
30
75
|
core,
|
|
31
76
|
otherParams = {},
|
|
32
77
|
products,
|
|
33
78
|
queryPayload,
|
|
34
79
|
handledByOsServer,
|
|
35
80
|
scheduleList: explicitScheduleList,
|
|
36
|
-
menuList: explicitMenuList
|
|
81
|
+
menuList: explicitMenuList,
|
|
82
|
+
requireCompleteDataVariantRelations = true,
|
|
83
|
+
requireEvaluatorReady = true
|
|
37
84
|
}) {
|
|
38
|
-
if (handledByOsServer
|
|
85
|
+
if (handledByOsServer) {
|
|
86
|
+
return {
|
|
87
|
+
status: 'handled-by-os-server',
|
|
88
|
+
products
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (products.length === 0) {
|
|
92
|
+
return {
|
|
93
|
+
status: 'empty',
|
|
94
|
+
products
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
if (requireCompleteDataVariantRelations && !hasCompleteDataVariantRelations(products)) {
|
|
98
|
+
return {
|
|
99
|
+
status: 'data-variants-unavailable',
|
|
100
|
+
products
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Explicit empty arrays mean the query hydrated the relation and there is no
|
|
105
|
+
// rule to apply. This is an authoritative local base-price result even when
|
|
106
|
+
// no evaluator has been installed.
|
|
107
|
+
if (!hasAnyDataVariants(products)) {
|
|
108
|
+
return {
|
|
109
|
+
status: 'resolved',
|
|
110
|
+
products
|
|
111
|
+
};
|
|
112
|
+
}
|
|
39
113
|
const evaluator = core?.context?.dataVariantEvaluator;
|
|
40
114
|
if (!evaluator || typeof evaluator.resolveProducts !== 'function') {
|
|
41
|
-
return
|
|
115
|
+
return {
|
|
116
|
+
status: 'evaluator-unavailable',
|
|
117
|
+
products
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
if (requireEvaluatorReady && !isEvaluatorReady(evaluator, products)) {
|
|
122
|
+
return {
|
|
123
|
+
status: 'evaluator-not-ready',
|
|
124
|
+
products
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return {
|
|
129
|
+
status: 'error',
|
|
130
|
+
products,
|
|
131
|
+
error
|
|
132
|
+
};
|
|
42
133
|
}
|
|
43
134
|
const strategyContext = queryPayload.strategy_context && typeof queryPayload.strategy_context === 'object' ? queryPayload.strategy_context : {};
|
|
44
135
|
const fatherModule = String(otherParams.fatherModule || '').trim();
|
|
@@ -49,7 +140,7 @@ function resolveClientDataVariants({
|
|
|
49
140
|
const businessData = {
|
|
50
141
|
products,
|
|
51
142
|
scheduleDateTime: queryPayload.schedule_datetime || queryPayload.schedule_date,
|
|
52
|
-
scheduleList: Array.isArray(explicitScheduleList) ? explicitScheduleList : Array.isArray(moduleScheduleList) ? moduleScheduleList : Array.isArray(contextScheduleList) ? contextScheduleList : [],
|
|
143
|
+
scheduleList: Array.isArray(explicitScheduleList) && explicitScheduleList.length > 0 ? explicitScheduleList : Array.isArray(moduleScheduleList) && moduleScheduleList.length > 0 ? moduleScheduleList : Array.isArray(contextScheduleList) ? contextScheduleList : [],
|
|
53
144
|
menuList: Array.isArray(explicitMenuList) ? explicitMenuList : Array.isArray(otherParams.menuList) ? otherParams.menuList : Array.isArray(contextMenuList) ? contextMenuList : [],
|
|
54
145
|
customerId: queryPayload.customer_id,
|
|
55
146
|
channel: strategyContext.channel,
|
|
@@ -60,9 +151,54 @@ function resolveClientDataVariants({
|
|
|
60
151
|
};
|
|
61
152
|
try {
|
|
62
153
|
const result = evaluator.resolveProducts(businessData);
|
|
63
|
-
|
|
154
|
+
if (!Array.isArray(result?.products)) {
|
|
155
|
+
return {
|
|
156
|
+
status: 'error',
|
|
157
|
+
products,
|
|
158
|
+
error: new Error('Data Variant evaluator returned an invalid product list')
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
status: 'resolved',
|
|
163
|
+
products: result.products
|
|
164
|
+
};
|
|
64
165
|
} catch (error) {
|
|
65
|
-
|
|
66
|
-
|
|
166
|
+
return {
|
|
167
|
+
status: 'error',
|
|
168
|
+
products,
|
|
169
|
+
error
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 未启用商品 OS 路由时,在客户端补做 Data Variant 解析。
|
|
176
|
+
*
|
|
177
|
+
* 商品列表和预约报价必须共用这个边界,避免 H5 的弹窗报价绕过客户端智能定价。
|
|
178
|
+
* OS Server 已接管时直接信任服务端结果,确保同一批商品只评估一次。
|
|
179
|
+
*/
|
|
180
|
+
function resolveClientDataVariants({
|
|
181
|
+
core,
|
|
182
|
+
otherParams = {},
|
|
183
|
+
products,
|
|
184
|
+
queryPayload,
|
|
185
|
+
handledByOsServer,
|
|
186
|
+
scheduleList: explicitScheduleList,
|
|
187
|
+
menuList: explicitMenuList
|
|
188
|
+
}) {
|
|
189
|
+
const result = tryResolveClientDataVariants({
|
|
190
|
+
core,
|
|
191
|
+
otherParams,
|
|
192
|
+
products,
|
|
193
|
+
queryPayload,
|
|
194
|
+
handledByOsServer,
|
|
195
|
+
scheduleList: explicitScheduleList,
|
|
196
|
+
menuList: explicitMenuList,
|
|
197
|
+
requireCompleteDataVariantRelations: false,
|
|
198
|
+
requireEvaluatorReady: false
|
|
199
|
+
});
|
|
200
|
+
if (result.status === 'error') {
|
|
201
|
+
console.error('[ClientDataVariants] Data Variant 客户端解析失败', result.error);
|
|
67
202
|
}
|
|
203
|
+
return result.products;
|
|
68
204
|
}
|
|
@@ -9,6 +9,7 @@ export declare class ProductList extends BaseModule implements Module {
|
|
|
9
9
|
private store;
|
|
10
10
|
private request;
|
|
11
11
|
private otherParams;
|
|
12
|
+
private rawProductPriceSources;
|
|
12
13
|
constructor(name?: string, version?: string);
|
|
13
14
|
initialize(core: PisellCore, options: any): Promise<void>;
|
|
14
15
|
/**
|
|
@@ -36,6 +37,19 @@ export declare class ProductList extends BaseModule implements Module {
|
|
|
36
37
|
schedule_date?: string;
|
|
37
38
|
channel?: string;
|
|
38
39
|
}): Promise<any>;
|
|
40
|
+
/**
|
|
41
|
+
* Remember raw remote product records before client-side Data Variant
|
|
42
|
+
* evaluation. Quote-only reads can then always start from a clean baseline.
|
|
43
|
+
*/
|
|
44
|
+
rememberRawProductQueryResult(products: Array<ProductData | Record<string, any>>): void;
|
|
45
|
+
private replaceRawProductQueryResult;
|
|
46
|
+
/**
|
|
47
|
+
* Return isolated raw price sources for the requested products.
|
|
48
|
+
*
|
|
49
|
+
* Missing IDs are intentionally omitted so callers can remotely fetch only
|
|
50
|
+
* the incomplete part of a batch quote.
|
|
51
|
+
*/
|
|
52
|
+
getRawProductPriceSources(ids: number[]): Map<number, ProductData>;
|
|
39
53
|
getProducts(): Promise<ProductData[]>;
|
|
40
54
|
getProduct(id: number): Promise<ProductData | undefined>;
|
|
41
55
|
getProductByIds(ids: number[]): Promise<ProductData[] | undefined>;
|
|
@@ -11,6 +11,7 @@ var _BaseModule = require("../BaseModule");
|
|
|
11
11
|
var _lodashEs = require("lodash-es");
|
|
12
12
|
var _dayjs = _interopRequireDefault(require("dayjs"));
|
|
13
13
|
var _clientDataVariants = require("./clientDataVariants");
|
|
14
|
+
var _platform = require("../../utils/platform");
|
|
14
15
|
var _types = require("./types");
|
|
15
16
|
Object.keys(_types).forEach(function (key) {
|
|
16
17
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -30,6 +31,7 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
30
31
|
store;
|
|
31
32
|
request;
|
|
32
33
|
otherParams = {};
|
|
34
|
+
rawProductPriceSources = new Map();
|
|
33
35
|
constructor(name, version) {
|
|
34
36
|
super(name, version);
|
|
35
37
|
}
|
|
@@ -37,6 +39,7 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
37
39
|
this.core = core;
|
|
38
40
|
this.store = options.store;
|
|
39
41
|
this.otherParams = options.otherParams || {};
|
|
42
|
+
this.rawProductPriceSources.clear();
|
|
40
43
|
if (Array.isArray(options.initialState?.list)) {
|
|
41
44
|
this.store.list = options.initialState.list.slice().sort((a, b) => Number(b.sort) - Number(a.sort));
|
|
42
45
|
this.core.effects.emit(`${this.name}:changed`, this.store.list);
|
|
@@ -119,7 +122,9 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
119
122
|
const rawApplicationCode = this.otherParams?.channel;
|
|
120
123
|
const mappedApplicationCode = typeof rawApplicationCode === 'string' ? channelMap[rawApplicationCode] || rawApplicationCode : rawApplicationCode;
|
|
121
124
|
const queryPayload = {
|
|
122
|
-
|
|
125
|
+
...((0, _platform.isCustomerUserPlatform)(this.otherParams?.platform) ? {} : {
|
|
126
|
+
open_quotation: 1
|
|
127
|
+
}),
|
|
123
128
|
open_bundle: 0,
|
|
124
129
|
exclude_extension_type: ['product_party', 'product_event', 'product_series_event', 'product_package_ticket', 'ticket', 'event_item'],
|
|
125
130
|
with: [..._clientDataVariants.PRODUCT_QUERY_DATA_VARIANT_RELATIONS],
|
|
@@ -158,6 +163,11 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
158
163
|
originalCallback(result);
|
|
159
164
|
return;
|
|
160
165
|
}
|
|
166
|
+
if (!handledByOsServer) {
|
|
167
|
+
// Subscription callbacks may contain only changed products. Upsert
|
|
168
|
+
// their raw records without evicting other IDs from the scope.
|
|
169
|
+
this.rememberRawProductQueryResult(callbackList);
|
|
170
|
+
}
|
|
161
171
|
const products = (0, _clientDataVariants.resolveClientDataVariants)({
|
|
162
172
|
core: this.core,
|
|
163
173
|
otherParams: this.otherParams,
|
|
@@ -180,6 +190,9 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
180
190
|
subscriberId: options?.subscriberId,
|
|
181
191
|
customToast: () => {}
|
|
182
192
|
});
|
|
193
|
+
if (!handledByOsServer && Array.isArray(productsData.data.list)) {
|
|
194
|
+
this.replaceRawProductQueryResult(productsData.data.list, product_ids);
|
|
195
|
+
}
|
|
183
196
|
const resolvedList = (0, _clientDataVariants.resolveClientDataVariants)({
|
|
184
197
|
core: this.core,
|
|
185
198
|
otherParams: this.otherParams,
|
|
@@ -214,6 +227,51 @@ class ProductList extends _BaseModule.BaseModule {
|
|
|
214
227
|
});
|
|
215
228
|
return productsData.data;
|
|
216
229
|
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Remember raw remote product records before client-side Data Variant
|
|
233
|
+
* evaluation. Quote-only reads can then always start from a clean baseline.
|
|
234
|
+
*/
|
|
235
|
+
rememberRawProductQueryResult(products) {
|
|
236
|
+
if (!Array.isArray(products)) return;
|
|
237
|
+
products.forEach(product => {
|
|
238
|
+
const source = product;
|
|
239
|
+
const id = Number(source?.id ?? source?.product_id);
|
|
240
|
+
if (!Number.isFinite(id)) return;
|
|
241
|
+
this.rawProductPriceSources.set(id, (0, _lodashEs.cloneDeep)(product));
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
replaceRawProductQueryResult(products, requestedIds) {
|
|
245
|
+
if (Array.isArray(requestedIds) && requestedIds.length > 0) {
|
|
246
|
+
requestedIds.forEach(rawId => {
|
|
247
|
+
const id = Number(rawId);
|
|
248
|
+
if (Number.isFinite(id)) this.rawProductPriceSources.delete(id);
|
|
249
|
+
});
|
|
250
|
+
} else {
|
|
251
|
+
// A non-ID query represents the latest catalog scope. Clearing before
|
|
252
|
+
// replacing prevents removed products from surviving indefinitely.
|
|
253
|
+
this.rawProductPriceSources.clear();
|
|
254
|
+
}
|
|
255
|
+
this.rememberRawProductQueryResult(products);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Return isolated raw price sources for the requested products.
|
|
260
|
+
*
|
|
261
|
+
* Missing IDs are intentionally omitted so callers can remotely fetch only
|
|
262
|
+
* the incomplete part of a batch quote.
|
|
263
|
+
*/
|
|
264
|
+
getRawProductPriceSources(ids) {
|
|
265
|
+
const products = new Map();
|
|
266
|
+
if (!Array.isArray(ids)) return products;
|
|
267
|
+
ids.forEach(rawId => {
|
|
268
|
+
const id = Number(rawId);
|
|
269
|
+
if (!Number.isFinite(id) || products.has(id)) return;
|
|
270
|
+
const product = this.rawProductPriceSources.get(id);
|
|
271
|
+
if (product) products.set(id, (0, _lodashEs.cloneDeep)(product));
|
|
272
|
+
});
|
|
273
|
+
return products;
|
|
274
|
+
}
|
|
217
275
|
async getProducts() {
|
|
218
276
|
await this.core.effects.emit(`${this.name}:onGetProducts`, this.store.list);
|
|
219
277
|
return (0, _lodashEs.cloneDeep)(this.store.list);
|
|
@@ -129,8 +129,23 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
|
|
|
129
129
|
*/
|
|
130
130
|
protected preferAuthoritativeProductQueryPrice(): boolean;
|
|
131
131
|
private getPriceQuerySchedule;
|
|
132
|
+
private isCustomerUserPriceQuery;
|
|
133
|
+
private getPriceQueryScheduleList;
|
|
134
|
+
private buildPriceQueryPayload;
|
|
135
|
+
private getRawProductPriceSources;
|
|
136
|
+
private rememberRawProductPriceSources;
|
|
137
|
+
private getSelectedProductVariantId;
|
|
138
|
+
/**
|
|
139
|
+
* 将目录原始商品还原为本次选择的 SKU 基线。
|
|
140
|
+
*
|
|
141
|
+
* 这里绝不能复用已评估后的目录商品,否则从命中智能价切回不命中的
|
|
142
|
+
* 日期时会残留旧价格。套餐仅验证所选关系存在,最终仍由 merge 方法把
|
|
143
|
+
* 评估后的套餐价格映射回用户的选择数据。
|
|
144
|
+
*/
|
|
145
|
+
private prepareRawProductForPriceQuery;
|
|
146
|
+
private resolveLocalProductForPriceQuery;
|
|
132
147
|
private loadProductsForPriceQuery;
|
|
133
|
-
private
|
|
148
|
+
private resolveProductsForPriceQuery;
|
|
134
149
|
private getAuthoritativeBundleItems;
|
|
135
150
|
private findAuthoritativeBundleItem;
|
|
136
151
|
private getAuthoritativeBundleUnitPrice;
|