@pisell/pisellos 2.2.190 → 2.2.191

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 (67) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +2 -1
  3. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +2 -7
  4. package/dist/modules/Cart/utils/cartProduct.js +13 -2
  5. package/dist/modules/Order/index.d.ts +1 -3
  6. package/dist/modules/Order/index.js +69 -95
  7. package/dist/modules/Order/types.d.ts +9 -6
  8. package/dist/modules/Order/utils.d.ts +9 -2
  9. package/dist/modules/Order/utils.js +36 -13
  10. package/dist/modules/SalesSummary/types.d.ts +3 -1
  11. package/dist/modules/SalesSummary/utils.js +7 -3
  12. package/dist/server/modules/order/index.d.ts +126 -6
  13. package/dist/server/modules/order/index.js +1204 -285
  14. package/dist/server/modules/order/types.d.ts +0 -2
  15. package/dist/server/modules/products/index.d.ts +90 -6
  16. package/dist/server/modules/products/index.js +1017 -361
  17. package/dist/solution/BaseSales/index.js +13 -7
  18. package/dist/solution/BaseSales/types.d.ts +8 -5
  19. package/dist/solution/BaseSales/types.js +2 -3
  20. package/dist/solution/BaseSales/utils/cartPromotion.js +7 -7
  21. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  22. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +16 -13
  23. package/dist/solution/BaseSales/utils.d.ts +1 -1
  24. package/dist/solution/BaseSales/utils.js +22 -19
  25. package/dist/solution/BookingByStep/index.d.ts +1 -1
  26. package/dist/solution/BookingTicket/index.d.ts +1 -1
  27. package/dist/solution/ScanOrder/index.js +7 -5
  28. package/dist/solution/ScanOrder/types.d.ts +8 -5
  29. package/dist/solution/ScanOrder/types.js +2 -3
  30. package/dist/solution/ScanOrder/utils.d.ts +1 -1
  31. package/dist/solution/ScanOrder/utils.js +26 -23
  32. package/dist/solution/VenueBooking/index.js +5 -3
  33. package/dist/solution/VenueBooking/sales-section-4-annotated.json +0 -2
  34. package/lib/model/strategy/adapter/promotion/index.js +0 -49
  35. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +2 -1
  36. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +2 -1
  37. package/lib/modules/Cart/utils/cartProduct.js +9 -2
  38. package/lib/modules/Order/index.d.ts +1 -3
  39. package/lib/modules/Order/index.js +45 -58
  40. package/lib/modules/Order/types.d.ts +9 -6
  41. package/lib/modules/Order/utils.d.ts +9 -2
  42. package/lib/modules/Order/utils.js +41 -10
  43. package/lib/modules/SalesSummary/types.d.ts +3 -1
  44. package/lib/modules/SalesSummary/utils.js +5 -3
  45. package/lib/server/modules/order/index.d.ts +126 -6
  46. package/lib/server/modules/order/index.js +722 -42
  47. package/lib/server/modules/order/types.d.ts +0 -2
  48. package/lib/server/modules/products/index.d.ts +90 -6
  49. package/lib/server/modules/products/index.js +492 -30
  50. package/lib/solution/BaseSales/index.js +14 -5
  51. package/lib/solution/BaseSales/types.d.ts +8 -5
  52. package/lib/solution/BaseSales/utils/cartPromotion.js +6 -6
  53. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.d.ts +1 -0
  54. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +21 -14
  55. package/lib/solution/BaseSales/utils.d.ts +1 -1
  56. package/lib/solution/BaseSales/utils.js +11 -7
  57. package/lib/solution/BookingByStep/index.d.ts +1 -1
  58. package/lib/solution/BookingTicket/index.d.ts +1 -1
  59. package/lib/solution/ScanOrder/index.js +4 -4
  60. package/lib/solution/ScanOrder/types.d.ts +8 -5
  61. package/lib/solution/ScanOrder/utils.d.ts +1 -1
  62. package/lib/solution/ScanOrder/utils.js +12 -8
  63. package/lib/solution/VenueBooking/index.js +2 -2
  64. package/lib/solution/VenueBooking/sales-section-4-annotated.json +0 -2
  65. package/package.json +1 -1
  66. package/dist/solution/Checkout/appointmentDemo.json +0 -1
  67. package/lib/solution/Checkout/appointmentDemo.json +0 -1
@@ -27,7 +27,11 @@ var import_Quotation = require("../../../modules/Quotation");
27
27
  var import_types = require("./types");
28
28
  var import_product = require("../../utils/product");
29
29
  var INDEXDB_STORE_NAME = "products";
30
+ var PRODUCT_FLOW_LOG_WINDOW_KEY = "__productModuleFlowLogs";
31
+ var PRODUCT_WRITE_STATS_WINDOW_KEY = "__productModuleWriteStats";
32
+ var PRODUCT_FLOW_LOG_MAX_LENGTH = 1e3;
30
33
  var PRODUCT_SYNC_DEBOUNCE_MS = 1e4;
34
+ var PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS = 3e4;
31
35
  var ProductsModule = class extends import_BaseModule.BaseModule {
32
36
  constructor(name, version) {
33
37
  super(name, version);
@@ -46,6 +50,14 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
46
50
  this.isPriceFormatterRegistered = false;
47
51
  this.quotationBridgeLoadedKey = "";
48
52
  this.pendingSyncMessages = [];
53
+ /** IndexDB 写入序号,便于日志关联 */
54
+ this.productFlowWriteSeq = 0;
55
+ /** 最近一次 SSE 全量拉取完成时间 */
56
+ this.lastServerFullFetchAtMs = 0;
57
+ /** 最近一次 preload 完成时间(含 IndexDB 命中) */
58
+ this.lastPreloadCompletedAtMs = 0;
59
+ /** IndexDB 写入串行队列,避免快照与 patch 并发交错 */
60
+ this.productIndexDBSaveQueue = Promise.resolve();
49
61
  }
50
62
  async initialize(core, options) {
51
63
  var _a;
@@ -75,6 +87,12 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
75
87
  this.registerBuiltinPriceFormatter();
76
88
  this.initProductDataSource();
77
89
  this.setupProductSync();
90
+ this.initProductFlowLogs();
91
+ this.pushProductFlowLog("initialize.complete", {
92
+ hasDbManager: !!this.dbManager,
93
+ hasLogger: !!this.logger,
94
+ initialProductCount: this.store.list.length
95
+ });
78
96
  this.logInfo("模块初始化完成", {
79
97
  hasDbManager: !!this.dbManager,
80
98
  hasLogger: !!this.logger,
@@ -131,6 +149,138 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
131
149
  }
132
150
  } catch {
133
151
  }
152
+ this.pushProductFlowLog(`error.${title}`, metadata);
153
+ }
154
+ /**
155
+ * 初始化 window 上的商品流程调试日志。
156
+ *
157
+ * @example
158
+ * window.clearProductModuleFlowLogs()
159
+ * copy(window.exportProductModuleFlowLogs?.() || '[]')
160
+ */
161
+ initProductFlowLogs() {
162
+ try {
163
+ const win = typeof globalThis !== "undefined" ? globalThis.window : void 0;
164
+ if (!win)
165
+ return;
166
+ win[PRODUCT_FLOW_LOG_WINDOW_KEY] = [];
167
+ win[PRODUCT_WRITE_STATS_WINDOW_KEY] = {};
168
+ win.clearProductModuleFlowLogs = () => {
169
+ win[PRODUCT_FLOW_LOG_WINDOW_KEY] = [];
170
+ win[PRODUCT_WRITE_STATS_WINDOW_KEY] = {};
171
+ this.productFlowWriteSeq = 0;
172
+ this.pushProductFlowLog("debug.clear", {});
173
+ };
174
+ win.exportProductModuleFlowLogs = () => {
175
+ const logs = win[PRODUCT_FLOW_LOG_WINDOW_KEY] || [];
176
+ return JSON.stringify(logs, null, 2);
177
+ };
178
+ win.exportProductModuleWriteStats = () => {
179
+ const stats = win[PRODUCT_WRITE_STATS_WINDOW_KEY] || {};
180
+ return JSON.stringify(stats, null, 2);
181
+ };
182
+ win.summarizeProductModuleWrites = (topN = 20) => {
183
+ const stats = win[PRODUCT_WRITE_STATS_WINDOW_KEY] || {};
184
+ return Object.entries(stats).map(([productId, entries]) => {
185
+ const list = entries || [];
186
+ const sources = [...new Set(list.map((item) => item.source))];
187
+ const last = list[list.length - 1];
188
+ return {
189
+ productId: Number(productId),
190
+ writeCount: list.length,
191
+ sources,
192
+ lastWriteAt: (last == null ? void 0 : last.ts) || ""
193
+ };
194
+ }).sort((a, b) => b.writeCount - a.writeCount).slice(0, topN);
195
+ };
196
+ this.pushProductFlowLog("debug.init", {
197
+ logKey: PRODUCT_FLOW_LOG_WINDOW_KEY,
198
+ writeStatsKey: PRODUCT_WRITE_STATS_WINDOW_KEY,
199
+ indexDbStrategy: "全量 replaceProductsSnapshotInIndexDB + 增量 patchProductsInIndexDB",
200
+ helpers: [
201
+ "clearProductModuleFlowLogs",
202
+ "exportProductModuleFlowLogs",
203
+ "exportProductModuleWriteStats",
204
+ "summarizeProductModuleWrites"
205
+ ]
206
+ });
207
+ } catch {
208
+ }
209
+ }
210
+ /**
211
+ * 写入 window 商品流程调试日志。
212
+ *
213
+ * @example
214
+ * this.pushProductFlowLog('mergeProductsToStore.start', { count: 3 })
215
+ */
216
+ pushProductFlowLog(step, metadata) {
217
+ try {
218
+ const win = typeof globalThis !== "undefined" ? globalThis.window : void 0;
219
+ if (!win)
220
+ return;
221
+ if (!Array.isArray(win[PRODUCT_FLOW_LOG_WINDOW_KEY])) {
222
+ win[PRODUCT_FLOW_LOG_WINDOW_KEY] = [];
223
+ }
224
+ const entry = {
225
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
226
+ step,
227
+ metadata: metadata || {}
228
+ };
229
+ win[PRODUCT_FLOW_LOG_WINDOW_KEY].push(entry);
230
+ const logs = win[PRODUCT_FLOW_LOG_WINDOW_KEY];
231
+ if (logs.length > PRODUCT_FLOW_LOG_MAX_LENGTH) {
232
+ logs.splice(0, logs.length - PRODUCT_FLOW_LOG_MAX_LENGTH);
233
+ }
234
+ console.log(`[ProductFlow] ${step}`, metadata || {});
235
+ } catch {
236
+ }
237
+ }
238
+ /** 当前内存 store 快照,供流程日志使用 */
239
+ getProductStoreSnapshot() {
240
+ return {
241
+ storeListCount: this.store.list.length,
242
+ storeMapCount: this.store.map.size,
243
+ pendingSyncCount: this.pendingSyncMessages.length
244
+ };
245
+ }
246
+ /**
247
+ * 记录 IndexDB 写入统计(按 productId 聚合)。
248
+ *
249
+ * @example
250
+ * this.trackProductWrite('pubsub.mergeProductsToStore', 'clear+bulkUpdate', products, { 1: 'update' })
251
+ */
252
+ trackProductWrite(source, writeMethod, products, mergeActions = {}, defaultMergeAction = "update") {
253
+ this.productFlowWriteSeq += 1;
254
+ const writeSeq = this.productFlowWriteSeq;
255
+ try {
256
+ const win = typeof globalThis !== "undefined" ? globalThis.window : void 0;
257
+ if (!win)
258
+ return writeSeq;
259
+ if (!win[PRODUCT_WRITE_STATS_WINDOW_KEY]) {
260
+ win[PRODUCT_WRITE_STATS_WINDOW_KEY] = {};
261
+ }
262
+ const stats = win[PRODUCT_WRITE_STATS_WINDOW_KEY];
263
+ for (const product of products) {
264
+ const productId = product == null ? void 0 : product.id;
265
+ if (productId === void 0 || productId === null)
266
+ continue;
267
+ const mergeAction = mergeActions[productId] || defaultMergeAction;
268
+ const entry = {
269
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
270
+ writeSeq,
271
+ source,
272
+ writeMethod,
273
+ productId,
274
+ mergeAction
275
+ };
276
+ const key = String(productId);
277
+ if (!stats[key])
278
+ stats[key] = [];
279
+ stats[key].push(entry);
280
+ }
281
+ } catch {
282
+ }
283
+ return writeSeq;
134
284
  }
135
285
  /**
136
286
  * 加载商品价格(原始方法,不带缓存)
@@ -703,6 +853,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
703
853
  this.logWarning("loadProductsByServer: ProductDataSource 不可用");
704
854
  return [];
705
855
  }
856
+ this.pushProductFlowLog("loadProductsByServer.start", this.getProductStoreSnapshot());
706
857
  this.logInfo("开始通过 DataSource SSE 加载商品列表");
707
858
  const t0 = performance.now();
708
859
  try {
@@ -710,15 +861,24 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
710
861
  const productList = await this.productDataSource.run({ sse: {} });
711
862
  const list = productList || [];
712
863
  (0, import_product.perfMark)("loadProductsByServer.SSE", performance.now() - tSSE, { count: list.length });
864
+ this.pushProductFlowLog("loadProductsByServer.sseSuccess", { count: list.length });
713
865
  this.logInfo("通过 DataSource SSE 加载商品列表成功", {
714
866
  productCount: list.length,
715
867
  duration: `${Math.round(performance.now() - t0)}ms`
716
868
  });
717
- await this.saveProductsToIndexDB(list);
869
+ await this.replaceProductsSnapshotInIndexDB(list, "loadProductsByServer");
870
+ this.lastServerFullFetchAtMs = Date.now();
718
871
  await this.core.effects.emit(import_types.ProductsHooks.onProductsLoaded, list);
872
+ this.pushProductFlowLog("loadProductsByServer.done", {
873
+ count: list.length,
874
+ ...this.getProductStoreSnapshot()
875
+ });
719
876
  (0, import_product.perfMark)("loadProductsByServer", performance.now() - t0, { count: list.length });
720
877
  return list;
721
878
  } catch (error) {
879
+ this.pushProductFlowLog("loadProductsByServer.failed", {
880
+ error: error instanceof Error ? error.message : String(error)
881
+ });
722
882
  const duration = Math.round(performance.now() - t0);
723
883
  const errorMessage = error instanceof Error ? error.message : String(error);
724
884
  console.error("[Products] 加载商品数据失败:", error);
@@ -811,11 +971,20 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
811
971
  * @param params 查询参数
812
972
  */
813
973
  async loadProductsByServerHttp(params) {
974
+ this.pushProductFlowLog("loadProductsByServerHttp.start", {
975
+ params: params || {},
976
+ ...this.getProductStoreSnapshot()
977
+ });
814
978
  const productList = await this.fetchProductsByHttp(params);
815
979
  if (productList.length > 0) {
816
- await this.saveProductsToIndexDB(productList);
980
+ await this.replaceProductsSnapshotInIndexDB(productList, "loadProductsByServerHttp");
981
+ this.lastServerFullFetchAtMs = Date.now();
817
982
  await this.core.effects.emit(import_types.ProductsHooks.onProductsLoaded, productList);
818
983
  }
984
+ this.pushProductFlowLog("loadProductsByServerHttp.done", {
985
+ count: productList.length,
986
+ ...this.getProductStoreSnapshot()
987
+ });
819
988
  return productList;
820
989
  }
821
990
  /**
@@ -860,6 +1029,12 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
860
1029
  */
861
1030
  async removeProductsByIds(ids) {
862
1031
  const idSet = new Set(ids);
1032
+ this.pushProductFlowLog("removeProductsByIds.start", {
1033
+ ids,
1034
+ count: ids.length,
1035
+ writeStrategy: "delete-by-id",
1036
+ ...this.getProductStoreSnapshot()
1037
+ });
863
1038
  this.logInfo("removeProductsByIds", { ids, count: ids.length });
864
1039
  this.store.list = this.store.list.filter((p) => !idSet.has(p.id));
865
1040
  for (const id of ids) {
@@ -870,6 +1045,13 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
870
1045
  for (const id of ids) {
871
1046
  await this.dbManager.delete(INDEXDB_STORE_NAME, id);
872
1047
  }
1048
+ this.trackProductWrite(
1049
+ "pubsub.removeProductsByIds",
1050
+ "delete",
1051
+ ids.map((id) => ({ id })),
1052
+ Object.fromEntries(ids.map((id) => [id, "delete"])),
1053
+ "delete"
1054
+ );
873
1055
  } catch (error) {
874
1056
  const errorMessage = error instanceof Error ? error.message : String(error);
875
1057
  this.logError("removeProductsByIds: IndexDB 删除失败", { ids, error: errorMessage });
@@ -882,6 +1064,10 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
882
1064
  );
883
1065
  }
884
1066
  this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1067
+ this.pushProductFlowLog("removeProductsByIds.done", {
1068
+ ids,
1069
+ ...this.getProductStoreSnapshot()
1070
+ });
885
1071
  this.logInfo("removeProductsByIds 完成", { remaining: this.store.list.length });
886
1072
  }
887
1073
  /**
@@ -890,6 +1076,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
890
1076
  */
891
1077
  async refreshProducts() {
892
1078
  const tTotal = performance.now();
1079
+ this.pushProductFlowLog("refreshProducts.start", this.getProductStoreSnapshot());
893
1080
  this.logInfo("refreshProducts 开始");
894
1081
  const products = await this.loadProductsByServer();
895
1082
  if (products && products.length > 0) {
@@ -901,6 +1088,11 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
901
1088
  this.logWarning("refreshProducts: 服务器未返回数据");
902
1089
  }
903
1090
  (0, import_product.perfMark)("refreshProducts", performance.now() - tTotal, { count: this.store.list.length });
1091
+ this.pushProductFlowLog("refreshProducts.done", {
1092
+ count: this.store.list.length,
1093
+ durationMs: Math.round(performance.now() - tTotal),
1094
+ ...this.getProductStoreSnapshot()
1095
+ });
904
1096
  return this.store.list;
905
1097
  }
906
1098
  /**
@@ -952,31 +1144,144 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
952
1144
  }
953
1145
  }
954
1146
  /**
955
- * 保存商品数据到 IndexDB(平铺存储)
956
- * @private
1147
+ * 串行执行商品 IndexDB 写入,避免快照替换与增量 patch 并发交错。
1148
+ *
1149
+ * @example
1150
+ * await this.runInProductIndexDBSaveQueue(async () => {
1151
+ * await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products)
1152
+ * })
957
1153
  */
958
- async saveProductsToIndexDB(products) {
1154
+ async runInProductIndexDBSaveQueue(task) {
1155
+ const queuedTask = this.productIndexDBSaveQueue.then(task, task);
1156
+ this.productIndexDBSaveQueue = queuedTask.then(() => void 0, () => void 0);
1157
+ return queuedTask;
1158
+ }
1159
+ /**
1160
+ * 全量快照写入 IndexDB:clear + bulkUpdate,仅用于 SSE/HTTP 全量拉取。
1161
+ *
1162
+ * @example
1163
+ * await this.replaceProductsSnapshotInIndexDB(list, 'loadProductsByServer')
1164
+ */
1165
+ async replaceProductsSnapshotInIndexDB(products, source) {
959
1166
  if (!this.dbManager) {
960
- this.logWarning("saveProductsToIndexDB: dbManager 不可用");
1167
+ this.pushProductFlowLog("replaceProductsSnapshotInIndexDB.skip", {
1168
+ source,
1169
+ reason: "no_dbManager",
1170
+ inputCount: products.length
1171
+ });
1172
+ this.logWarning("replaceProductsSnapshotInIndexDB: dbManager 不可用");
961
1173
  return;
962
1174
  }
963
- this.logInfo("开始保存商品数据到 IndexDB", { productCount: products.length });
964
1175
  try {
965
- const t0 = performance.now();
966
- await this.dbManager.clear(INDEXDB_STORE_NAME);
967
- await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
968
- (0, import_product.perfMark)("saveProductsToIndexDB", performance.now() - t0, { count: products.length });
969
- console.log(
970
- `[Products] 已将 ${products.length} 个商品平铺保存到 IndexDB`
971
- );
972
- this.logInfo("商品数据已保存到 IndexDB", { productCount: products.length });
1176
+ await this.runInProductIndexDBSaveQueue(async () => {
1177
+ this.pushProductFlowLog("replaceProductsSnapshotInIndexDB.start", {
1178
+ source,
1179
+ writeStrategy: "clear+bulkUpdate",
1180
+ inputCount: products.length,
1181
+ storeListCount: this.store.list.length
1182
+ });
1183
+ this.logInfo("replaceProductsSnapshotInIndexDB 开始", { source, productCount: products.length });
1184
+ const t0 = performance.now();
1185
+ await this.dbManager.clear(INDEXDB_STORE_NAME);
1186
+ this.pushProductFlowLog("replaceProductsSnapshotInIndexDB.cleared", {
1187
+ source,
1188
+ inputCount: products.length
1189
+ });
1190
+ if (products.length > 0) {
1191
+ await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products);
1192
+ }
1193
+ const writeSeq = this.trackProductWrite(
1194
+ source,
1195
+ "clear+bulkUpdate",
1196
+ products,
1197
+ {},
1198
+ "snapshot"
1199
+ );
1200
+ (0, import_product.perfMark)("replaceProductsSnapshotInIndexDB", performance.now() - t0, { count: products.length });
1201
+ this.pushProductFlowLog("replaceProductsSnapshotInIndexDB.done", {
1202
+ source,
1203
+ writeSeq,
1204
+ count: products.length
1205
+ });
1206
+ this.logInfo("replaceProductsSnapshotInIndexDB 完成", { source, productCount: products.length });
1207
+ });
973
1208
  } catch (error) {
974
1209
  const errorMessage = error instanceof Error ? error.message : String(error);
975
- console.error("[Products] 保存数据到 IndexDB 失败:", error);
976
- this.logError("保存数据到 IndexDB 失败", {
1210
+ this.pushProductFlowLog("replaceProductsSnapshotInIndexDB.error", {
1211
+ source,
977
1212
  productCount: products.length,
978
1213
  error: errorMessage
979
1214
  });
1215
+ this.logError("全量保存商品到 IndexDB 失败", {
1216
+ source,
1217
+ productCount: products.length,
1218
+ error: errorMessage
1219
+ });
1220
+ }
1221
+ }
1222
+ /**
1223
+ * 增量 upsert 商品到 IndexDB,不清空表;用于 pubsub 批次合并后的变更商品。
1224
+ *
1225
+ * @example
1226
+ * await this.patchProductsInIndexDB(changedProducts, 'pubsub.processProductSyncMessages', mergeActions)
1227
+ */
1228
+ async patchProductsInIndexDB(products, source, mergeActions = {}) {
1229
+ if (!this.dbManager || products.length === 0) {
1230
+ this.pushProductFlowLog("patchProductsInIndexDB.skip", {
1231
+ source,
1232
+ hasDbManager: !!this.dbManager,
1233
+ inputCount: products.length
1234
+ });
1235
+ return;
1236
+ }
1237
+ const validProducts = products.filter((p) => (p == null ? void 0 : p.id) !== void 0 && (p == null ? void 0 : p.id) !== null);
1238
+ if (validProducts.length === 0) {
1239
+ this.pushProductFlowLog("patchProductsInIndexDB.skipNoId", { source, inputCount: products.length });
1240
+ return;
1241
+ }
1242
+ try {
1243
+ await this.runInProductIndexDBSaveQueue(async () => {
1244
+ let writeMethod = "none";
1245
+ this.pushProductFlowLog("patchProductsInIndexDB.start", {
1246
+ source,
1247
+ writeStrategy: "bulkUpdate-patch",
1248
+ count: validProducts.length,
1249
+ productIds: validProducts.map((p) => p.id),
1250
+ storeListCount: this.store.list.length
1251
+ });
1252
+ this.logInfo("patchProductsInIndexDB 开始", { source, count: validProducts.length });
1253
+ const t0 = performance.now();
1254
+ if (typeof this.dbManager.bulkUpdate === "function") {
1255
+ writeMethod = "bulkUpdate";
1256
+ await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, validProducts);
1257
+ } else if (typeof this.dbManager.update === "function") {
1258
+ writeMethod = "update";
1259
+ for (const product of validProducts) {
1260
+ await this.dbManager.update(INDEXDB_STORE_NAME, product);
1261
+ }
1262
+ }
1263
+ const writeSeq = this.trackProductWrite(source, writeMethod, validProducts, mergeActions);
1264
+ (0, import_product.perfMark)("patchProductsInIndexDB", performance.now() - t0, { count: validProducts.length });
1265
+ this.pushProductFlowLog("patchProductsInIndexDB.done", {
1266
+ source,
1267
+ writeSeq,
1268
+ writeMethod,
1269
+ count: validProducts.length
1270
+ });
1271
+ this.logInfo("patchProductsInIndexDB 完成", { source, count: validProducts.length });
1272
+ });
1273
+ } catch (error) {
1274
+ const errorMessage = error instanceof Error ? error.message : String(error);
1275
+ this.pushProductFlowLog("patchProductsInIndexDB.error", {
1276
+ source,
1277
+ error: errorMessage,
1278
+ productCount: validProducts.length
1279
+ });
1280
+ this.logError("增量保存商品到 IndexDB 失败", {
1281
+ source,
1282
+ error: errorMessage,
1283
+ productCount: validProducts.length
1284
+ });
980
1285
  }
981
1286
  }
982
1287
  /**
@@ -999,6 +1304,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
999
1304
  async preload() {
1000
1305
  console.log("[Products] 开始预加载数据...");
1001
1306
  const tTotal = performance.now();
1307
+ this.pushProductFlowLog("preload.start", this.getProductStoreSnapshot());
1002
1308
  this.logInfo("开始预加载数据");
1003
1309
  try {
1004
1310
  const tIndexDB = performance.now();
@@ -1023,6 +1329,12 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1023
1329
  duration: `${Math.round(performance.now() - tTotal)}ms`,
1024
1330
  source: "IndexDB"
1025
1331
  });
1332
+ this.pushProductFlowLog("preload.done", {
1333
+ source: "IndexDB",
1334
+ loadedCount: cachedData.length,
1335
+ ...this.getProductStoreSnapshot()
1336
+ });
1337
+ this.lastPreloadCompletedAtMs = Date.now();
1026
1338
  return;
1027
1339
  }
1028
1340
  console.log("[Products] IndexDB 中没有缓存数据,从服务器加载...");
@@ -1050,7 +1362,14 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1050
1362
  duration: `${Math.round(performance.now() - tTotal)}ms`,
1051
1363
  source: "Server"
1052
1364
  });
1365
+ this.pushProductFlowLog("preload.done", {
1366
+ source: "Server",
1367
+ loadedCount: products.length,
1368
+ ...this.getProductStoreSnapshot()
1369
+ });
1370
+ this.lastPreloadCompletedAtMs = Date.now();
1053
1371
  } else {
1372
+ this.pushProductFlowLog("preload.empty", this.getProductStoreSnapshot());
1054
1373
  (0, import_product.perfMark)("preload(empty)", performance.now() - tTotal, { source: "empty" });
1055
1374
  this.logWarning("预加载完成但未获取到数据", {
1056
1375
  duration: `${Math.round(performance.now() - tTotal)}ms`
@@ -1096,6 +1415,17 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1096
1415
  action_filed: data.action_filed
1097
1416
  });
1098
1417
  this.pendingSyncMessages.push({ ...data, _channelKey: channelKey });
1418
+ this.pushProductFlowLog("pubsub.messageEnqueued", {
1419
+ channelKey,
1420
+ action: data.action,
1421
+ operation: data.operation,
1422
+ id: data.id,
1423
+ ids: data.ids,
1424
+ hasBody: !!data.body,
1425
+ changeTypes: data.change_types,
1426
+ pendingSyncCount: this.pendingSyncMessages.length,
1427
+ debounceMs: PRODUCT_SYNC_DEBOUNCE_MS
1428
+ });
1099
1429
  if (this.syncTimer) {
1100
1430
  clearTimeout(this.syncTimer);
1101
1431
  }
@@ -1148,6 +1478,10 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1148
1478
  this.pendingSyncMessages = [];
1149
1479
  if (messages.length === 0)
1150
1480
  return;
1481
+ this.pushProductFlowLog("processProductSyncMessages.start", {
1482
+ messageCount: messages.length,
1483
+ ...this.getProductStoreSnapshot()
1484
+ });
1151
1485
  this.logInfo("processProductSyncMessages: 开始处理", { count: messages.length });
1152
1486
  const deleteIds = [];
1153
1487
  const bodyUpdates = /* @__PURE__ */ new Map();
@@ -1202,22 +1536,64 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1202
1536
  const uniqueDeleteIds = [...new Set(deleteIds)];
1203
1537
  const uniqueSSEIds = [...new Set(sseRefreshIds)];
1204
1538
  const uniquePriceIds = [...new Set(priceRefreshIds)];
1539
+ this.pushProductFlowLog("processProductSyncMessages.parsed", {
1540
+ deleteIds: uniqueDeleteIds,
1541
+ bodyUpdateIds: [...bodyUpdates.keys()],
1542
+ sseRefreshIds: uniqueSSEIds,
1543
+ priceRefreshIds: uniquePriceIds,
1544
+ shouldClearPriceCache
1545
+ });
1205
1546
  if (uniqueDeleteIds.length > 0) {
1206
1547
  await this.removeProductsByIds(uniqueDeleteIds);
1207
1548
  }
1549
+ const patchProductMap = /* @__PURE__ */ new Map();
1550
+ const patchMergeActions = {};
1551
+ let storeMutated = uniqueDeleteIds.length > 0;
1208
1552
  if (bodyUpdates.size > 0) {
1209
- await this.applyBodyUpdatesToStore(bodyUpdates);
1553
+ const bodyResult = await this.applyBodyUpdatesToStore(bodyUpdates, {
1554
+ skipIndexDB: true,
1555
+ skipEmit: true
1556
+ });
1557
+ for (const product of bodyResult.changedProducts) {
1558
+ patchProductMap.set(product.id, product);
1559
+ patchMergeActions[product.id] = bodyResult.mergeActions[product.id];
1560
+ }
1561
+ storeMutated = true;
1210
1562
  }
1563
+ let freshProducts = [];
1211
1564
  if (uniqueSSEIds.length > 0) {
1212
- const freshProducts = await this.fetchProductsBySSE(uniqueSSEIds);
1565
+ freshProducts = await this.fetchProductsBySSE(uniqueSSEIds);
1213
1566
  if (freshProducts.length > 0) {
1214
- await this.mergeProductsToStore(freshProducts);
1567
+ const mergeResult = await this.mergeProductsToStore(freshProducts, {
1568
+ skipIndexDB: true,
1569
+ skipEmit: true
1570
+ });
1571
+ for (const product of mergeResult.changedProducts) {
1572
+ patchProductMap.set(product.id, product);
1573
+ patchMergeActions[product.id] = mergeResult.mergeActions[product.id];
1574
+ }
1575
+ storeMutated = true;
1215
1576
  }
1216
1577
  this.logInfo("processProductSyncMessages: SSE 增量更新完成123", {
1217
1578
  requestedCount: uniqueSSEIds.length,
1218
1579
  receivedCount: freshProducts.length
1219
1580
  });
1220
1581
  }
1582
+ const patchProducts = [...patchProductMap.values()];
1583
+ if (patchProducts.length > 0) {
1584
+ await this.patchProductsInIndexDB(
1585
+ patchProducts,
1586
+ "pubsub.processProductSyncMessages",
1587
+ patchMergeActions
1588
+ );
1589
+ this.pushProductFlowLog("processProductSyncMessages.patchDone", {
1590
+ patchCount: patchProducts.length,
1591
+ patchProductIds: patchProducts.map((p) => p.id)
1592
+ });
1593
+ }
1594
+ if (storeMutated) {
1595
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1596
+ }
1221
1597
  const allChangedIds = [.../* @__PURE__ */ new Set([
1222
1598
  ...Array.from(bodyUpdates.keys()),
1223
1599
  ...uniqueSSEIds,
@@ -1233,6 +1609,7 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1233
1609
  });
1234
1610
  const hasChanges = uniqueDeleteIds.length > 0 || allChangedIds.length > 0 || shouldClearPriceCache;
1235
1611
  if (!hasChanges) {
1612
+ this.pushProductFlowLog("processProductSyncMessages.noChanges", {});
1236
1613
  this.logInfo("processProductSyncMessages: 没有变更,不触发 onProductsSyncCompleted");
1237
1614
  return;
1238
1615
  }
@@ -1248,6 +1625,14 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1248
1625
  await this.core.effects.emit(import_types.ProductsHooks.onProductsSyncCompleted, {
1249
1626
  changedIds: allChangedIds
1250
1627
  });
1628
+ this.pushProductFlowLog("processProductSyncMessages.done", {
1629
+ deleteCount: uniqueDeleteIds.length,
1630
+ bodyUpdateCount: bodyUpdates.size,
1631
+ sseRefreshCount: uniqueSSEIds.length,
1632
+ allChangedIds,
1633
+ shouldClearPriceCache,
1634
+ ...this.getProductStoreSnapshot()
1635
+ });
1251
1636
  }
1252
1637
  /**
1253
1638
  * 通过 SSE 按 ids 增量拉取商品数据
@@ -1281,19 +1666,32 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1281
1666
  /**
1282
1667
  * 将 body 完整数据直接覆盖到本地 store(不调用报价单接口)
1283
1668
  * 已存在的 → 直接替换;不存在的 → 追加
1284
- * 同时更新 Map 缓存、IndexDB,触发 onProductsChanged
1285
- * 价格缓存由 processProductSyncMessages 末尾统一清除
1669
+ *
1670
+ * @example
1671
+ * await this.applyBodyUpdatesToStore(bodyUpdates, { skipIndexDB: true })
1286
1672
  */
1287
- async applyBodyUpdatesToStore(bodyUpdates) {
1673
+ async applyBodyUpdatesToStore(bodyUpdates, options) {
1674
+ const changedProductIds = [...bodyUpdates.keys()];
1675
+ this.pushProductFlowLog("applyBodyUpdatesToStore.start", {
1676
+ changedProductIds,
1677
+ count: bodyUpdates.size,
1678
+ skipIndexDB: !!(options == null ? void 0 : options.skipIndexDB),
1679
+ ...this.getProductStoreSnapshot()
1680
+ });
1288
1681
  this.logInfo("applyBodyUpdatesToStore: 开始", { count: bodyUpdates.size });
1289
1682
  let updatedCount = 0;
1290
1683
  let newCount = 0;
1291
1684
  const appliedIds = /* @__PURE__ */ new Set();
1685
+ const mergeActions = {};
1686
+ const changedProducts = [];
1292
1687
  this.store.list = this.store.list.map((p) => {
1293
1688
  if (bodyUpdates.has(p.id)) {
1294
1689
  updatedCount++;
1295
1690
  appliedIds.add(p.id);
1296
- return bodyUpdates.get(p.id);
1691
+ mergeActions[p.id] = "update";
1692
+ const next = bodyUpdates.get(p.id);
1693
+ changedProducts.push(next);
1694
+ return next;
1297
1695
  }
1298
1696
  return p;
1299
1697
  });
@@ -1301,31 +1699,51 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1301
1699
  if (!appliedIds.has(id)) {
1302
1700
  this.store.list.push(body);
1303
1701
  newCount++;
1702
+ mergeActions[id] = "insert";
1703
+ changedProducts.push(body);
1304
1704
  }
1305
1705
  }
1306
1706
  this.syncProductsMap();
1307
- await this.saveProductsToIndexDB(this.store.list);
1308
- this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1707
+ if (!(options == null ? void 0 : options.skipIndexDB)) {
1708
+ await this.patchProductsInIndexDB(changedProducts, "pubsub.bodyUpdate", mergeActions);
1709
+ }
1710
+ if (!(options == null ? void 0 : options.skipEmit)) {
1711
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1712
+ }
1713
+ this.pushProductFlowLog("applyBodyUpdatesToStore.done", {
1714
+ changedProductIds,
1715
+ updatedCount,
1716
+ newCount,
1717
+ totalCount: this.store.list.length,
1718
+ indexDbPatchCount: (options == null ? void 0 : options.skipIndexDB) ? 0 : changedProducts.length
1719
+ });
1309
1720
  this.logInfo("applyBodyUpdatesToStore: 完成", {
1310
1721
  updatedCount,
1311
1722
  newCount,
1312
1723
  totalCount: this.store.list.length
1313
1724
  });
1725
+ return { changedProducts, mergeActions, updatedCount, newCount };
1314
1726
  }
1315
1727
  /**
1316
1728
  * 将增量拉取的商品合并到 store
1317
1729
  * 已存在的 → 替换;新的 → 追加
1318
- * 同时更新 store.map、IndexDB,触发 onProductsChanged
1730
+ *
1731
+ * @example
1732
+ * await this.mergeProductsToStore(freshProducts, { skipIndexDB: true })
1319
1733
  */
1320
- async mergeProductsToStore(freshProducts) {
1734
+ async mergeProductsToStore(freshProducts, options) {
1321
1735
  const freshMap = /* @__PURE__ */ new Map();
1322
1736
  for (const p of freshProducts) {
1323
1737
  freshMap.set(p.id, p);
1324
1738
  }
1739
+ const mergeActions = {};
1740
+ const changedProducts = [];
1325
1741
  const updatedList = this.store.list.map((p) => {
1326
1742
  if (freshMap.has(p.id)) {
1327
1743
  const fresh = freshMap.get(p.id);
1328
1744
  freshMap.delete(p.id);
1745
+ mergeActions[p.id] = "update";
1746
+ changedProducts.push(fresh);
1329
1747
  return fresh;
1330
1748
  }
1331
1749
  return p;
@@ -1333,17 +1751,38 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1333
1751
  const newCount = freshMap.size;
1334
1752
  for (const p of freshMap.values()) {
1335
1753
  updatedList.push(p);
1754
+ mergeActions[p.id] = "insert";
1755
+ changedProducts.push(p);
1336
1756
  }
1337
1757
  const updatedCount = freshProducts.length - newCount;
1758
+ const changedProductIds = freshProducts.map((p) => p.id);
1759
+ this.pushProductFlowLog("mergeProductsToStore.start", {
1760
+ changedProductIds,
1761
+ incomingCount: freshProducts.length,
1762
+ skipIndexDB: !!(options == null ? void 0 : options.skipIndexDB),
1763
+ ...this.getProductStoreSnapshot()
1764
+ });
1338
1765
  this.store.list = updatedList;
1339
1766
  this.syncProductsMap();
1340
- await this.saveProductsToIndexDB(this.store.list);
1767
+ if (!(options == null ? void 0 : options.skipIndexDB)) {
1768
+ await this.patchProductsInIndexDB(changedProducts, "pubsub.mergeProductsToStore", mergeActions);
1769
+ }
1770
+ if (!(options == null ? void 0 : options.skipEmit)) {
1771
+ this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1772
+ }
1773
+ this.pushProductFlowLog("mergeProductsToStore.done", {
1774
+ changedProductIds,
1775
+ updatedCount,
1776
+ newCount,
1777
+ totalCount: this.store.list.length,
1778
+ indexDbPatchCount: (options == null ? void 0 : options.skipIndexDB) ? 0 : changedProducts.length
1779
+ });
1341
1780
  this.logInfo("mergeProductsToStore: 合并完成", {
1342
1781
  updatedCount,
1343
1782
  newCount,
1344
1783
  totalCount: this.store.list.length
1345
1784
  });
1346
- this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1785
+ return { changedProducts, mergeActions, updatedCount, newCount };
1347
1786
  }
1348
1787
  /**
1349
1788
  * 静默全量刷新:后台重新拉取全量 SSE 数据并更新本地
@@ -1352,6 +1791,22 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1352
1791
  */
1353
1792
  async silentRefresh() {
1354
1793
  const t0 = performance.now();
1794
+ const now = Date.now();
1795
+ const elapsedSincePreload = now - this.lastPreloadCompletedAtMs;
1796
+ const elapsedSinceFullFetch = now - this.lastServerFullFetchAtMs;
1797
+ if (this.lastPreloadCompletedAtMs > 0 && elapsedSincePreload < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS || this.lastServerFullFetchAtMs > 0 && elapsedSinceFullFetch < PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS) {
1798
+ this.pushProductFlowLog("silentRefresh.skipped", {
1799
+ elapsedSincePreloadMs: this.lastPreloadCompletedAtMs > 0 ? elapsedSincePreload : null,
1800
+ elapsedSinceFullFetchMs: this.lastServerFullFetchAtMs > 0 ? elapsedSinceFullFetch : null,
1801
+ minIntervalMs: PRODUCT_SILENT_REFRESH_MIN_INTERVAL_MS,
1802
+ ...this.getProductStoreSnapshot()
1803
+ });
1804
+ return this.store.list;
1805
+ }
1806
+ this.pushProductFlowLog("silentRefresh.start", {
1807
+ note: "会调用 loadProductsByServer → replaceProductsSnapshotInIndexDB",
1808
+ ...this.getProductStoreSnapshot()
1809
+ });
1355
1810
  this.logInfo("silentRefresh 开始");
1356
1811
  try {
1357
1812
  const products = await this.loadProductsByServer();
@@ -1361,17 +1816,24 @@ var ProductsModule = class extends import_BaseModule.BaseModule {
1361
1816
  this.clearPriceCache();
1362
1817
  this.core.effects.emit(import_types.ProductsHooks.onProductsChanged, this.store.list);
1363
1818
  await this.core.effects.emit(import_types.ProductsHooks.onProductsSyncCompleted, null);
1819
+ this.pushProductFlowLog("silentRefresh.done", {
1820
+ productCount: products.length,
1821
+ durationMs: Math.round(performance.now() - t0),
1822
+ ...this.getProductStoreSnapshot()
1823
+ });
1364
1824
  this.logInfo("silentRefresh 完成", {
1365
1825
  productCount: products.length,
1366
1826
  duration: `${Math.round(performance.now() - t0)}ms`
1367
1827
  });
1368
1828
  } else {
1829
+ this.pushProductFlowLog("silentRefresh.empty", {});
1369
1830
  this.logWarning("silentRefresh: 服务器未返回数据");
1370
1831
  }
1371
1832
  (0, import_product.perfMark)("silentRefresh", performance.now() - t0, { count: this.store.list.length });
1372
1833
  return this.store.list;
1373
1834
  } catch (error) {
1374
1835
  const errorMessage = error instanceof Error ? error.message : String(error);
1836
+ this.pushProductFlowLog("silentRefresh.failed", { error: errorMessage });
1375
1837
  this.logError("silentRefresh 失败", {
1376
1838
  duration: `${Math.round(performance.now() - t0)}ms`,
1377
1839
  error: errorMessage