@pisell/pisellos 2.2.259 → 2.2.260

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 (103) hide show
  1. package/dist/model/strategy/adapter/dataVariant/adapter.d.ts +49 -0
  2. package/dist/model/strategy/adapter/dataVariant/adapter.js +152 -0
  3. package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +87 -0
  4. package/dist/model/strategy/adapter/dataVariant/evaluator.js +755 -0
  5. package/dist/model/strategy/adapter/dataVariant/examples.d.ts +39 -0
  6. package/dist/model/strategy/adapter/dataVariant/examples.js +282 -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 +146 -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/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +13 -17
  14. package/dist/modules/Order/index.d.ts +1 -32
  15. package/dist/modules/Order/index.js +288 -615
  16. package/dist/modules/Order/types.d.ts +2 -20
  17. package/dist/modules/Order/utils.d.ts +0 -4
  18. package/dist/modules/Order/utils.js +28 -114
  19. package/dist/modules/Product/index.d.ts +1 -1
  20. package/dist/modules/Product/types.d.ts +22 -0
  21. package/dist/modules/ProductList/index.d.ts +1 -1
  22. package/dist/modules/ProductList/index.js +4 -2
  23. package/dist/modules/ProductList/types.d.ts +2 -0
  24. package/dist/modules/Rules/index.d.ts +3 -3
  25. package/dist/modules/Rules/index.js +3 -8
  26. package/dist/modules/Rules/types.d.ts +1 -2
  27. package/dist/modules/SalesSummary/index.d.ts +3 -7
  28. package/dist/modules/SalesSummary/index.js +39 -67
  29. package/dist/modules/SalesSummary/types.d.ts +0 -1
  30. package/dist/modules/SalesSummary/utils.js +1 -2
  31. package/dist/modules/SurchargeList/index.d.ts +4 -6
  32. package/dist/modules/SurchargeList/index.js +39 -62
  33. package/dist/modules/SurchargeList/types.d.ts +2 -6
  34. package/dist/server/index.d.ts +50 -3
  35. package/dist/server/index.js +1040 -1075
  36. package/dist/server/modules/order/index.d.ts +3 -22
  37. package/dist/server/modules/order/index.js +316 -398
  38. package/dist/server/modules/products/index.d.ts +26 -7
  39. package/dist/server/modules/products/index.js +166 -76
  40. package/dist/server/modules/products/types.d.ts +14 -0
  41. package/dist/solution/BaseSales/index.d.ts +2 -22
  42. package/dist/solution/BaseSales/index.js +701 -995
  43. package/dist/solution/BaseSales/types.d.ts +0 -1
  44. package/dist/solution/BaseSales/types.js +1 -2
  45. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +3 -6
  46. package/dist/solution/BookingByStep/index.d.ts +1 -1
  47. package/dist/solution/BookingTicket/index.d.ts +0 -8
  48. package/dist/solution/BookingTicket/index.js +6 -153
  49. package/dist/solution/BookingTicket/types.d.ts +2 -0
  50. package/lib/model/strategy/adapter/dataVariant/adapter.d.ts +49 -0
  51. package/lib/model/strategy/adapter/dataVariant/adapter.js +139 -0
  52. package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +87 -0
  53. package/lib/model/strategy/adapter/dataVariant/evaluator.js +564 -0
  54. package/lib/model/strategy/adapter/dataVariant/examples.d.ts +39 -0
  55. package/lib/model/strategy/adapter/dataVariant/examples.js +295 -0
  56. package/lib/model/strategy/adapter/dataVariant/index.d.ts +4 -0
  57. package/lib/model/strategy/adapter/dataVariant/index.js +38 -0
  58. package/lib/model/strategy/adapter/dataVariant/type.d.ts +146 -0
  59. package/lib/model/strategy/adapter/dataVariant/type.js +31 -0
  60. package/lib/model/strategy/adapter/index.d.ts +4 -0
  61. package/lib/model/strategy/adapter/index.js +13 -2
  62. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +1 -11
  63. package/lib/modules/Order/index.d.ts +1 -32
  64. package/lib/modules/Order/index.js +64 -402
  65. package/lib/modules/Order/types.d.ts +2 -20
  66. package/lib/modules/Order/utils.d.ts +0 -4
  67. package/lib/modules/Order/utils.js +2 -84
  68. package/lib/modules/Product/index.d.ts +1 -1
  69. package/lib/modules/Product/types.d.ts +22 -0
  70. package/lib/modules/ProductList/index.d.ts +1 -1
  71. package/lib/modules/ProductList/index.js +4 -2
  72. package/lib/modules/ProductList/types.d.ts +2 -0
  73. package/lib/modules/Rules/index.d.ts +3 -3
  74. package/lib/modules/Rules/index.js +3 -4
  75. package/lib/modules/Rules/types.d.ts +1 -2
  76. package/lib/modules/SalesSummary/index.d.ts +3 -7
  77. package/lib/modules/SalesSummary/index.js +11 -28
  78. package/lib/modules/SalesSummary/types.d.ts +0 -1
  79. package/lib/modules/SalesSummary/utils.js +0 -2
  80. package/lib/modules/SurchargeList/index.d.ts +4 -6
  81. package/lib/modules/SurchargeList/index.js +21 -41
  82. package/lib/modules/SurchargeList/types.d.ts +2 -6
  83. package/lib/server/index.d.ts +50 -3
  84. package/lib/server/index.js +215 -244
  85. package/lib/server/modules/order/index.d.ts +3 -22
  86. package/lib/server/modules/order/index.js +53 -103
  87. package/lib/server/modules/products/index.d.ts +26 -7
  88. package/lib/server/modules/products/index.js +113 -35
  89. package/lib/server/modules/products/types.d.ts +14 -0
  90. package/lib/solution/BaseSales/index.d.ts +2 -22
  91. package/lib/solution/BaseSales/index.js +60 -246
  92. package/lib/solution/BaseSales/types.d.ts +0 -1
  93. package/lib/solution/BaseSales/types.js +1 -2
  94. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +6 -6
  95. package/lib/solution/BookingByStep/index.d.ts +1 -1
  96. package/lib/solution/BookingTicket/index.d.ts +0 -8
  97. package/lib/solution/BookingTicket/index.js +0 -101
  98. package/lib/solution/BookingTicket/types.d.ts +2 -0
  99. package/package.json +1 -1
  100. package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +0 -5
  101. package/dist/modules/Order/utils/bundleDiscountHydration.js +0 -144
  102. package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +0 -5
  103. package/lib/modules/Order/utils/bundleDiscountHydration.js +0 -139
@@ -48,7 +48,6 @@ var ORDER_BUSINESS_WRITE_SOURCES = /* @__PURE__ */ new Set([
48
48
  "overwriteExistingOrder",
49
49
  "markOrderSyncedByExternalSaleNumber"
50
50
  ]);
51
- var ORDER_MERGE_SELF_CHECK = false;
52
51
  var OrderModule = class extends import_BaseModule.BaseModule {
53
52
  constructor(name, version) {
54
53
  super(name, version);
@@ -57,6 +56,8 @@ var OrderModule = class extends import_BaseModule.BaseModule {
57
56
  this.pendingSyncMessages = [];
58
57
  this.isProcessingSyncBatch = false;
59
58
  this.isIdlePhase = true;
59
+ // Map<resource_id, OrderId[]> 资源到订单的倒排索引
60
+ this.resourceIdIndex = /* @__PURE__ */ new Map();
60
61
  this.orderSQLiteSaveQueue = Promise.resolve();
61
62
  /** 按 storageKey 记录最近一次 SQLite 写入来源与时间,用于去重 */
62
63
  this.recentSqliteWriteByStorageKey = /* @__PURE__ */ new Map();
@@ -308,11 +309,12 @@ var OrderModule = class extends import_BaseModule.BaseModule {
308
309
  };
309
310
  }
310
311
  updateCreateStore() {
312
+ var _a;
311
313
  const appPlugin = this.core.getPlugin("app");
312
314
  const app = appPlugin == null ? void 0 : appPlugin.getApp();
313
315
  if (app) {
314
316
  const coreData = app.data.store.getStore().getDataByModel("core");
315
- if (coreData == null ? void 0 : coreData.core) {
317
+ if (!((_a = this.store.createdAtQuery) == null ? void 0 : _a.sales_time_between) && (coreData == null ? void 0 : coreData.core)) {
316
318
  const today = (0, import_dayjs.default)();
317
319
  if (coreData == null ? void 0 : coreData.core.operating_day_boundary) {
318
320
  const operatingDayBoundary = coreData.core.operating_day_boundary;
@@ -479,6 +481,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
479
481
  return this.store.list;
480
482
  }
481
483
  }
484
+ getOrdersByResourceId(resourceId) {
485
+ const ids = this.resourceIdIndex.get(String(resourceId)) || [];
486
+ return ids.map((id) => this.store.map.get(id)).filter((order) => !!order);
487
+ }
482
488
  normalizeOrderForMemoryList(order) {
483
489
  const externalSaleNumber = order == null ? void 0 : order.external_sale_number;
484
490
  if ((order == null ? void 0 : order.order_id) !== void 0 && (order == null ? void 0 : order.order_id) !== null && (order == null ? void 0 : order.order_id) !== "") {
@@ -611,33 +617,22 @@ var OrderModule = class extends import_BaseModule.BaseModule {
611
617
  const patchedOrders = [...pendingMap.values()];
612
618
  const mergeActions = {};
613
619
  const updatedList = [...this.store.list];
614
- const identityKeyToIndex = /* @__PURE__ */ new Map();
615
- for (let index = 0; index < updatedList.length; index += 1) {
616
- for (const key of this.getOrderIdentityMatchKeys(updatedList[index])) {
617
- if (!identityKeyToIndex.has(key))
618
- identityKeyToIndex.set(key, index);
619
- }
620
- }
621
620
  for (const [storageKey, pendingOrder] of memoryPendingMap.entries()) {
622
- const matchIndex = this.lookupOrderIndexByIdentityKeys(
623
- identityKeyToIndex,
624
- this.getOrderIdentityMatchKeys(pendingOrder)
625
- );
626
- if (matchIndex >= 0) {
627
- const mergedOrder = this.mergeOrderRecords(updatedList[matchIndex], pendingOrder, {
628
- preferIncomingProducts: true
629
- });
630
- updatedList[matchIndex] = mergedOrder;
631
- this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
632
- mergeActions[storageKey] = "update";
621
+ const matchIndex = this.findOrderIndexByIdentity(updatedList, pendingOrder);
622
+ if (matchIndex < 0)
633
623
  continue;
634
- }
635
- const insertIndex = updatedList.length;
636
- updatedList.push(pendingOrder);
637
- this.registerOrderIdentityKeys(identityKeyToIndex, pendingOrder, insertIndex);
624
+ updatedList[matchIndex] = this.mergeOrderRecords(updatedList[matchIndex], pendingOrder, {
625
+ preferIncomingProducts: true
626
+ });
627
+ pendingMap.delete(storageKey);
628
+ memoryPendingMap.delete(storageKey);
629
+ mergeActions[storageKey] = "update";
630
+ }
631
+ for (const [storageKey, order] of memoryPendingMap.entries()) {
638
632
  mergeActions[storageKey] = "insert";
633
+ updatedList.push(order);
639
634
  }
640
- this.store.list = this.runOrderStoreSelfCheck(updatedList, "upsertPendingSyncOrders.store");
635
+ this.store.list = this.compactOrderListByIdentity(updatedList, "upsertPendingSyncOrders.store").list;
641
636
  this.syncOrdersMap();
642
637
  await this.patchOrdersInSQLite(patchedOrders, "upsertPendingSyncOrders", mergeActions);
643
638
  this.logInfo("upsertPendingSyncOrders-结束", {
@@ -679,11 +674,21 @@ var OrderModule = class extends import_BaseModule.BaseModule {
679
674
  }
680
675
  syncOrdersMap() {
681
676
  this.store.map.clear();
677
+ this.resourceIdIndex.clear();
682
678
  for (const order of this.store.list) {
683
679
  const primaryIdentity = this.getOrderMapKey(order);
684
680
  if (!primaryIdentity)
685
681
  continue;
686
682
  this.store.map.set(primaryIdentity, order);
683
+ const resourceIds = this.extractResourceIds(order);
684
+ for (const resourceId of resourceIds) {
685
+ const key = String(resourceId);
686
+ const existing = this.resourceIdIndex.get(key) || [];
687
+ if (existing.some((eid) => this.getIdKey(eid) === primaryIdentity))
688
+ continue;
689
+ existing.push(primaryIdentity);
690
+ this.resourceIdIndex.set(key, existing);
691
+ }
687
692
  }
688
693
  }
689
694
  getOrderMapKey(order) {
@@ -730,6 +735,24 @@ var OrderModule = class extends import_BaseModule.BaseModule {
730
735
  }
731
736
  return keys;
732
737
  }
738
+ extractResourceIds(order) {
739
+ const ids = /* @__PURE__ */ new Set();
740
+ const visitResources = (resources) => {
741
+ if (!Array.isArray(resources))
742
+ return;
743
+ resources.forEach((resource) => {
744
+ const rid = resource == null ? void 0 : resource.relation_id;
745
+ if (rid !== void 0 && rid !== null && rid !== "")
746
+ ids.add(String(rid));
747
+ if (Array.isArray(resource == null ? void 0 : resource.children))
748
+ visitResources(resource.children);
749
+ });
750
+ };
751
+ if (Array.isArray(order == null ? void 0 : order.bookings)) {
752
+ order.bookings.forEach((booking) => visitResources((booking == null ? void 0 : booking.resources) || []));
753
+ }
754
+ return [...ids];
755
+ }
733
756
  getOrderDateKey(order) {
734
757
  var _a, _b, _c, _d;
735
758
  const candidates = [
@@ -948,10 +971,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
948
971
  * 将增量订单合并到 store
949
972
  */
950
973
  async mergeOrdersToStore(freshOrders, source) {
951
- const storeOrderCountBefore = this.store.list.length;
952
974
  this.logInfo("mergeOrdersToStore-开始", {
953
975
  freshOrderCount: freshOrders.length,
954
- storeOrderCountBefore,
976
+ storeOrderCountBefore: this.store.list.length,
955
977
  source
956
978
  });
957
979
  this.logDuplicateOrders(`${source}.beforeMerge`, this.store.list);
@@ -970,45 +992,19 @@ var OrderModule = class extends import_BaseModule.BaseModule {
970
992
  const patchedOrders = [];
971
993
  const mergeActions = {};
972
994
  const updatedList = [...this.store.list];
973
- const identityKeyToIndex = /* @__PURE__ */ new Map();
974
- for (let index = 0; index < updatedList.length; index += 1) {
975
- const identityKeys = this.getOrderIdentityMatchKeys(updatedList[index]);
976
- for (const key of identityKeys) {
977
- if (!identityKeyToIndex.has(key))
978
- identityKeyToIndex.set(key, index);
979
- }
980
- }
981
995
  for (const fresh of uniqueFreshOrders) {
982
- const freshIdentityKeys = this.getOrderIdentityMatchKeys(fresh);
983
- const matchIndex = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys);
996
+ const matchIndex = this.findOrderIndexByIdentity(updatedList, fresh);
984
997
  const storageKey = this.getOrderStorageKey(fresh);
985
998
  const mergeKey = storageKey || (fresh.order_id !== void 0 && fresh.order_id !== null ? this.getIdKey(fresh.order_id) : "");
986
999
  if (matchIndex >= 0) {
987
- const existingOrder = updatedList[matchIndex];
988
- if (this.isPendingSyncOrder(existingOrder) && this.isRemoteEchoMergeSource(source)) {
989
- this.logInfo("mergeOrdersToStore-跳过 pending 订单远端覆盖", {
990
- source,
991
- order_id: existingOrder.order_id ?? fresh.order_id ?? null,
992
- external_sale_number: existingOrder.external_sale_number ?? fresh.external_sale_number ?? null,
993
- local_payment_status: existingOrder.payment_status ?? null,
994
- remote_payment_status: fresh.payment_status ?? null,
995
- local_need_sync: existingOrder.need_sync ?? null,
996
- remote_need_sync: fresh.need_sync ?? null,
997
- reason: "protect_pending_sync_order_from_remote_echo"
998
- });
999
- continue;
1000
- }
1001
- const mergedOrder = this.mergeOrderRecords(existingOrder, fresh);
1000
+ const mergedOrder = this.mergeOrderRecords(updatedList[matchIndex], fresh);
1002
1001
  updatedList[matchIndex] = mergedOrder;
1003
- this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
1004
1002
  patchedOrders.push(mergedOrder);
1005
1003
  if (mergeKey)
1006
1004
  mergeActions[mergeKey] = "update";
1007
1005
  continue;
1008
1006
  }
1009
- const insertIndex = updatedList.length;
1010
1007
  updatedList.push(fresh);
1011
- this.registerOrderIdentityKeys(identityKeyToIndex, fresh, insertIndex);
1012
1008
  patchedOrders.push(fresh);
1013
1009
  if (mergeKey)
1014
1010
  mergeActions[mergeKey] = "insert";
@@ -1020,7 +1016,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1020
1016
  updateCount,
1021
1017
  storeOrderCountAfter: this.store.list.length
1022
1018
  });
1023
- this.store.list = this.runOrderStoreSelfCheck(updatedList, `${source}.store`);
1019
+ this.store.list = this.compactOrderListByIdentity(updatedList, `${source}.store`).list;
1024
1020
  this.syncOrdersMap();
1025
1021
  await this.patchOrdersInSQLite(patchedOrders, source, mergeActions);
1026
1022
  this.logInfo("mergeOrdersToStore-结束", {
@@ -1031,53 +1027,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1031
1027
  });
1032
1028
  await this.emitOrdersChanged(patchedOrders, source);
1033
1029
  }
1034
- /**
1035
- * 在身份索引中查找与目标订单共享任一身份键的已存在订单下标。
1036
- * 返回所有命中键里最小的下标,等价于原 findOrderIndexByIdentity 的「取首个匹配」语义。
1037
- *
1038
- * @example
1039
- * const idx = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys)
1040
- */
1041
- lookupOrderIndexByIdentityKeys(identityKeyToIndex, identityKeys) {
1042
- let matchIndex = -1;
1043
- for (const key of identityKeys) {
1044
- const index = identityKeyToIndex.get(key);
1045
- if (index === void 0)
1046
- continue;
1047
- if (matchIndex < 0 || index < matchIndex)
1048
- matchIndex = index;
1049
- }
1050
- return matchIndex;
1051
- }
1052
- isRemoteEchoMergeSource(source) {
1053
- return source === "pubsub.bodyUpsert" || source === "pubsub.httpRefresh";
1054
- }
1055
- /**
1056
- * 将订单的全部身份键登记到索引并指向其在列表中的下标。
1057
- * 合并后订单新增的身份字段(如挂单同步后拿到 order_id)也会被登记。
1058
- *
1059
- * @example
1060
- * this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex)
1061
- */
1062
- registerOrderIdentityKeys(identityKeyToIndex, order, index) {
1063
- const identityKeys = this.getOrderIdentityMatchKeys(order);
1064
- for (const key of identityKeys) {
1065
- identityKeyToIndex.set(key, index);
1066
- }
1067
- }
1068
- /**
1069
- * 合并热路径去重兜底:默认直接返回原列表(增量索引已保证无重复)。
1070
- * 仅当 ORDER_MERGE_SELF_CHECK 开启时,额外做一次全表压缩并在发现残留重复时告警。
1071
- */
1072
- runOrderStoreSelfCheck(orders, source) {
1073
- if (!ORDER_MERGE_SELF_CHECK)
1074
- return orders;
1075
- const { list, removedCount } = this.compactOrderListByIdentity(orders, source);
1076
- if (removedCount > 0) {
1077
- this.logWarning("合并自检发现残留重复订单,已压缩", { source, removedCount });
1078
- }
1079
- return list;
1080
- }
1081
1030
  normalizeOrderSyncMessage(res) {
1082
1031
  const data = (res == null ? void 0 : res.data) || res;
1083
1032
  if (!data)
@@ -1691,6 +1640,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1691
1640
  async clear() {
1692
1641
  this.store.list = [];
1693
1642
  this.store.map = /* @__PURE__ */ new Map();
1643
+ this.resourceIdIndex.clear();
1694
1644
  if (this.dbManager) {
1695
1645
  await this.runInOrderSQLiteSaveQueue(async () => {
1696
1646
  await this.dbManager.clear(INDEXDB_STORE_NAME);
@@ -18,7 +18,8 @@ export declare class ProductsModule extends BaseModule implements Module {
18
18
  private productsPriceCache;
19
19
  private readonly CACHE_MAX_DAYS;
20
20
  private formatters;
21
- private isPriceFormatterRegistered;
21
+ private isBuiltinFormatterRegistered;
22
+ private isLegacyPriceFormatterEnabled;
22
23
  private quotationPriceBridge?;
23
24
  private quotationBridgeChannel?;
24
25
  private quotationBridgeLoadedKey?;
@@ -36,6 +37,8 @@ export declare class ProductsModule extends BaseModule implements Module {
36
37
  private lastPreloadCompletedAtMs;
37
38
  /** IndexDB 写入串行队列,避免快照与 patch 并发交错 */
38
39
  private productIndexDBSaveQueue;
40
+ /** 最近一次智能定价评估提取的 schedule 变价时间点(HH:mm),供 Server 订阅定时刷新使用 */
41
+ private lastScheduleTimePoints;
39
42
  constructor(name?: string, version?: string);
40
43
  initialize(core: PisellCore, options: any): Promise<void>;
41
44
  /**
@@ -108,6 +111,10 @@ export declare class ProductsModule extends BaseModule implements Module {
108
111
  */
109
112
  private prepareProductsWithPrice;
110
113
  private getProductsPriceCacheKey;
114
+ /**
115
+ * 稳定序列化策略上下文,避免对象 key 顺序不同导致缓存 key 抖动。
116
+ */
117
+ private stringifyStable;
111
118
  private normalizeProductIds;
112
119
  /**
113
120
  * 按轻量范围返回商品 ID,不做 structuredClone。
@@ -122,14 +129,26 @@ export declare class ProductsModule extends BaseModule implements Module {
122
129
  */
123
130
  private applyFormatters;
124
131
  /**
125
- * 注册内置的价格格式化器
126
- * 这个格式化器负责将价格数据应用到商品上
132
+ * 注册内置商品格式化器。
133
+ *
134
+ * 智能定价默认替代报价单逻辑;报价单 formatter 仅在 enableLegacyPriceFormatter=true 时启用。
127
135
  * @private
128
136
  */
129
- private registerBuiltinPriceFormatter;
137
+ private registerBuiltinProductFormatters;
138
+ /**
139
+ * 是否启用旧报价单价格 formatter。
140
+ *
141
+ * 默认关闭,避免报价单逻辑覆盖智能定价结果。
142
+ */
143
+ private shouldEnableLegacyPriceFormatter;
144
+ /**
145
+ * 返回最近一次 prepareProductsWithPrice / DataVariant 评估提取的变价时间点(HH:mm)。
146
+ * 供 OS Server 商品 query 订阅定时刷新使用。
147
+ */
148
+ getLastScheduleTimePoints(): string[];
130
149
  /**
131
150
  * 注册商品格式化器
132
- * 格式化器会按注册顺序依次执行(内置价格格式化器始终是第一个)
151
+ * 格式化器会按注册顺序依次执行;legacy 启用时先兜底报价,再由智能定价覆盖。
133
152
  * @param formatter 格式化函数
134
153
  * @param prepend 是否插入到队列开头(默认 false,追加到末尾)
135
154
  * @example
@@ -144,8 +163,8 @@ export declare class ProductsModule extends BaseModule implements Module {
144
163
  */
145
164
  registerFormatter(formatter: ProductFormatter, prepend?: boolean): void;
146
165
  /**
147
- * 清空所有格式化器(包括内置价格格式化器)
148
- * @param keepBuiltin 是否保留内置价格格式化器(默认 true)
166
+ * 清空所有格式化器(包括内置智能定价格式化器)
167
+ * @param keepBuiltin 是否保留内置智能定价格式化器(默认 true)
149
168
  */
150
169
  clearFormatters(keepBuiltin?: boolean): void;
151
170
  /**
@@ -43,8 +43,10 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
43
43
  // 最多缓存7天
44
44
  // 商品格式化器列表
45
45
  this.formatters = [];
46
- // 是否已注册内置价格格式化器
47
- this.isPriceFormatterRegistered = false;
46
+ // 是否已注册内置商品格式化器
47
+ this.isBuiltinFormatterRegistered = false;
48
+ // 旧报价单格式化器默认关闭,仅显式开启时作为兼容兜底注册
49
+ this.isLegacyPriceFormatterEnabled = false;
48
50
  this.quotationBridgeLoadedKey = "";
49
51
  this.quotationScheduleCache = /* @__PURE__ */ new Map();
50
52
  this.quotationScheduleCacheSource = null;
@@ -56,6 +58,8 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
56
58
  this.lastPreloadCompletedAtMs = 0;
57
59
  /** IndexDB 写入串行队列,避免快照与 patch 并发交错 */
58
60
  this.productIndexDBSaveQueue = Promise.resolve();
61
+ /** 最近一次智能定价评估提取的 schedule 变价时间点(HH:mm),供 Server 订阅定时刷新使用 */
62
+ this.lastScheduleTimePoints = [];
59
63
  }
60
64
  async initialize(core, options) {
61
65
  var _a;
@@ -82,7 +86,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
82
86
  console.warn("[Products] IndexDB Manager 未找到");
83
87
  }
84
88
  }
85
- this.registerBuiltinPriceFormatter();
89
+ this.registerBuiltinProductFormatters();
86
90
  this.initProductDataSource();
87
91
  this.setupProductSync();
88
92
  this.logInfo("模块初始化完成", {
@@ -474,7 +478,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
474
478
  * @private
475
479
  */
476
480
  async prepareProductsWithPrice(schedule_date, extraContext, options) {
477
- var _a, _b;
481
+ var _a, _b, _c, _d;
478
482
  const tTotal = performance.now();
479
483
  const targetIds = options == null ? void 0 : options.productIds;
480
484
  const isIncremental = Array.isArray(targetIds);
@@ -499,21 +503,28 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
499
503
  (0, import_product.perfMark)("prepareProducts.extractIds", performance.now() - tIds, { count: ids.length });
500
504
  }
501
505
  this.logInfo("获取到商品列表", { productCount: products.length });
502
- const tPrice = performance.now();
503
- const priceData = await this.loadProductsPrice({
504
- ids,
505
- schedule_date,
506
- schedule_datetime: extraContext == null ? void 0 : extraContext.schedule_datetime,
507
- customer_id: extraContext == null ? void 0 : extraContext.customer_id
508
- });
509
- (0, import_product.perfMark)("prepareProducts.loadPrice", performance.now() - tPrice, { count: ids.length });
510
- this.logInfo("获取商品报价单价格成功", { priceDataCount: (priceData == null ? void 0 : priceData.length) ?? 0 });
506
+ let priceData = [];
507
+ if (this.isLegacyPriceFormatterEnabled) {
508
+ const tPrice = performance.now();
509
+ priceData = await this.loadProductsPrice({
510
+ ids,
511
+ schedule_date,
512
+ schedule_datetime: extraContext == null ? void 0 : extraContext.schedule_datetime,
513
+ customer_id: extraContext == null ? void 0 : extraContext.customer_id
514
+ });
515
+ (0, import_product.perfMark)("prepareProducts.loadPrice", performance.now() - tPrice, { count: ids.length });
516
+ this.logInfo("获取商品报价单价格成功", { priceDataCount: (priceData == null ? void 0 : priceData.length) ?? 0 });
517
+ }
511
518
  const context = {
512
519
  schedule_date,
513
520
  schedule_datetime: extraContext == null ? void 0 : extraContext.schedule_datetime,
514
521
  customer_id: extraContext == null ? void 0 : extraContext.customer_id,
515
522
  priceData,
516
523
  locale: (_b = (_a = this.core) == null ? void 0 : _a.context) == null ? void 0 : _b.locale,
524
+ dataVariantEvaluator: (extraContext == null ? void 0 : extraContext.dataVariantEvaluator) || ((_d = (_c = this.core) == null ? void 0 : _c.context) == null ? void 0 : _d.dataVariantEvaluator),
525
+ menuList: (extraContext == null ? void 0 : extraContext.menuList) || [],
526
+ scheduleList: (extraContext == null ? void 0 : extraContext.scheduleList) || [],
527
+ strategy_context: extraContext == null ? void 0 : extraContext.strategy_context,
517
528
  ...extraContext
518
529
  };
519
530
  const tFormat = performance.now();
@@ -553,7 +564,21 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
553
564
  getProductsPriceCacheKey(schedule_date, extraContext, productIds) {
554
565
  const datetime = (extraContext == null ? void 0 : extraContext.schedule_datetime) || schedule_date;
555
566
  const productScope = productIds ? `:products:${productIds.join(",")}` : "";
556
- return `${datetime}:customer:${(extraContext == null ? void 0 : extraContext.customer_id) ?? ""}${productScope}`;
567
+ const strategyScope = this.stringifyStable((extraContext == null ? void 0 : extraContext.strategy_context) || {});
568
+ return `${datetime}:customer:${(extraContext == null ? void 0 : extraContext.customer_id) ?? ""}:strategy:${strategyScope}${productScope}`;
569
+ }
570
+ /**
571
+ * 稳定序列化策略上下文,避免对象 key 顺序不同导致缓存 key 抖动。
572
+ */
573
+ stringifyStable(value) {
574
+ if (value == null)
575
+ return "";
576
+ if (Array.isArray(value))
577
+ return `[${value.map((item) => this.stringifyStable(item)).join(",")}]`;
578
+ if (typeof value === "object") {
579
+ return `{${Object.keys(value).sort().map((key) => `${key}:${this.stringifyStable(value[key])}`).join(",")}}`;
580
+ }
581
+ return String(value);
557
582
  }
558
583
  normalizeProductIds(productIds) {
559
584
  if (!Array.isArray(productIds))
@@ -637,21 +662,49 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
637
662
  return result;
638
663
  }
639
664
  /**
640
- * 注册内置的价格格式化器
641
- * 这个格式化器负责将价格数据应用到商品上
665
+ * 注册内置商品格式化器。
666
+ *
667
+ * 智能定价默认替代报价单逻辑;报价单 formatter 仅在 enableLegacyPriceFormatter=true 时启用。
642
668
  * @private
643
669
  */
644
- registerBuiltinPriceFormatter() {
645
- if (this.isPriceFormatterRegistered) {
646
- console.warn("[ProductsModule] 内置价格格式化器已注册,跳过");
670
+ registerBuiltinProductFormatters() {
671
+ if (this.isBuiltinFormatterRegistered) {
672
+ console.warn("[ProductsModule] 内置商品格式化器已注册,跳过");
647
673
  return;
648
674
  }
649
- const priceFormatter = (products, context) => {
675
+ this.isLegacyPriceFormatterEnabled = this.shouldEnableLegacyPriceFormatter();
676
+ const dataVariantFormatter = (products, context) => {
677
+ var _a, _b;
678
+ const evaluator = context.dataVariantEvaluator || ((_b = (_a = this.core) == null ? void 0 : _a.context) == null ? void 0 : _b.dataVariantEvaluator);
679
+ if (!evaluator || typeof evaluator.resolveProducts !== "function") {
680
+ this.lastScheduleTimePoints = [];
681
+ this.logWarning("智能定价评估器未注入,跳过 DataVariant 格式化", {
682
+ productCount: products.length
683
+ });
684
+ return products;
685
+ }
686
+ const strategyContext = context.strategy_context || {};
687
+ const businessData = {
688
+ products,
689
+ scheduleDateTime: context.schedule_datetime || context.schedule_date,
690
+ scheduleList: context.scheduleList || [],
691
+ menuList: context.menuList || [],
692
+ customerId: context.customer_id,
693
+ channel: strategyContext.channel,
694
+ orderType: strategyContext.orderType || strategyContext.order_type,
695
+ businessCode: strategyContext.business_code ?? strategyContext.businessCode,
696
+ custom: strategyContext
697
+ };
698
+ const result = evaluator.resolveProducts(businessData);
699
+ this.lastScheduleTimePoints = Array.isArray(result == null ? void 0 : result.scheduleTimePoints) ? [...result.scheduleTimePoints] : [];
700
+ return Array.isArray(result == null ? void 0 : result.products) ? result.products : products;
701
+ };
702
+ const legacyPriceFormatter = (products, context) => {
650
703
  if (!context.priceData || context.priceData.length === 0) {
651
- console.log("[ProductsModule] 💰 没有价格数据,跳过价格应用");
704
+ console.log("[ProductsModule] 💰 没有价格数据,跳过 legacy 报价单价格应用");
652
705
  return products;
653
706
  }
654
- console.log(`[ProductsModule] 💰 应用价格数据到 ${products.length} 个商品`);
707
+ console.log(`[ProductsModule] 💰 应用 legacy 报价单价格到 ${products.length} 个商品`);
655
708
  return (0, import_product.applyPriceDataToProducts)(products, context.priceData);
656
709
  };
657
710
  const i18nFormatter = (products, context) => {
@@ -660,15 +713,34 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
660
713
  const detailValueFormatter = (products, context) => {
661
714
  return (0, import_product.applyDetailValueToProducts)(products, context);
662
715
  };
663
- this.formatters.unshift(priceFormatter);
716
+ if (this.isLegacyPriceFormatterEnabled) {
717
+ this.formatters.push(legacyPriceFormatter);
718
+ }
719
+ this.formatters.push(dataVariantFormatter);
664
720
  this.formatters.push(i18nFormatter);
665
721
  this.formatters.push(detailValueFormatter);
666
- this.isPriceFormatterRegistered = true;
667
- console.log("[ProductsModule] ✅ 内置价格格式化器已注册(第 1 个)");
722
+ this.isBuiltinFormatterRegistered = true;
723
+ console.log("[ProductsModule] ✅ 内置商品格式化器已注册(智能定价优先)");
724
+ }
725
+ /**
726
+ * 是否启用旧报价单价格 formatter。
727
+ *
728
+ * 默认关闭,避免报价单逻辑覆盖智能定价结果。
729
+ */
730
+ shouldEnableLegacyPriceFormatter() {
731
+ var _a, _b, _c;
732
+ return ((_a = this.otherParams) == null ? void 0 : _a.enableLegacyPriceFormatter) === true || ((_c = (_b = this.core) == null ? void 0 : _b.context) == null ? void 0 : _c.enableLegacyPriceFormatter) === true;
733
+ }
734
+ /**
735
+ * 返回最近一次 prepareProductsWithPrice / DataVariant 评估提取的变价时间点(HH:mm)。
736
+ * 供 OS Server 商品 query 订阅定时刷新使用。
737
+ */
738
+ getLastScheduleTimePoints() {
739
+ return [...this.lastScheduleTimePoints];
668
740
  }
669
741
  /**
670
742
  * 注册商品格式化器
671
- * 格式化器会按注册顺序依次执行(内置价格格式化器始终是第一个)
743
+ * 格式化器会按注册顺序依次执行;legacy 启用时先兜底报价,再由智能定价覆盖。
672
744
  * @param formatter 格式化函数
673
745
  * @param prepend 是否插入到队列开头(默认 false,追加到末尾)
674
746
  * @example
@@ -683,7 +755,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
683
755
  */
684
756
  registerFormatter(formatter, prepend = false) {
685
757
  if (prepend) {
686
- const insertIndex = this.isPriceFormatterRegistered ? 1 : 0;
758
+ const insertIndex = this.isBuiltinFormatterRegistered ? this.isLegacyPriceFormatterEnabled ? 2 : 1 : 0;
687
759
  this.formatters.splice(insertIndex, 0, formatter);
688
760
  console.log(`[ProductsModule] 📌 已在位置 ${insertIndex + 1} 插入格式化器,当前共 ${this.formatters.length} 个`);
689
761
  } else {
@@ -692,16 +764,17 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
692
764
  }
693
765
  }
694
766
  /**
695
- * 清空所有格式化器(包括内置价格格式化器)
696
- * @param keepBuiltin 是否保留内置价格格式化器(默认 true)
767
+ * 清空所有格式化器(包括内置智能定价格式化器)
768
+ * @param keepBuiltin 是否保留内置智能定价格式化器(默认 true)
697
769
  */
698
770
  clearFormatters(keepBuiltin = true) {
699
- if (keepBuiltin && this.isPriceFormatterRegistered) {
700
- this.formatters = [this.formatters[0]];
701
- console.log("[ProductsModule] 🧹 已清空自定义格式化器,保留内置价格格式化器");
771
+ if (keepBuiltin && this.isBuiltinFormatterRegistered) {
772
+ const builtinCount = this.isLegacyPriceFormatterEnabled ? 2 : 1;
773
+ this.formatters = this.formatters.slice(0, builtinCount);
774
+ console.log("[ProductsModule] 🧹 已清空自定义格式化器,保留内置智能定价格式化器");
702
775
  } else {
703
776
  this.formatters = [];
704
- this.isPriceFormatterRegistered = false;
777
+ this.isBuiltinFormatterRegistered = false;
705
778
  console.log("[ProductsModule] 🧹 已清空所有格式化器");
706
779
  }
707
780
  }
@@ -802,7 +875,8 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
802
875
  "resourceRelation",
803
876
  "bundleGroup.bundleItem",
804
877
  "optionGroup.optionItem",
805
- "variantGroup.variantItem"
878
+ "variantGroup.variantItem",
879
+ "dataVariants"
806
880
  ],
807
881
  open_deposit: 1,
808
882
  is_append_product_description: 1,
@@ -1227,7 +1301,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1227
1301
  * Server 层监听该事件后对变更商品增量执行 prepareProductsWithPrice 并更新缓存
1228
1302
  */
1229
1303
  async processProductSyncMessages() {
1230
- var _a, _b, _c, _d, _e, _f;
1304
+ var _a, _b, _c, _d, _e, _f, _g;
1231
1305
  const messages = [...this.pendingSyncMessages];
1232
1306
  this.pendingSyncMessages = [];
1233
1307
  if (messages.length === 0)
@@ -1281,6 +1355,10 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1281
1355
  if ((_f = msg.relation_product_ids) == null ? void 0 : _f.length) {
1282
1356
  sseRefreshIds.push(...msg.relation_product_ids);
1283
1357
  }
1358
+ } else if (["product_data_variant"].includes(channelKey)) {
1359
+ if ((_g = msg.product_ids) == null ? void 0 : _g.length) {
1360
+ sseRefreshIds.push(...msg.product_ids);
1361
+ }
1284
1362
  }
1285
1363
  }
1286
1364
  const uniqueDeleteIds = [...new Set(deleteIds)];
@@ -110,6 +110,8 @@ export interface ProductSyncMessage {
110
110
  change_types?: string[];
111
111
  /** 关联商品 ID(product_collection / product_category / product_quotation 变更时携带) */
112
112
  relation_product_ids?: number[];
113
+ /** 关联商品 ID(product_data_variant 变更时携带) */
114
+ product_ids?: number[];
113
115
  message_uuid?: string;
114
116
  timestamp?: string;
115
117
  /** 内部标记:来源频道 key */
@@ -124,6 +126,18 @@ export interface ProductFormatterContext {
124
126
  customer_id?: number | string;
125
127
  priceData?: LoadProductsPriceData[];
126
128
  scheduleModule?: any;
129
+ /** 全量餐牌列表,供智能定价 menu_match 等条件判断使用 */
130
+ menuList?: any[];
131
+ /** 全量日程列表,供智能定价 schedule_match 等条件判断使用 */
132
+ scheduleList?: any[];
133
+ /** 外部传入的策略上下文,仅承载定价条件,不参与商品集合筛选 */
134
+ strategy_context?: Record<string, any>;
135
+ /** 智能定价评估器,由宿主统一注入到 PisellOS context */
136
+ dataVariantEvaluator?: {
137
+ resolveProducts: (businessData: any) => {
138
+ products: ProductData[];
139
+ };
140
+ };
127
141
  locale?: string;
128
142
  }
129
143
  /**