@pisell/pisellos 2.2.190 → 2.2.192

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 +60 -7
  13. package/dist/server/modules/order/index.js +670 -178
  14. package/dist/server/modules/order/types.d.ts +0 -2
  15. package/dist/server/modules/products/index.d.ts +33 -6
  16. package/dist/server/modules/products/index.js +528 -231
  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 +60 -7
  46. package/lib/server/modules/order/index.js +350 -40
  47. package/lib/server/modules/order/types.d.ts +0 -2
  48. package/lib/server/modules/products/index.d.ts +33 -6
  49. package/lib/server/modules/products/index.js +159 -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
@@ -40,6 +40,12 @@ var INDEXDB_STORE_NAME = "orders";
40
40
  var ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY = "server_order_last_full_fetch_at";
41
41
  var ORDER_SYNC_THROTTLE_MS_STORAGE_KEY = "order_sync_throttle_ms";
42
42
  var DEFAULT_ORDER_SYNC_THROTTLE_MS = 2e3;
43
+ var ORDER_SQLITE_DEDUPE_MS = 15e3;
44
+ var ORDER_SILENT_REFRESH_MIN_INTERVAL_MS = 3e4;
45
+ var ORDER_BUSINESS_WRITE_SOURCES = /* @__PURE__ */ new Set([
46
+ "upsertOrdersFromRemote",
47
+ "overwriteExistingOrder"
48
+ ]);
43
49
  var OrderModule = class extends import_BaseModule.BaseModule {
44
50
  constructor(name, version) {
45
51
  super(name, version);
@@ -51,6 +57,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
51
57
  // Map<resource_id, OrderId[]> 资源到订单的倒排索引
52
58
  this.resourceIdIndex = /* @__PURE__ */ new Map();
53
59
  this.orderSQLiteSaveQueue = Promise.resolve();
60
+ /** 按 storageKey 记录最近一次 SQLite 写入来源与时间,用于去重 */
61
+ this.recentSqliteWriteByStorageKey = /* @__PURE__ */ new Map();
62
+ /** 最近一次 SSE 全量拉取完成时间 */
63
+ this.lastServerFullFetchAtMs = 0;
54
64
  }
55
65
  async initialize(core, options) {
56
66
  var _a, _b;
@@ -155,6 +165,159 @@ var OrderModule = class extends import_BaseModule.BaseModule {
155
165
  } catch {
156
166
  }
157
167
  }
168
+ /**
169
+ * 记录订单最近一次 SQLite 写入,供 pubsub 回声去重使用。
170
+ *
171
+ * @example
172
+ * this.recordRecentSqliteWrite('upsertOrdersFromRemote', orders)
173
+ */
174
+ recordRecentSqliteWrite(source, orders) {
175
+ const now = Date.now();
176
+ for (const order of orders) {
177
+ const storageKey = this.getOrderStorageKey(order);
178
+ if (!storageKey)
179
+ continue;
180
+ this.recentSqliteWriteByStorageKey.set(storageKey, { source, ts: now });
181
+ }
182
+ }
183
+ /**
184
+ * 过滤 pubsub 触发的冗余 SQLite patch(业务 API 刚写入过的 update 回声)。
185
+ *
186
+ * @example
187
+ * const { toWrite, skipped } = this.filterRedundantPatchOrders('pubsub.bodyUpsert', orders, mergeActions)
188
+ */
189
+ filterRedundantPatchOrders(source, orders, mergeActions) {
190
+ const eligibleSkipSources = /* @__PURE__ */ new Set(["pubsub.bodyUpsert", "pubsub.httpRefresh"]);
191
+ if (!eligibleSkipSources.has(source)) {
192
+ return { toWrite: orders, skipped: [] };
193
+ }
194
+ const now = Date.now();
195
+ const toWrite = [];
196
+ const skipped = [];
197
+ for (const order of orders) {
198
+ const storageKey = this.getOrderStorageKey(order);
199
+ if (!storageKey) {
200
+ toWrite.push(order);
201
+ continue;
202
+ }
203
+ const orderKey = order.order_id !== void 0 && order.order_id !== null ? this.getIdKey(order.order_id) : "";
204
+ const mergeAction = orderKey ? mergeActions[orderKey] : void 0;
205
+ if (mergeAction === "insert") {
206
+ toWrite.push(order);
207
+ continue;
208
+ }
209
+ const recent = this.recentSqliteWriteByStorageKey.get(storageKey);
210
+ if (recent && ORDER_BUSINESS_WRITE_SOURCES.has(recent.source) && now - recent.ts < ORDER_SQLITE_DEDUPE_MS) {
211
+ skipped.push({
212
+ orderId: order.order_id ?? null,
213
+ storageKey,
214
+ reason: "skip_pubsub_echo_after_business_write",
215
+ lastSource: recent.source,
216
+ elapsedMs: now - recent.ts
217
+ });
218
+ continue;
219
+ }
220
+ toWrite.push(order);
221
+ }
222
+ return { toWrite, skipped };
223
+ }
224
+ /**
225
+ * 过滤 pubsub HTTP 增量拉取 ids:本地刚由业务 API 写入的单可跳过 HTTP。
226
+ *
227
+ * @example
228
+ * const ids = this.filterRedundantHttpRefreshIds([99331, 99332])
229
+ */
230
+ filterRedundantHttpRefreshIds(ids) {
231
+ const now = Date.now();
232
+ const toFetch = [];
233
+ const skipped = [];
234
+ for (const id of ids) {
235
+ const existing = this.getOrderByOrderId(id);
236
+ if (!existing) {
237
+ toFetch.push(id);
238
+ continue;
239
+ }
240
+ const storageKey = this.getOrderStorageKey(existing);
241
+ const recent = storageKey ? this.recentSqliteWriteByStorageKey.get(storageKey) : void 0;
242
+ if (recent && recent.source === "upsertOrdersFromRemote" && now - recent.ts < ORDER_SQLITE_DEDUPE_MS) {
243
+ skipped.push({
244
+ orderId: id,
245
+ lastSource: recent.source,
246
+ elapsedMs: now - recent.ts
247
+ });
248
+ continue;
249
+ }
250
+ toFetch.push(id);
251
+ }
252
+ return { toFetch, skipped };
253
+ }
254
+ /**
255
+ * 解析 pubsub 消息会走哪条同步分支,便于对照为何触发 HTTP 重拉或 body 直写。
256
+ *
257
+ * @example
258
+ * this.resolveSyncMessageRoute(msg)
259
+ * // => { route: 'httpRefresh.batchIds', ... }
260
+ */
261
+ resolveSyncMessageRoute(msg) {
262
+ var _a;
263
+ const hasBatchIds = Array.isArray(msg.ids) && msg.ids.length > 0;
264
+ const singleIdRaw = msg.id ?? msg.order_id;
265
+ const hasSingleId = !hasBatchIds && singleIdRaw !== void 0 && singleIdRaw !== null;
266
+ const normalizedBody = this.normalizeOrderSyncPayload(msg.body || msg.data);
267
+ if (hasBatchIds) {
268
+ return {
269
+ route: "httpRefresh.batchIds",
270
+ hasBatchIds: true,
271
+ hasSingleId: false,
272
+ hasNormalizedBody: !!normalizedBody,
273
+ singleId: null,
274
+ batchIds: msg.ids || [],
275
+ bodyOrderId: (normalizedBody == null ? void 0 : normalizedBody.order_id) ?? null
276
+ };
277
+ }
278
+ if (hasSingleId && normalizedBody) {
279
+ return {
280
+ route: "bodyUpsert",
281
+ hasBatchIds: false,
282
+ hasSingleId: true,
283
+ hasNormalizedBody: true,
284
+ singleId: singleIdRaw,
285
+ batchIds: [],
286
+ bodyOrderId: normalizedBody.order_id ?? null
287
+ };
288
+ }
289
+ if (hasSingleId) {
290
+ return {
291
+ route: "httpRefresh.singleId",
292
+ hasBatchIds: false,
293
+ hasSingleId: true,
294
+ hasNormalizedBody: !!normalizedBody,
295
+ singleId: singleIdRaw,
296
+ batchIds: [],
297
+ bodyOrderId: (normalizedBody == null ? void 0 : normalizedBody.order_id) ?? null
298
+ };
299
+ }
300
+ if ((_a = msg.relation_order_ids) == null ? void 0 : _a.length) {
301
+ return {
302
+ route: "httpRefresh.relationIds",
303
+ hasBatchIds: false,
304
+ hasSingleId: false,
305
+ hasNormalizedBody: !!normalizedBody,
306
+ singleId: null,
307
+ batchIds: msg.relation_order_ids,
308
+ bodyOrderId: (normalizedBody == null ? void 0 : normalizedBody.order_id) ?? null
309
+ };
310
+ }
311
+ return {
312
+ route: "noop",
313
+ hasBatchIds: false,
314
+ hasSingleId: false,
315
+ hasNormalizedBody: !!normalizedBody,
316
+ singleId: null,
317
+ batchIds: [],
318
+ bodyOrderId: (normalizedBody == null ? void 0 : normalizedBody.order_id) ?? null
319
+ };
320
+ }
158
321
  getOrderSyncThrottleMs() {
159
322
  const raw = localStorage.getItem(ORDER_SYNC_THROTTLE_MS_STORAGE_KEY);
160
323
  if (raw !== null) {
@@ -167,8 +330,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
167
330
  async preload() {
168
331
  const getData = async () => {
169
332
  const orders = await this.loadOrdersByServer();
170
- if (orders.length === 0)
333
+ if (orders.length === 0) {
171
334
  return;
335
+ }
172
336
  this.store.list = (0, import_lodash_es.cloneDeep)(orders);
173
337
  this.syncOrdersMap();
174
338
  this.core.effects.emit(import_types.OrderHooks.onOrdersChanged, this.store.list);
@@ -189,8 +353,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
189
353
  async getLocalOrderByOrderId(orderId) {
190
354
  const key = this.getIdKey(orderId);
191
355
  const memoryOrder = this.store.map.get(key);
192
- if (memoryOrder)
356
+ if (memoryOrder) {
193
357
  return memoryOrder;
358
+ }
194
359
  const orders = await this.loadOrdersFromSQLite();
195
360
  const localOrder = orders.find((order) => {
196
361
  const shopOrderNumber = order == null ? void 0 : order.shop_order_number;
@@ -226,12 +391,13 @@ var OrderModule = class extends import_BaseModule.BaseModule {
226
391
  count: orderList.length
227
392
  });
228
393
  this.markOrderPulledAtNow();
394
+ this.lastServerFullFetchAtMs = Date.now();
229
395
  } catch {
230
396
  orderList = [];
231
397
  this.logInfo("loadOrdersByServer-SSE拉取失败,回退为空数组");
232
398
  }
233
399
  }
234
- await this.saveOrdersToSQLite(orderList);
400
+ await this.replaceOrdersSnapshotInSQLite(orderList, "loadOrdersByServer");
235
401
  this.logInfo("loadOrdersByServer-结束", {
236
402
  count: orderList.length
237
403
  });
@@ -245,6 +411,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
245
411
  */
246
412
  async silentRefresh() {
247
413
  const startTime = Date.now();
414
+ const elapsedSinceFullFetch = Date.now() - this.lastServerFullFetchAtMs;
415
+ if (this.lastServerFullFetchAtMs > 0 && elapsedSinceFullFetch < ORDER_SILENT_REFRESH_MIN_INTERVAL_MS) {
416
+ return this.store.list;
417
+ }
248
418
  this.logInfo("silentRefresh 开始");
249
419
  try {
250
420
  const orders = await this.loadOrdersByServer();
@@ -300,7 +470,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
300
470
  return this.getIdKey(id) === key ? fresh : order;
301
471
  });
302
472
  this.syncOrdersMap();
303
- await this.saveOrdersToSQLite(this.store.list);
473
+ await this.updateOrderInSQLite(fresh, "overwriteExistingOrder");
304
474
  this.logInfo("overwriteExistingOrder-结束", {
305
475
  orderId,
306
476
  storeOrderCountAfter: this.store.list.length
@@ -318,7 +488,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
318
488
  return;
319
489
  }
320
490
  this.logInfo("upsertOrdersFromRemote-开始", { count: freshOrders.length });
321
- await this.mergeOrdersToStore(freshOrders);
491
+ await this.mergeOrdersToStore(freshOrders, "upsertOrdersFromRemote");
322
492
  }
323
493
  /**
324
494
  * 通过 SSE 按自定义 query 拉取订单(支持 select/with 精简字段)
@@ -426,7 +596,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
426
596
  this.orderDataSource.run({
427
597
  pubsub: {
428
598
  callback: (res) => {
429
- console.log("orderDataSource");
599
+ console.log("orderDataSource", res);
430
600
  const pubsubReceivedAt = Date.now();
431
601
  const message = this.normalizeOrderSyncMessage(res);
432
602
  if (!message)
@@ -434,6 +604,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
434
604
  message._pubsubReceivedAt = pubsubReceivedAt;
435
605
  this.pendingSyncMessages.push(message);
436
606
  const throttleMs = this.getOrderSyncThrottleMs();
607
+ const routeInfo = this.resolveSyncMessageRoute(message);
437
608
  this.logInfo("orderSync-收到消息并入队", {
438
609
  id: message.id ?? null,
439
610
  order_id: message.order_id ?? null,
@@ -506,7 +677,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
506
677
  * await this.processOrderSyncMessages()
507
678
  */
508
679
  async processOrderSyncMessages() {
509
- var _a;
510
680
  const messages = [...this.pendingSyncMessages];
511
681
  this.pendingSyncMessages = [];
512
682
  if (messages.length === 0)
@@ -523,38 +693,47 @@ var OrderModule = class extends import_BaseModule.BaseModule {
523
693
  });
524
694
  const upsertOrders = /* @__PURE__ */ new Map();
525
695
  const refreshIds = [];
526
- for (const msg of messages) {
527
- const hasBatchIds = Array.isArray(msg.ids) && msg.ids.length > 0;
528
- const singleId = msg.id ?? msg.order_id;
529
- const hasSingleId = !hasBatchIds && singleId !== void 0 && singleId !== null;
530
- const rawBody = msg.body || msg.data;
531
- const normalizedBody = this.normalizeOrderSyncPayload(rawBody);
532
- if (hasSingleId && normalizedBody) {
696
+ messages.forEach((msg, msgIndex) => {
697
+ const routeInfo = this.resolveSyncMessageRoute(msg);
698
+ const hasBatchIds = routeInfo.hasBatchIds;
699
+ const singleId = routeInfo.singleId;
700
+ const hasSingleId = routeInfo.hasSingleId;
701
+ const normalizedBody = this.normalizeOrderSyncPayload(msg.body || msg.data);
702
+ if (routeInfo.route === "bodyUpsert" && normalizedBody) {
533
703
  upsertOrders.set(this.getIdKey(normalizedBody.order_id), normalizedBody);
534
- continue;
704
+ return;
535
705
  }
536
706
  if (hasBatchIds)
537
- refreshIds.push(...msg.ids);
538
- else if (hasSingleId)
707
+ refreshIds.push(...routeInfo.batchIds);
708
+ else if (routeInfo.route === "httpRefresh.singleId" && singleId !== null)
539
709
  refreshIds.push(singleId);
540
- else if ((_a = msg.relation_order_ids) == null ? void 0 : _a.length)
541
- refreshIds.push(...msg.relation_order_ids);
542
- }
710
+ else if (routeInfo.route === "httpRefresh.relationIds")
711
+ refreshIds.push(...routeInfo.batchIds);
712
+ });
543
713
  const uniqueRefreshIds = this.uniqueOrderIds(refreshIds);
544
714
  const upsertList = [...upsertOrders.values()];
545
715
  this.logInfo("processOrderSyncMessages-去重完成", {
546
716
  upsertCount: upsertList.length,
547
717
  refreshIdCount: uniqueRefreshIds.length
548
718
  });
549
- if (upsertList.length > 0)
550
- await this.mergeOrdersToStore(upsertList);
719
+ if (upsertList.length > 0) {
720
+ await this.mergeOrdersToStore(upsertList, "pubsub.bodyUpsert");
721
+ }
551
722
  if (uniqueRefreshIds.length > 0) {
552
- const freshOrders = await this.fetchOrdersByHttp(uniqueRefreshIds);
553
- if (freshOrders.length > 0)
554
- await this.mergeOrdersToStore(freshOrders);
723
+ const { toFetch, skipped } = this.filterRedundantHttpRefreshIds(uniqueRefreshIds);
724
+ if (skipped.length > 0) {
725
+ }
726
+ if (toFetch.length > 0) {
727
+ const freshOrders = await this.fetchOrdersByHttp(toFetch);
728
+ if (freshOrders.length > 0) {
729
+ await this.mergeOrdersToStore(freshOrders, "pubsub.httpRefresh");
730
+ } else {
731
+ }
732
+ }
555
733
  }
556
- if (upsertList.length === 0 && uniqueRefreshIds.length === 0)
734
+ if (upsertList.length === 0 && uniqueRefreshIds.length === 0) {
557
735
  return;
736
+ }
558
737
  const batchProcessEndAt = Date.now();
559
738
  this.logInfo("processOrderSyncMessages-结束", {
560
739
  upsertCount: upsertList.length,
@@ -595,7 +774,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
595
774
  /**
596
775
  * 将增量订单合并到 store
597
776
  */
598
- async mergeOrdersToStore(freshOrders) {
777
+ async mergeOrdersToStore(freshOrders, source) {
599
778
  this.logInfo("mergeOrdersToStore-开始", {
600
779
  freshOrderCount: freshOrders.length,
601
780
  storeOrderCountBefore: this.store.list.length
@@ -608,6 +787,8 @@ var OrderModule = class extends import_BaseModule.BaseModule {
608
787
  freshMap.set(this.getIdKey(id), order);
609
788
  }
610
789
  const uniqueFreshCount = freshMap.size;
790
+ const patchedOrders = [...freshMap.values()];
791
+ const mergeActions = {};
611
792
  const updatedList = this.store.list.map((order) => {
612
793
  const id = order.order_id;
613
794
  if (id === void 0 || id === null)
@@ -617,14 +798,21 @@ var OrderModule = class extends import_BaseModule.BaseModule {
617
798
  return order;
618
799
  const fresh = freshMap.get(key);
619
800
  freshMap.delete(key);
801
+ mergeActions[key] = "update";
620
802
  return fresh;
621
803
  });
622
804
  for (const order of freshMap.values()) {
805
+ const id = order.order_id;
806
+ if (id !== void 0 && id !== null) {
807
+ mergeActions[this.getIdKey(id)] = "insert";
808
+ }
623
809
  updatedList.push(order);
624
810
  }
811
+ const insertCount = Object.values(mergeActions).filter((v) => v === "insert").length;
812
+ const updateCount = Object.values(mergeActions).filter((v) => v === "update").length;
625
813
  this.store.list = updatedList;
626
814
  this.syncOrdersMap();
627
- await this.saveOrdersToSQLite(this.store.list);
815
+ await this.patchOrdersInSQLite(patchedOrders, source, mergeActions);
628
816
  this.logInfo("mergeOrdersToStore-结束", {
629
817
  uniqueFreshCount,
630
818
  storeOrderCountAfter: this.store.list.length
@@ -674,6 +862,24 @@ var OrderModule = class extends import_BaseModule.BaseModule {
674
862
  getIdKey(id) {
675
863
  return String(id);
676
864
  }
865
+ /**
866
+ * 解析订单 SQLite 主键,规则与宿主 OrderDataSource.getOrderStorageKey 一致。
867
+ *
868
+ * @example
869
+ * const key = this.getOrderStorageKey({ order_id: 1001, external_sale_number: 'S-1001' })
870
+ * // => 'S-1001'
871
+ */
872
+ getOrderStorageKey(order) {
873
+ const externalSaleNumber = order == null ? void 0 : order.external_sale_number;
874
+ if (externalSaleNumber !== void 0 && externalSaleNumber !== null && externalSaleNumber !== "") {
875
+ return String(externalSaleNumber);
876
+ }
877
+ const orderId = order == null ? void 0 : order.order_id;
878
+ if (orderId !== void 0 && orderId !== null && orderId !== "") {
879
+ return String(orderId);
880
+ }
881
+ return "";
882
+ }
677
883
  /**
678
884
  * 当前订单拉取窗口:
679
885
  * - 优先使用 createdAtQuery.sales_time_between(已按营业日边界初始化)
@@ -745,12 +951,11 @@ var OrderModule = class extends import_BaseModule.BaseModule {
745
951
  }
746
952
  }
747
953
  /**
748
- * 串行执行订单 SQLite 保存任务,保证 clear + bulkAdd 组合不会被其它订单保存插入。
954
+ * 串行执行订单 SQLite 写入任务,避免快照替换与增量 upsert 并发交错。
749
955
  *
750
956
  * @example
751
957
  * await this.runInOrderSQLiteSaveQueue(async () => {
752
- * await this.dbManager.clear(INDEXDB_STORE_NAME)
753
- * await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, orders)
958
+ * await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, orders)
754
959
  * })
755
960
  */
756
961
  async runInOrderSQLiteSaveQueue(task) {
@@ -759,33 +964,138 @@ var OrderModule = class extends import_BaseModule.BaseModule {
759
964
  return queuedTask;
760
965
  }
761
966
  /**
762
- * 保存订单列表到 SQLite,内部会先清空 orders 表再批量写入当前快照。
967
+ * 批量增量 upsert 订单到 SQLite,不清空表;用于 pubsub / HTTP 增量合并。
763
968
  *
764
969
  * @example
765
- * await this.saveOrdersToSQLite(this.store.list)
970
+ * await this.patchOrdersInSQLite(freshOrders)
766
971
  */
767
- async saveOrdersToSQLite(orderList) {
768
- if (!this.dbManager)
972
+ async patchOrdersInSQLite(orders, source, mergeActions = {}) {
973
+ if (!this.dbManager || orders.length === 0) {
974
+ return;
975
+ }
976
+ const ordersSnapshot = (0, import_lodash_es.cloneDeep)(orders).filter((order) => this.getOrderStorageKey(order));
977
+ if (ordersSnapshot.length === 0) {
978
+ return;
979
+ }
980
+ const { toWrite, skipped } = this.filterRedundantPatchOrders(
981
+ source,
982
+ ordersSnapshot,
983
+ mergeActions
984
+ );
985
+ if (toWrite.length === 0) {
769
986
  return;
987
+ }
988
+ try {
989
+ await this.runInOrderSQLiteSaveQueue(async () => {
990
+ let writeMethod = "none";
991
+ this.logInfo("patchOrdersInSQLite-开始", {
992
+ source,
993
+ count: toWrite.length,
994
+ dedupedCount: skipped.length
995
+ });
996
+ if (typeof this.dbManager.bulkUpdate === "function") {
997
+ writeMethod = "bulkUpdate";
998
+ await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, toWrite);
999
+ } else if (typeof this.dbManager.bulkAdd === "function") {
1000
+ writeMethod = "bulkAdd";
1001
+ await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, toWrite);
1002
+ } else if (typeof this.dbManager.update === "function") {
1003
+ writeMethod = "update";
1004
+ for (const order of toWrite) {
1005
+ await this.dbManager.update(INDEXDB_STORE_NAME, order);
1006
+ }
1007
+ }
1008
+ this.recordRecentSqliteWrite(source, toWrite);
1009
+ this.logInfo("patchOrdersInSQLite-完成", {
1010
+ source,
1011
+ count: toWrite.length,
1012
+ writeMethod,
1013
+ dedupedCount: skipped.length
1014
+ });
1015
+ });
1016
+ } catch (error) {
1017
+ this.logError("增量保存订单到 SQLite 失败", {
1018
+ error: error instanceof Error ? error.message : String(error),
1019
+ orderCount: ordersSnapshot.length
1020
+ });
1021
+ }
1022
+ }
1023
+ /**
1024
+ * 单条 upsert 订单到 SQLite;用于 overwriteExistingOrder 等单条覆盖场景。
1025
+ *
1026
+ * @example
1027
+ * await this.updateOrderInSQLite(freshOrder)
1028
+ */
1029
+ async updateOrderInSQLite(order, source) {
1030
+ if (!this.dbManager || !this.getOrderStorageKey(order)) {
1031
+ return;
1032
+ }
1033
+ const orderSnapshot = (0, import_lodash_es.cloneDeep)(order);
1034
+ try {
1035
+ await this.runInOrderSQLiteSaveQueue(async () => {
1036
+ let writeMethod = "none";
1037
+ this.logInfo("updateOrderInSQLite-开始", {
1038
+ source,
1039
+ orderId: orderSnapshot.order_id ?? null,
1040
+ storageKey: this.getOrderStorageKey(orderSnapshot)
1041
+ });
1042
+ if (typeof this.dbManager.update === "function") {
1043
+ writeMethod = "update";
1044
+ await this.dbManager.update(INDEXDB_STORE_NAME, orderSnapshot);
1045
+ } else if (typeof this.dbManager.bulkUpdate === "function") {
1046
+ writeMethod = "bulkUpdate";
1047
+ await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, [orderSnapshot]);
1048
+ } else if (typeof this.dbManager.bulkAdd === "function") {
1049
+ writeMethod = "bulkAdd";
1050
+ await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, [orderSnapshot]);
1051
+ }
1052
+ this.recordRecentSqliteWrite(source, [orderSnapshot]);
1053
+ this.logInfo("updateOrderInSQLite-完成", {
1054
+ source,
1055
+ orderId: orderSnapshot.order_id ?? null,
1056
+ writeMethod
1057
+ });
1058
+ });
1059
+ } catch (error) {
1060
+ this.logError("单条保存订单到 SQLite 失败", {
1061
+ error: error instanceof Error ? error.message : String(error),
1062
+ orderId: orderSnapshot.order_id ?? null
1063
+ });
1064
+ }
1065
+ }
1066
+ /**
1067
+ * 全量快照替换 orders 表(clear + bulkAdd),用于 SSE 全量拉取与营业日窗口裁剪。
1068
+ *
1069
+ * @example
1070
+ * await this.replaceOrdersSnapshotInSQLite(orderList)
1071
+ */
1072
+ async replaceOrdersSnapshotInSQLite(orderList, source) {
1073
+ if (!this.dbManager) {
1074
+ return;
1075
+ }
770
1076
  const orderListSnapshot = (0, import_lodash_es.cloneDeep)(orderList);
771
1077
  try {
772
1078
  await this.runInOrderSQLiteSaveQueue(async () => {
773
- this.logInfo("saveOrdersToSQLite-开始", {
1079
+ this.logInfo("replaceOrdersSnapshotInSQLite-开始", {
1080
+ source,
774
1081
  count: orderListSnapshot.length
775
1082
  });
776
1083
  await this.dbManager.clear(INDEXDB_STORE_NAME);
777
- this.logInfo("saveOrdersToSQLite-clear完成", {
1084
+ this.logInfo("replaceOrdersSnapshotInSQLite-clear完成", {
778
1085
  count: orderListSnapshot.length
779
1086
  });
780
- if (orderListSnapshot.length === 0)
1087
+ if (orderListSnapshot.length === 0) {
781
1088
  return;
1089
+ }
782
1090
  await this.dbManager.bulkAdd(INDEXDB_STORE_NAME, orderListSnapshot);
783
- this.logInfo("saveOrdersToSQLite-bulkAdd完成", {
1091
+ this.recordRecentSqliteWrite(source, orderListSnapshot);
1092
+ this.logInfo("replaceOrdersSnapshotInSQLite-bulkAdd完成", {
1093
+ source,
784
1094
  count: orderListSnapshot.length
785
1095
  });
786
1096
  });
787
1097
  } catch (error) {
788
- this.logError("保存订单到 SQLite 失败", {
1098
+ this.logError("全量保存订单到 SQLite 失败", {
789
1099
  error: error instanceof Error ? error.message : String(error),
790
1100
  orderList: orderListSnapshot.length
791
1101
  });
@@ -94,8 +94,6 @@ export interface OrderProductLineItem {
94
94
  num: number;
95
95
  /** 多规格 ID。来源:Detail.product_variant_id */
96
96
  product_variant_id?: number;
97
- /** 选项配置 */
98
- product_option_item?: unknown[];
99
97
  /**
100
98
  * 销售单价(交易快照,仅输出)。
101
99
  * 来源:Detail.selling_price
@@ -27,6 +27,12 @@ export declare class ProductsModule extends BaseModule implements Module {
27
27
  private productDataSource;
28
28
  private pendingSyncMessages;
29
29
  private syncTimer?;
30
+ /** 最近一次 SSE 全量拉取完成时间 */
31
+ private lastServerFullFetchAtMs;
32
+ /** 最近一次 preload 完成时间(含 IndexDB 命中) */
33
+ private lastPreloadCompletedAtMs;
34
+ /** IndexDB 写入串行队列,避免快照与 patch 并发交错 */
35
+ private productIndexDBSaveQueue;
30
36
  constructor(name?: string, version?: string);
31
37
  initialize(core: PisellCore, options: any): Promise<void>;
32
38
  /**
@@ -209,10 +215,28 @@ export declare class ProductsModule extends BaseModule implements Module {
209
215
  */
210
216
  private loadProductsFromIndexDB;
211
217
  /**
212
- * 保存商品数据到 IndexDB(平铺存储)
213
- * @private
218
+ * 串行执行商品 IndexDB 写入,避免快照替换与增量 patch 并发交错。
219
+ *
220
+ * @example
221
+ * await this.runInProductIndexDBSaveQueue(async () => {
222
+ * await this.dbManager.bulkUpdate(INDEXDB_STORE_NAME, products)
223
+ * })
224
+ */
225
+ private runInProductIndexDBSaveQueue;
226
+ /**
227
+ * 全量快照写入 IndexDB:clear + bulkUpdate,仅用于 SSE/HTTP 全量拉取。
228
+ *
229
+ * @example
230
+ * await this.replaceProductsSnapshotInIndexDB(list, 'loadProductsByServer')
214
231
  */
215
- private saveProductsToIndexDB;
232
+ private replaceProductsSnapshotInIndexDB;
233
+ /**
234
+ * 增量 upsert 商品到 IndexDB,不清空表;用于 pubsub 批次合并后的变更商品。
235
+ *
236
+ * @example
237
+ * await this.patchProductsInIndexDB(changedProducts, 'pubsub.processProductSyncMessages', mergeActions)
238
+ */
239
+ private patchProductsInIndexDB;
216
240
  /**
217
241
  * 同步更新商品 Map 缓存
218
242
  * 将 list 中的商品同步到 map,以 id 为 key
@@ -262,14 +286,17 @@ export declare class ProductsModule extends BaseModule implements Module {
262
286
  /**
263
287
  * 将 body 完整数据直接覆盖到本地 store(不调用报价单接口)
264
288
  * 已存在的 → 直接替换;不存在的 → 追加
265
- * 同时更新 Map 缓存、IndexDB,触发 onProductsChanged
266
- * 价格缓存由 processProductSyncMessages 末尾统一清除
289
+ *
290
+ * @example
291
+ * await this.applyBodyUpdatesToStore(bodyUpdates, { skipIndexDB: true })
267
292
  */
268
293
  private applyBodyUpdatesToStore;
269
294
  /**
270
295
  * 将增量拉取的商品合并到 store
271
296
  * 已存在的 → 替换;新的 → 追加
272
- * 同时更新 store.map、IndexDB,触发 onProductsChanged
297
+ *
298
+ * @example
299
+ * await this.mergeProductsToStore(freshProducts, { skipIndexDB: true })
273
300
  */
274
301
  private mergeProductsToStore;
275
302
  /**