@pisell/pisellos 2.3.30 → 2.3.32

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.
Files changed (73) hide show
  1. package/dist/model/strategy/adapter/dataVariant/adapter.d.ts +50 -0
  2. package/dist/model/strategy/adapter/dataVariant/adapter.js +167 -0
  3. package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +89 -0
  4. package/dist/model/strategy/adapter/dataVariant/evaluator.js +780 -0
  5. package/dist/model/strategy/adapter/dataVariant/examples.d.ts +39 -0
  6. package/dist/model/strategy/adapter/dataVariant/examples.js +277 -0
  7. package/dist/model/strategy/adapter/dataVariant/index.d.ts +4 -0
  8. package/dist/model/strategy/adapter/dataVariant/index.js +4 -0
  9. package/dist/model/strategy/adapter/dataVariant/type.d.ts +148 -0
  10. package/dist/model/strategy/adapter/dataVariant/type.js +54 -0
  11. package/dist/model/strategy/adapter/index.d.ts +4 -0
  12. package/dist/model/strategy/adapter/index.js +5 -1
  13. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  14. package/dist/modules/Discount/index.d.ts +5 -2
  15. package/dist/modules/Discount/index.js +30 -7
  16. package/dist/modules/Discount/types.d.ts +4 -0
  17. package/dist/modules/Order/index.d.ts +2 -1
  18. package/dist/modules/Order/index.js +25 -11
  19. package/dist/modules/Order/types.d.ts +2 -0
  20. package/dist/modules/Product/index.d.ts +1 -1
  21. package/dist/modules/Product/types.d.ts +22 -0
  22. package/dist/modules/ProductList/index.d.ts +1 -1
  23. package/dist/modules/ProductList/index.js +4 -2
  24. package/dist/modules/ProductList/types.d.ts +2 -0
  25. package/dist/server/index.d.ts +50 -0
  26. package/dist/server/index.js +1095 -849
  27. package/dist/server/modules/order/index.d.ts +8 -3
  28. package/dist/server/modules/order/index.js +765 -675
  29. package/dist/server/modules/products/index.d.ts +26 -7
  30. package/dist/server/modules/products/index.js +167 -76
  31. package/dist/server/modules/products/types.d.ts +14 -0
  32. package/dist/solution/BaseSales/index.d.ts +2 -0
  33. package/dist/solution/BaseSales/index.js +23 -13
  34. package/dist/solution/BookingByStep/index.d.ts +1 -1
  35. package/dist/solution/BookingTicket/types.d.ts +2 -0
  36. package/dist/solution/ShopDiscount/index.js +15 -14
  37. package/lib/model/strategy/adapter/dataVariant/adapter.d.ts +50 -0
  38. package/lib/model/strategy/adapter/dataVariant/adapter.js +149 -0
  39. package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +89 -0
  40. package/lib/model/strategy/adapter/dataVariant/evaluator.js +583 -0
  41. package/lib/model/strategy/adapter/dataVariant/examples.d.ts +39 -0
  42. package/lib/model/strategy/adapter/dataVariant/examples.js +293 -0
  43. package/lib/model/strategy/adapter/dataVariant/index.d.ts +4 -0
  44. package/lib/model/strategy/adapter/dataVariant/index.js +38 -0
  45. package/lib/model/strategy/adapter/dataVariant/type.d.ts +148 -0
  46. package/lib/model/strategy/adapter/dataVariant/type.js +31 -0
  47. package/lib/model/strategy/adapter/index.d.ts +4 -0
  48. package/lib/model/strategy/adapter/index.js +13 -2
  49. package/lib/model/strategy/adapter/promotion/index.js +0 -49
  50. package/lib/modules/Discount/index.d.ts +5 -2
  51. package/lib/modules/Discount/index.js +23 -3
  52. package/lib/modules/Discount/types.d.ts +4 -0
  53. package/lib/modules/Order/index.d.ts +2 -1
  54. package/lib/modules/Order/index.js +13 -1
  55. package/lib/modules/Order/types.d.ts +2 -0
  56. package/lib/modules/Product/index.d.ts +1 -1
  57. package/lib/modules/Product/types.d.ts +22 -0
  58. package/lib/modules/ProductList/index.d.ts +1 -1
  59. package/lib/modules/ProductList/index.js +4 -2
  60. package/lib/modules/ProductList/types.d.ts +2 -0
  61. package/lib/server/index.d.ts +50 -0
  62. package/lib/server/index.js +206 -29
  63. package/lib/server/modules/order/index.d.ts +8 -3
  64. package/lib/server/modules/order/index.js +152 -43
  65. package/lib/server/modules/products/index.d.ts +26 -7
  66. package/lib/server/modules/products/index.js +114 -35
  67. package/lib/server/modules/products/types.d.ts +14 -0
  68. package/lib/solution/BaseSales/index.d.ts +2 -0
  69. package/lib/solution/BaseSales/index.js +12 -3
  70. package/lib/solution/BookingByStep/index.d.ts +1 -1
  71. package/lib/solution/BookingTicket/types.d.ts +2 -0
  72. package/lib/solution/ShopDiscount/index.js +2 -1
  73. package/package.json +1 -1
@@ -68,6 +68,10 @@ export interface DiscountWithReason {
68
68
  discountList: Discount[];
69
69
  unavailableReasonKey: DiscountFilterRejectKey | null;
70
70
  }
71
+ export interface DiscountPrepareConfigResult {
72
+ discountList: Discount[];
73
+ availableWalletIds: number[];
74
+ }
71
75
  interface ExtensionData {
72
76
  id: number;
73
77
  shop_id: number;
@@ -76,6 +76,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
76
76
  apply?: boolean;
77
77
  }): Promise<Discount[]>;
78
78
  getDiscountList(): Discount[];
79
+ getAvailableWalletIds(): number[];
79
80
  private syncScannedDiscountsToOriginalDiscountList;
80
81
  private ensureEditDiscountConfigForProductChange;
81
82
  scanCode(code: string, customerId?: number): Promise<{
@@ -484,7 +485,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
484
485
  generateIdempotencyToken(): string;
485
486
  syncPaymentsToOrder<T = any>(params: SyncPaymentsToOrderParams): Promise<SyncPaymentsToOrderResult<T>>;
486
487
  createOrder(params: CommitOrderParams['query']): {
487
- type: "virtual" | "appointment_booking";
488
+ type: "appointment_booking" | "virtual";
488
489
  platform: string;
489
490
  sales_channel: string;
490
491
  order_sales_channel: string;
@@ -311,6 +311,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
311
311
  if (!this.store.summary) {
312
312
  this.store.summary = (0, import_utils.createEmptySummary)();
313
313
  }
314
+ if (!Array.isArray(this.store.availableWalletIds)) {
315
+ this.store.availableWalletIds = [];
316
+ }
314
317
  this.request = this.core.getPlugin("request");
315
318
  this.appPlugin = this.core.getPlugin("app");
316
319
  if (!this.appPlugin) {
@@ -438,15 +441,19 @@ var OrderModule = class extends import_BaseModule.BaseModule {
438
441
  async loadDiscountConfig(params) {
439
442
  var _a, _b, _c, _d, _e, _f, _g;
440
443
  const orderId = params.orderId || ((_a = this.store.tempOrder) == null ? void 0 : _a.order_id) || void 0;
441
- const discountList = await ((_b = this.store.discount) == null ? void 0 : _b.loadPrepareConfig({
444
+ const prepareConfigResult = await ((_b = this.store.discount) == null ? void 0 : _b.loadPrepareConfig({
442
445
  customer_id: params.customerId,
443
446
  action: params.action || (orderId ? "edit" : "create"),
444
447
  with_good_pass: 1,
445
448
  with_discount_card: 1,
446
449
  with_wallet_pass_holder: 1,
450
+ with_available_wallet_flag: 1,
451
+ with_available_wallet_pass_flag: 1,
447
452
  request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
448
453
  ...orderId ? { order_id: orderId } : {}
449
454
  }));
455
+ const discountList = prepareConfigResult == null ? void 0 : prepareConfigResult.discountList;
456
+ this.store.availableWalletIds = (prepareConfigResult == null ? void 0 : prepareConfigResult.availableWalletIds) || [];
450
457
  if (discountList) {
451
458
  const currentDiscountList = ((_c = this.store.discount) == null ? void 0 : _c.getDiscountList()) || [];
452
459
  const currentEditDiscounts = currentDiscountList.filter((discount) => discount == null ? void 0 : discount.isEditMode);
@@ -480,6 +487,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
480
487
  var _a;
481
488
  return ((_a = this.store.discount) == null ? void 0 : _a.getDiscountList()) || [];
482
489
  }
490
+ getAvailableWalletIds() {
491
+ return Array.isArray(this.store.availableWalletIds) ? [...this.store.availableWalletIds] : [];
492
+ }
483
493
  async syncScannedDiscountsToOriginalDiscountList(discountList) {
484
494
  var _a, _b, _c, _d;
485
495
  const scanDiscounts = (discountList || []).filter((discount) => discount == null ? void 0 : discount.isScan);
@@ -2427,6 +2437,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
2427
2437
  }
2428
2438
  const nextCustomerId = tempOrder.customer_id ?? null;
2429
2439
  if (String(previousCustomerId ?? "") !== String(nextCustomerId ?? "")) {
2440
+ this.store.availableWalletIds = [];
2430
2441
  (0, import_utils.clearTempOrderHolderAssignments)(tempOrder);
2431
2442
  this.clearCustomerBoundDiscounts(tempOrder);
2432
2443
  this.applyDiscount();
@@ -2466,6 +2477,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
2466
2477
  tempOrder.customer = null;
2467
2478
  if (tempOrder._extend)
2468
2479
  delete tempOrder._extend.customerSnapshot;
2480
+ this.store.availableWalletIds = [];
2469
2481
  this.clearCustomerBoundDiscounts(tempOrder);
2470
2482
  this.persistTempOrder();
2471
2483
  this.saveDraftInBackground();
@@ -309,6 +309,7 @@ export interface OrderState {
309
309
  lastOrderInfo?: Record<string, any>;
310
310
  syncState?: OrderSyncState;
311
311
  discountList: any[];
312
+ availableWalletIds?: number[];
312
313
  discount: DiscountModule | null;
313
314
  rules: RulesModule | null;
314
315
  }
@@ -807,6 +808,7 @@ export interface OrderModuleAPI {
807
808
  apply?: boolean;
808
809
  }) => Promise<Discount[]>;
809
810
  getDiscountList: () => Discount[];
811
+ getAvailableWalletIds: () => number[];
810
812
  applyDiscount: () => void;
811
813
  /**
812
814
  * 注入促销评估器。
@@ -49,5 +49,5 @@ export declare class Product extends BaseModule implements Module {
49
49
  getCategories(): ProductCategory[];
50
50
  setOtherParams(key: string, value: any): void;
51
51
  getOtherParams(): any;
52
- getProductType(): "duration" | "session" | "normal";
52
+ getProductType(): "normal" | "duration" | "session";
53
53
  }
@@ -9,6 +9,26 @@ export interface ProductCollection {
9
9
  /** 商品集合封面 */
10
10
  cover: string;
11
11
  }
12
+ /**
13
+ * 商品 Data Variant 记录,结构与后端 data_variant 表对齐。
14
+ */
15
+ export interface ProductDataVariant {
16
+ /** 变体记录 id */
17
+ id: number;
18
+ /** 店铺 id */
19
+ shop_id: number;
20
+ /** 所属模块,商品场景固定为 product */
21
+ module: string;
22
+ /** 模块数据 id,商品场景对应 product.id */
23
+ module_data_id: number;
24
+ /** 关联 data_variant_rule.id */
25
+ data_variant_rule_id: number;
26
+ /** 命中策略后覆盖到商品上的字段 */
27
+ data: Record<string, any>;
28
+ created_at: string | null;
29
+ updated_at: string | null;
30
+ deleted_at: string | null;
31
+ }
12
32
  /**
13
33
  * 商品基本信息接口
14
34
  */
@@ -214,6 +234,8 @@ export interface ProductData {
214
234
  bundle_group_count: number;
215
235
  /** 选项组数量 */
216
236
  option_group_count: number;
237
+ /** 智能定价变体列表,/product/query with dataVariants 返回 */
238
+ data_variants?: ProductDataVariant[];
217
239
  /** 服务时长 */
218
240
  service_times?: any;
219
241
  }
@@ -26,7 +26,7 @@ export declare class ProductList extends BaseModule implements Module {
26
26
  * const products = await productList.getAddTimeProducts({ schedule_date: '2026-06-16' });
27
27
  */
28
28
  getAddTimeProducts(params?: ProductListLoadProductsParams): Promise<any>;
29
- loadProducts({ category_ids, product_ids, collection, menu_list_ids, customer_id: paramsCustomerId, with_count, schedule_datetime, schedule_date, cacheId, with_schedule, extension_type, status, }?: ProductListLoadProductsParams, options?: {
29
+ loadProducts({ category_ids, product_ids, collection, menu_list_ids, customer_id: paramsCustomerId, with_count, schedule_datetime, schedule_date, cacheId, with_schedule, extension_type, status, strategy_context, }?: ProductListLoadProductsParams, options?: {
30
30
  callback?: (result: any) => void;
31
31
  subscriberId?: string;
32
32
  }): Promise<any>;
@@ -95,7 +95,8 @@ var ProductList = class extends import_BaseModule.BaseModule {
95
95
  cacheId,
96
96
  with_schedule,
97
97
  extension_type,
98
- status = "published"
98
+ status = "published",
99
+ strategy_context
99
100
  } = {}, options) {
100
101
  var _a, _b;
101
102
  let userPlugin = this.core.getPlugin("user");
@@ -135,7 +136,8 @@ var ProductList = class extends import_BaseModule.BaseModule {
135
136
  is_eject: 1,
136
137
  with_schedule,
137
138
  schedule_datetime,
138
- extension_type
139
+ extension_type,
140
+ strategy_context
139
141
  },
140
142
  { osServer: true, callback: options == null ? void 0 : options.callback, subscriberId: options == null ? void 0 : options.subscriberId, customToast: () => {
141
143
  } }
@@ -12,6 +12,8 @@ export interface ProductListLoadProductsParams {
12
12
  with_schedule?: number;
13
13
  extension_type?: string[];
14
14
  status?: string;
15
+ /** 智能定价条件上下文,仅影响策略命中,不参与商品集合筛选 */
16
+ strategy_context?: Record<string, any>;
15
17
  }
16
18
  export interface ProductListData {
17
19
  list: ProductData[];
@@ -47,6 +47,8 @@ declare class Server {
47
47
  private prefixRouterGet;
48
48
  router: Router;
49
49
  private productQuerySubscribers;
50
+ /** subscriberId → 智能定价变价时间点定时器句柄(与 subscriber 分离存储,便于 clear) */
51
+ private productQueryScheduleTimers;
50
52
  private orderQuerySubscribers;
51
53
  private bookingQuerySubscribers;
52
54
  private bookingRemoteQuerySubscribers;
@@ -211,6 +213,48 @@ declare class Server {
211
213
  * 根据 subscriberId 移除商品查询订阅者
212
214
  */
213
215
  removeProductQuerySubscriber(subscriberId?: string): void;
216
+ /**
217
+ * 构建商品 query 响应 data 段,附带智能定价 schedule_time_points 元数据。
218
+ */
219
+ private buildProductQueryResultPayload;
220
+ /**
221
+ * 是否启用商品 query 订阅的 schedule 时间点定时重算。
222
+ * strategy_context.enable_schedule_reload === false 时关闭。
223
+ */
224
+ private shouldScheduleProductQueryReload;
225
+ /**
226
+ * 清除指定 subscriber 的全部 schedule 定时器,防止重复 query 或 unsubscribe 后泄漏。
227
+ */
228
+ private clearSubscriberScheduleTimers;
229
+ /**
230
+ * 从 productQueryScheduleTimers 移除已触发的 timer 引用。
231
+ */
232
+ private removeScheduleTimerRef;
233
+ /**
234
+ * 计算 schedule 变价时间点到当前时刻的延迟(ms)。已过期返回 -1。
235
+ */
236
+ private computeScheduleTimePointDelayMs;
237
+ /**
238
+ * 定时重算前刷新 subscriber schedule 上下文:使用执行瞬间时间,禁止沿用首次 query 的 schedule_datetime。
239
+ */
240
+ private refreshSubscriberScheduleAtExecution;
241
+ /**
242
+ * 为 subscriber 注册智能定价 scheduleTimePoints 定时重算。
243
+ * 必须先 clearSubscriberScheduleTimers,再调用本方法。
244
+ */
245
+ private scheduleSubscriberTimePointReloads;
246
+ /**
247
+ * schedule 变价时间点定时器触发:刷新 schedule_datetime 后重算并 callback。
248
+ */
249
+ private onScheduleTimePointTimerFire;
250
+ /**
251
+ * 对单个商品 query 订阅者重算并推送(定时路径;不新建 schedule timer)。
252
+ */
253
+ private recomputeAndNotifySubscriber;
254
+ /**
255
+ * query 完成后为 subscriber 同步 schedule 定时器(先 clear 再 schedule)。
256
+ */
257
+ private syncProductQueryScheduleTimers;
214
258
  /**
215
259
  * 处理商品查询请求
216
260
  * 存储订阅者信息,便于数据变更时推送最新结果
@@ -584,6 +628,12 @@ declare class Server {
584
628
  * @param options.changedIds 变更的商品 IDs,用于增量更新价格缓存
585
629
  */
586
630
  private computeProductQueryResult;
631
+ /**
632
+ * 构建商品格式化上下文。
633
+ *
634
+ * 智能定价条件使用全量 menuList/scheduleList;Product Query 顶层参数只保留筛选商品集合所需字段。
635
+ */
636
+ private buildProductFormatterContext;
587
637
  /**
588
638
  * 数据变更后,遍历所有订阅者重新计算查询结果并通过 callback 推送
589
639
  * 由 ProductsModule 的 onProductsSyncCompleted 事件触发
@@ -78,6 +78,8 @@ var Server = class {
78
78
  };
79
79
  // ---- 商品查询订阅者 ----
80
80
  this.productQuerySubscribers = /* @__PURE__ */ new Map();
81
+ /** subscriberId → 智能定价变价时间点定时器句柄(与 subscriber 分离存储,便于 clear) */
82
+ this.productQueryScheduleTimers = /* @__PURE__ */ new Map();
81
83
  // ---- 订单 / 预约列表查询订阅者 ----
82
84
  this.orderQuerySubscribers = /* @__PURE__ */ new Map();
83
85
  this.bookingQuerySubscribers = /* @__PURE__ */ new Map();
@@ -211,7 +213,7 @@ var Server = class {
211
213
  */
212
214
  this.handleProductQuery = async ({ url, method, data, config }) => {
213
215
  console.log("[Server] handleProductQuery:", url, method, data, config);
214
- const { menu_list_ids, schedule_datetime, schedule_date, customer_id, ids, extension_type } = data;
216
+ const { menu_list_ids, schedule_datetime, schedule_date, customer_id, ids, extension_type, strategy_context } = data;
215
217
  const { callback, subscriberId } = config || {};
216
218
  this.logInfo("handleProductQuery: 开始处理商品查询请求", {
217
219
  menu_list_ids,
@@ -219,19 +221,32 @@ var Server = class {
219
221
  schedule_datetime,
220
222
  schedule_date,
221
223
  customer_id,
222
- extension_type
224
+ extension_type,
225
+ strategyContextKeys: strategy_context ? Object.keys(strategy_context) : []
223
226
  });
224
227
  if (subscriberId && typeof callback === "function") {
225
228
  this.productQuerySubscribers.set(subscriberId, {
226
229
  callback,
227
- context: { menu_list_ids, ids, schedule_date, schedule_datetime, customer_id, extension_type }
230
+ context: { menu_list_ids, ids, schedule_date, schedule_datetime, customer_id, extension_type, strategy_context }
228
231
  });
229
232
  this.logInfo("handleProductQuery: 已注册订阅者", {
230
233
  subscriberId,
231
234
  totalSubscribers: this.productQuerySubscribers.size
232
235
  });
233
236
  }
234
- return this.computeProductQueryResult({ menu_list_ids, ids, schedule_date, schedule_datetime, customer_id, extension_type });
237
+ const result = await this.computeProductQueryResult({
238
+ menu_list_ids,
239
+ ids,
240
+ schedule_date,
241
+ schedule_datetime,
242
+ customer_id,
243
+ extension_type,
244
+ strategy_context
245
+ });
246
+ if (subscriberId && typeof callback === "function") {
247
+ this.syncProductQueryScheduleTimers(subscriberId, strategy_context);
248
+ }
249
+ return result;
235
250
  };
236
251
  /**
237
252
  * 按商品 id 查询单条(GET /shop/product/query/:productId)
@@ -2054,6 +2069,7 @@ var Server = class {
2054
2069
  */
2055
2070
  removeProductQuerySubscriber(subscriberId) {
2056
2071
  if (subscriberId) {
2072
+ this.clearSubscriberScheduleTimers(subscriberId);
2057
2073
  this.productQuerySubscribers.delete(subscriberId);
2058
2074
  this.logInfo("removeProductQuerySubscriber: 已移除订阅者", {
2059
2075
  subscriberId,
@@ -2061,6 +2077,156 @@ var Server = class {
2061
2077
  });
2062
2078
  }
2063
2079
  }
2080
+ /**
2081
+ * 构建商品 query 响应 data 段,附带智能定价 schedule_time_points 元数据。
2082
+ */
2083
+ buildProductQueryResultPayload(list, count) {
2084
+ var _a, _b;
2085
+ return {
2086
+ list,
2087
+ count: count ?? list.length,
2088
+ schedule_time_points: ((_b = (_a = this.products) == null ? void 0 : _a.getLastScheduleTimePoints) == null ? void 0 : _b.call(_a)) ?? []
2089
+ };
2090
+ }
2091
+ /**
2092
+ * 是否启用商品 query 订阅的 schedule 时间点定时重算。
2093
+ * strategy_context.enable_schedule_reload === false 时关闭。
2094
+ */
2095
+ shouldScheduleProductQueryReload(strategy_context) {
2096
+ return (strategy_context == null ? void 0 : strategy_context.enable_schedule_reload) !== false;
2097
+ }
2098
+ /**
2099
+ * 清除指定 subscriber 的全部 schedule 定时器,防止重复 query 或 unsubscribe 后泄漏。
2100
+ */
2101
+ clearSubscriberScheduleTimers(subscriberId) {
2102
+ const timers = this.productQueryScheduleTimers.get(subscriberId);
2103
+ if (timers == null ? void 0 : timers.length) {
2104
+ timers.forEach((timerId) => clearTimeout(timerId));
2105
+ }
2106
+ this.productQueryScheduleTimers.delete(subscriberId);
2107
+ const subscriber = this.productQuerySubscribers.get(subscriberId);
2108
+ if (subscriber) {
2109
+ subscriber.scheduleTimerIds = [];
2110
+ }
2111
+ }
2112
+ /**
2113
+ * 从 productQueryScheduleTimers 移除已触发的 timer 引用。
2114
+ */
2115
+ removeScheduleTimerRef(subscriberId, timerId) {
2116
+ const timers = this.productQueryScheduleTimers.get(subscriberId);
2117
+ if (!(timers == null ? void 0 : timers.length))
2118
+ return;
2119
+ const next = timers.filter((id) => id !== timerId);
2120
+ if (next.length === 0) {
2121
+ this.productQueryScheduleTimers.delete(subscriberId);
2122
+ } else {
2123
+ this.productQueryScheduleTimers.set(subscriberId, next);
2124
+ }
2125
+ const subscriber = this.productQuerySubscribers.get(subscriberId);
2126
+ if (subscriber) {
2127
+ subscriber.scheduleTimerIds = next;
2128
+ }
2129
+ }
2130
+ /**
2131
+ * 计算 schedule 变价时间点到当前时刻的延迟(ms)。已过期返回 -1。
2132
+ */
2133
+ computeScheduleTimePointDelayMs(scheduleDate, timePoint) {
2134
+ const parts = timePoint.split(":");
2135
+ if (parts.length < 2)
2136
+ return -1;
2137
+ const hours = parseInt(parts[0], 10);
2138
+ const minutes = parseInt(parts[1], 10);
2139
+ if (!Number.isFinite(hours) || !Number.isFinite(minutes))
2140
+ return -1;
2141
+ const target = (0, import_dayjs.default)(scheduleDate).hour(hours).minute(minutes).second(4).millisecond(0);
2142
+ const delayMs = target.diff((0, import_dayjs.default)());
2143
+ return delayMs >= 0 ? delayMs : -1;
2144
+ }
2145
+ /**
2146
+ * 定时重算前刷新 subscriber schedule 上下文:使用执行瞬间时间,禁止沿用首次 query 的 schedule_datetime。
2147
+ */
2148
+ refreshSubscriberScheduleAtExecution(subscriber) {
2149
+ const executedAt = (0, import_dayjs.default)();
2150
+ const bookingDate = subscriber.context.schedule_date || executedAt.format("YYYY-MM-DD");
2151
+ subscriber.context.schedule_date = bookingDate;
2152
+ subscriber.context.schedule_datetime = `${bookingDate} ${executedAt.format("HH:mm:ss")}`;
2153
+ }
2154
+ /**
2155
+ * 为 subscriber 注册智能定价 scheduleTimePoints 定时重算。
2156
+ * 必须先 clearSubscriberScheduleTimers,再调用本方法。
2157
+ */
2158
+ scheduleSubscriberTimePointReloads(subscriberId, timePoints) {
2159
+ const subscriber = this.productQuerySubscribers.get(subscriberId);
2160
+ if (!subscriber || !timePoints.length)
2161
+ return;
2162
+ const scheduleDate = subscriber.context.schedule_date || (0, import_dayjs.default)().format("YYYY-MM-DD");
2163
+ const timerIds = [];
2164
+ for (const timePoint of timePoints) {
2165
+ const delayMs = this.computeScheduleTimePointDelayMs(scheduleDate, timePoint);
2166
+ if (delayMs < 0)
2167
+ continue;
2168
+ const timerId = setTimeout(() => {
2169
+ void this.onScheduleTimePointTimerFire(subscriberId, timerId);
2170
+ }, delayMs);
2171
+ timerIds.push(timerId);
2172
+ }
2173
+ if (timerIds.length > 0) {
2174
+ this.productQueryScheduleTimers.set(subscriberId, timerIds);
2175
+ subscriber.scheduleTimerIds = timerIds;
2176
+ this.logInfo("scheduleSubscriberTimePointReloads: 已注册定时器", {
2177
+ subscriberId,
2178
+ timerCount: timerIds.length,
2179
+ timePoints
2180
+ });
2181
+ }
2182
+ }
2183
+ /**
2184
+ * schedule 变价时间点定时器触发:刷新 schedule_datetime 后重算并 callback。
2185
+ */
2186
+ async onScheduleTimePointTimerFire(subscriberId, timerId) {
2187
+ this.removeScheduleTimerRef(subscriberId, timerId);
2188
+ if (!this.productQuerySubscribers.has(subscriberId))
2189
+ return;
2190
+ const subscriber = this.productQuerySubscribers.get(subscriberId);
2191
+ this.refreshSubscriberScheduleAtExecution(subscriber);
2192
+ await this.recomputeAndNotifySubscriber(subscriberId);
2193
+ }
2194
+ /**
2195
+ * 对单个商品 query 订阅者重算并推送(定时路径;不新建 schedule timer)。
2196
+ */
2197
+ async recomputeAndNotifySubscriber(subscriberId) {
2198
+ const subscriber = this.productQuerySubscribers.get(subscriberId);
2199
+ if (!subscriber)
2200
+ return;
2201
+ try {
2202
+ const result = await this.computeProductQueryResult(subscriber.context);
2203
+ subscriber.callback(result);
2204
+ this.logInfo("recomputeAndNotifySubscriber: 已推送", {
2205
+ subscriberId,
2206
+ schedule_datetime: subscriber.context.schedule_datetime
2207
+ });
2208
+ } catch (error) {
2209
+ const errorMessage = error instanceof Error ? error.message : String(error);
2210
+ this.logError("recomputeAndNotifySubscriber: 推送失败", {
2211
+ subscriberId,
2212
+ error: errorMessage
2213
+ });
2214
+ }
2215
+ }
2216
+ /**
2217
+ * query 完成后为 subscriber 同步 schedule 定时器(先 clear 再 schedule)。
2218
+ */
2219
+ syncProductQueryScheduleTimers(subscriberId, strategy_context) {
2220
+ var _a, _b;
2221
+ this.clearSubscriberScheduleTimers(subscriberId);
2222
+ if (!this.shouldScheduleProductQueryReload(strategy_context)) {
2223
+ return;
2224
+ }
2225
+ const timePoints = ((_b = (_a = this.products) == null ? void 0 : _a.getLastScheduleTimePoints) == null ? void 0 : _b.call(_a)) ?? [];
2226
+ if (timePoints.length > 0) {
2227
+ this.scheduleSubscriberTimePointReloads(subscriberId, timePoints);
2228
+ }
2229
+ }
2064
2230
  /**
2065
2231
  * 执行 sales 搜索并登记可见订单号。
2066
2232
  * 搜索参数完全透传后端,OS 不做过滤、筛选、排序。
@@ -4707,8 +4873,14 @@ var Server = class {
4707
4873
  */
4708
4874
  async computeProductQueryResult(context, options) {
4709
4875
  const tTotal = performance.now();
4710
- const { menu_list_ids, ids, schedule_date, schedule_datetime, customer_id, extension_type, product_id } = context;
4876
+ const { menu_list_ids, ids, schedule_date, schedule_datetime, customer_id, extension_type, product_id, strategy_context } = context;
4711
4877
  const queryIds = Array.isArray(ids) ? ids.map((id) => Number(id)).filter((id) => Number.isFinite(id)) : [];
4878
+ const formatterContext = this.buildProductFormatterContext({
4879
+ schedule_date,
4880
+ schedule_datetime,
4881
+ customer_id,
4882
+ strategy_context
4883
+ });
4712
4884
  this.logInfo("computeProductQueryResult 开始", {
4713
4885
  menuListIdsCount: (menu_list_ids == null ? void 0 : menu_list_ids.length) ?? 0,
4714
4886
  ids: queryIds,
@@ -4717,20 +4889,17 @@ var Server = class {
4717
4889
  customer_id,
4718
4890
  extension_type,
4719
4891
  product_id,
4892
+ strategyContextKeys: strategy_context ? Object.keys(strategy_context) : [],
4720
4893
  changedIds: options == null ? void 0 : options.changedIds
4721
4894
  });
4722
4895
  if (!this.products) {
4723
4896
  this.logError("computeProductQueryResult: Products 模块未注册");
4724
- return { message: "Products 模块未注册", data: { list: [], count: 0 } };
4897
+ return { message: "Products 模块未注册", data: this.buildProductQueryResultPayload([], 0) };
4725
4898
  }
4726
4899
  if (queryIds.length > 0) {
4727
4900
  const uniqueIds = Array.from(new Set(queryIds));
4728
4901
  const tPrice2 = performance.now();
4729
- const productsWithPrice = await this.products.getProductsWithPrice(schedule_date, {
4730
- scheduleModule: this.getSchedule(),
4731
- schedule_datetime,
4732
- customer_id
4733
- }, {
4902
+ const productsWithPrice = await this.products.getProductsWithPrice(schedule_date, formatterContext, {
4734
4903
  changedIds: options == null ? void 0 : options.changedIds,
4735
4904
  productIds: uniqueIds
4736
4905
  });
@@ -4751,7 +4920,7 @@ var Server = class {
4751
4920
  });
4752
4921
  return {
4753
4922
  code: 200,
4754
- data: { list: extensionFilteredProducts, count: extensionFilteredProducts.length },
4923
+ data: this.buildProductQueryResultPayload(extensionFilteredProducts),
4755
4924
  message: "",
4756
4925
  status: true
4757
4926
  };
@@ -4759,9 +4928,7 @@ var Server = class {
4759
4928
  if (product_id != null && Number.isFinite(Number(product_id))) {
4760
4929
  const pid = Number(product_id);
4761
4930
  const tPrice2 = performance.now();
4762
- const allProductsWithPrice = await this.products.getProductsWithPrice(schedule_date, {
4763
- scheduleModule: this.getSchedule()
4764
- }, {
4931
+ const allProductsWithPrice = await this.products.getProductsWithPrice(schedule_date, formatterContext, {
4765
4932
  changedIds: options == null ? void 0 : options.changedIds
4766
4933
  });
4767
4934
  (0, import_product.perfMark)("computeQuery.getProductsWithPrice(single)", performance.now() - tPrice2, {
@@ -4791,11 +4958,7 @@ var Server = class {
4791
4958
  }
4792
4959
  if (this.shouldQueryProductsByExtensionTypes(extension_type)) {
4793
4960
  const tPrice2 = performance.now();
4794
- let filteredProducts2 = await this.products.getProductsWithPrice(schedule_date, {
4795
- scheduleModule: this.getSchedule(),
4796
- schedule_datetime,
4797
- customer_id
4798
- }, {
4961
+ let filteredProducts2 = await this.products.getProductsWithPrice(schedule_date, formatterContext, {
4799
4962
  changedIds: options == null ? void 0 : options.changedIds
4800
4963
  });
4801
4964
  (0, import_product.perfMark)("computeQuery.getProductsWithPrice(extensionType)", performance.now() - tPrice2, {
@@ -4835,18 +4998,18 @@ var Server = class {
4835
4998
  });
4836
4999
  return {
4837
5000
  code: 200,
4838
- data: { list: filteredProducts2, count: filteredProducts2.length },
5001
+ data: this.buildProductQueryResultPayload(filteredProducts2),
4839
5002
  message: "",
4840
5003
  status: true
4841
5004
  };
4842
5005
  }
4843
5006
  if (!this.menu) {
4844
5007
  this.logError("computeProductQueryResult: Menu 模块未注册");
4845
- return { message: "Menu 模块未注册", data: { list: [], count: 0 } };
5008
+ return { message: "Menu 模块未注册", data: this.buildProductQueryResultPayload([], 0) };
4846
5009
  }
4847
5010
  if (!this.schedule) {
4848
5011
  this.logError("computeProductQueryResult: Schedule 模块未注册");
4849
- return { message: "Schedule 模块未注册", data: { list: [], count: 0 } };
5012
+ return { message: "Schedule 模块未注册", data: this.buildProductQueryResultPayload([], 0) };
4850
5013
  }
4851
5014
  let activeMenuList = [];
4852
5015
  if (menu_list_ids && Array.isArray(menu_list_ids) && menu_list_ids.length > 0) {
@@ -4870,11 +5033,7 @@ var Server = class {
4870
5033
  isAllProducts: !!productScope.isAllProducts
4871
5034
  });
4872
5035
  const tPrice = performance.now();
4873
- let filteredProducts = scopedProductIds.length > 0 ? await this.products.getProductsWithPrice(schedule_date, {
4874
- scheduleModule: this.getSchedule(),
4875
- schedule_datetime,
4876
- customer_id
4877
- }, {
5036
+ let filteredProducts = scopedProductIds.length > 0 ? await this.products.getProductsWithPrice(schedule_date, formatterContext, {
4878
5037
  changedIds: options == null ? void 0 : options.changedIds,
4879
5038
  productIds: productScope.isAllProducts ? void 0 : scopedProductIds
4880
5039
  }) : [];
@@ -4917,11 +5076,29 @@ var Server = class {
4917
5076
  });
4918
5077
  return {
4919
5078
  code: 200,
4920
- data: { list: filteredProducts, count: filteredProducts.length },
5079
+ data: this.buildProductQueryResultPayload(filteredProducts),
4921
5080
  message: "",
4922
5081
  status: true
4923
5082
  };
4924
5083
  }
5084
+ /**
5085
+ * 构建商品格式化上下文。
5086
+ *
5087
+ * 智能定价条件使用全量 menuList/scheduleList;Product Query 顶层参数只保留筛选商品集合所需字段。
5088
+ */
5089
+ buildProductFormatterContext(context) {
5090
+ var _a, _b, _c, _d;
5091
+ const scheduleModule = this.getSchedule();
5092
+ return {
5093
+ scheduleModule,
5094
+ schedule_datetime: context.schedule_datetime,
5095
+ customer_id: context.customer_id,
5096
+ strategy_context: context.strategy_context,
5097
+ menuList: ((_b = (_a = this.menu) == null ? void 0 : _a.getMenuList) == null ? void 0 : _b.call(_a)) || [],
5098
+ scheduleList: ((_c = scheduleModule == null ? void 0 : scheduleModule.getScheduleList) == null ? void 0 : _c.call(scheduleModule)) || [],
5099
+ dataVariantEvaluator: (_d = this.core.context) == null ? void 0 : _d.dataVariantEvaluator
5100
+ };
5101
+ }
4925
5102
  /**
4926
5103
  * 数据变更后,遍历所有订阅者重新计算查询结果并通过 callback 推送
4927
5104
  * 由 ProductsModule 的 onProductsSyncCompleted 事件触发
@@ -123,6 +123,9 @@ export declare class OrderModule extends BaseModule implements Module {
123
123
  */
124
124
  silentRefresh(): Promise<OrderData[]>;
125
125
  private normalizeOrderForMemoryList;
126
+ private normalizeOrdersForMemoryList;
127
+ private hasRealOrderId;
128
+ private shouldKeepOrderInMemory;
126
129
  private withoutSyntheticDraftOrderId;
127
130
  /**
128
131
  * 仅覆盖本地已存在的订单;不存在则直接跳过,不落库、不 emit。
@@ -151,8 +154,8 @@ export declare class OrderModule extends BaseModule implements Module {
151
154
  throwOnError?: boolean;
152
155
  }): Promise<void>;
153
156
  /**
154
- * 将 POS 草稿订单按 SQLite storage key 合并进 store 并落库。
155
- * 草稿只表示本地可恢复快照,不进入同步队列。
157
+ * 将 POS 草稿订单按 SQLite storage key 落库。
158
+ * 只有具备真实 order_id 的草稿进入 store;其余草稿仅供本地恢复查询。
156
159
  */
157
160
  upsertLocalDraftOrders(draftOrders: OrderData[]): Promise<void>;
158
161
  /**
@@ -276,6 +279,7 @@ export declare class OrderModule extends BaseModule implements Module {
276
279
  private isDraftOrder;
277
280
  private shouldProtectDraftOrderFromIncoming;
278
281
  private normalizeRemoteSyncedOrder;
282
+ private mergeRemoteIdentityIntoDraftOrder;
279
283
  private mergeRemoteSnapshotWithPendingOrders;
280
284
  /**
281
285
  * 串行执行订单 SQLite 写入任务,避免快照替换与增量 upsert 并发交错。
@@ -301,7 +305,8 @@ export declare class OrderModule extends BaseModule implements Module {
301
305
  */
302
306
  private updateOrderInSQLite;
303
307
  /**
304
- * 全量快照替换 orders 表(clear + bulkAdd),用于 SSE 全量拉取与营业日窗口裁剪。
308
+ * 全量快照替换 orders 表(clear + bulkAdd),并保留本地 draft / need_sync 订单。
309
+ * 用于 SSE 全量拉取与营业日窗口裁剪。
305
310
  *
306
311
  * @example
307
312
  * await this.replaceOrdersSnapshotInSQLite(orderList)