@pisell/pisellos 2.3.81 → 2.3.82

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.
@@ -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 | 3 | 4 | 5 | 6;
329
+ weekNum: 0 | 1 | 2 | 3 | 4 | 5 | 6;
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, "num" | "skip">;
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
- var platform = String(((_this$otherParams14 = this.otherParams) === null || _this$otherParams14 === void 0 ? void 0 : _this$otherParams14.platform) || '').toLowerCase();
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
+ }
@@ -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
- * 未启用商品 OS 路由时,在客户端补做 Data Variant 解析。
68
+ * Strict client-side Data Variant resolution for quote fast paths.
25
69
  *
26
- * 商品列表和预约报价必须共用这个边界,避免 H5 的弹窗报价绕过客户端智能定价。
27
- * OS Server 已接管时直接信任服务端结果,确保同一批商品只评估一次。
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 resolveClientDataVariants({
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 || products.length === 0) return products;
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 products;
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
- return Array.isArray(result?.products) ? result.products : products;
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
- console.error('[ClientDataVariants] Data Variant 客户端解析失败', error);
66
- return products;
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
- open_quotation: 1,
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 loadProductForPriceQuery;
148
+ private resolveProductsForPriceQuery;
134
149
  private getAuthoritativeBundleItems;
135
150
  private findAuthoritativeBundleItem;
136
151
  private getAuthoritativeBundleUnitPrice;