@pisell/pisellos 2.2.260 → 2.2.262

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 (107) hide show
  1. package/dist/model/strategy/adapter/dataVariant/adapter.d.ts +1 -0
  2. package/dist/model/strategy/adapter/dataVariant/adapter.js +15 -0
  3. package/dist/model/strategy/adapter/dataVariant/evaluator.d.ts +2 -0
  4. package/dist/model/strategy/adapter/dataVariant/evaluator.js +25 -0
  5. package/dist/model/strategy/adapter/dataVariant/examples.js +1 -6
  6. package/dist/model/strategy/adapter/dataVariant/type.d.ts +2 -0
  7. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  8. package/dist/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +23 -13
  9. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +2 -1
  10. package/dist/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +1 -1
  11. package/dist/modules/BookingContext/utils/cacheItemToBookingInput.js +32 -1
  12. package/dist/modules/Discount/index.d.ts +5 -2
  13. package/dist/modules/Discount/index.js +30 -7
  14. package/dist/modules/Discount/types.d.ts +4 -0
  15. package/dist/modules/Order/index.d.ts +45 -2
  16. package/dist/modules/Order/index.js +1552 -926
  17. package/dist/modules/Order/types.d.ts +36 -2
  18. package/dist/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
  19. package/dist/modules/Order/utils/bundleDiscountHydration.js +144 -0
  20. package/dist/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
  21. package/dist/modules/Order/utils/orderCollectionIdentity.js +405 -0
  22. package/dist/modules/Order/utils.d.ts +8 -3
  23. package/dist/modules/Order/utils.js +136 -56
  24. package/dist/modules/Rules/index.d.ts +4 -10
  25. package/dist/modules/Rules/index.js +63 -12
  26. package/dist/modules/Rules/types.d.ts +12 -2
  27. package/dist/modules/SalesSummary/index.d.ts +7 -3
  28. package/dist/modules/SalesSummary/index.js +67 -39
  29. package/dist/modules/SalesSummary/types.d.ts +1 -0
  30. package/dist/modules/SalesSummary/utils.js +2 -1
  31. package/dist/modules/SurchargeList/index.d.ts +6 -4
  32. package/dist/modules/SurchargeList/index.js +62 -39
  33. package/dist/modules/SurchargeList/types.d.ts +6 -2
  34. package/dist/server/index.d.ts +8 -0
  35. package/dist/server/index.js +1365 -911
  36. package/dist/server/modules/order/index.d.ts +32 -7
  37. package/dist/server/modules/order/index.js +551 -464
  38. package/dist/server/modules/order/types.d.ts +4 -0
  39. package/dist/server/modules/products/index.d.ts +5 -1
  40. package/dist/server/modules/products/index.js +384 -315
  41. package/dist/server/modules/products/types.d.ts +4 -0
  42. package/dist/solution/BaseSales/index.d.ts +42 -2
  43. package/dist/solution/BaseSales/index.js +1416 -775
  44. package/dist/solution/BaseSales/types.d.ts +7 -1
  45. package/dist/solution/BaseSales/types.js +3 -2
  46. package/dist/solution/BaseSales/utils/cartPromotion.js +19 -7
  47. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -3
  48. package/dist/solution/BaseSales/utils/parseSalesResponse.js +6 -8
  49. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +20 -4
  50. package/dist/solution/BookingByStep/index.d.ts +1 -1
  51. package/dist/solution/BookingTicket/index.d.ts +8 -0
  52. package/dist/solution/BookingTicket/index.js +212 -31
  53. package/dist/solution/ShopDiscount/index.js +15 -14
  54. package/lib/model/strategy/adapter/dataVariant/adapter.d.ts +1 -0
  55. package/lib/model/strategy/adapter/dataVariant/adapter.js +10 -0
  56. package/lib/model/strategy/adapter/dataVariant/evaluator.d.ts +2 -0
  57. package/lib/model/strategy/adapter/dataVariant/evaluator.js +20 -1
  58. package/lib/model/strategy/adapter/dataVariant/examples.js +0 -2
  59. package/lib/model/strategy/adapter/dataVariant/type.d.ts +2 -0
  60. package/lib/model/strategy/adapter/promotion/index.js +0 -49
  61. package/lib/modules/BookingContext/utils/buildCacheItemFromOrderLine.js +16 -1
  62. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +1 -0
  63. package/lib/modules/BookingContext/utils/cacheItemToBookingInput.d.ts +1 -1
  64. package/lib/modules/BookingContext/utils/cacheItemToBookingInput.js +23 -2
  65. package/lib/modules/Discount/index.d.ts +5 -2
  66. package/lib/modules/Discount/index.js +23 -3
  67. package/lib/modules/Discount/types.d.ts +4 -0
  68. package/lib/modules/Order/index.d.ts +45 -2
  69. package/lib/modules/Order/index.js +720 -116
  70. package/lib/modules/Order/types.d.ts +36 -2
  71. package/lib/modules/Order/utils/bundleDiscountHydration.d.ts +5 -0
  72. package/lib/modules/Order/utils/bundleDiscountHydration.js +139 -0
  73. package/lib/modules/Order/utils/orderCollectionIdentity.d.ts +53 -0
  74. package/lib/modules/Order/utils/orderCollectionIdentity.js +415 -0
  75. package/lib/modules/Order/utils.d.ts +8 -3
  76. package/lib/modules/Order/utils.js +104 -18
  77. package/lib/modules/Rules/index.d.ts +4 -10
  78. package/lib/modules/Rules/index.js +61 -9
  79. package/lib/modules/Rules/types.d.ts +12 -2
  80. package/lib/modules/SalesSummary/index.d.ts +7 -3
  81. package/lib/modules/SalesSummary/index.js +28 -11
  82. package/lib/modules/SalesSummary/types.d.ts +1 -0
  83. package/lib/modules/SalesSummary/utils.js +2 -0
  84. package/lib/modules/SurchargeList/index.d.ts +6 -4
  85. package/lib/modules/SurchargeList/index.js +41 -21
  86. package/lib/modules/SurchargeList/types.d.ts +6 -2
  87. package/lib/server/index.d.ts +8 -0
  88. package/lib/server/index.js +336 -11
  89. package/lib/server/modules/order/index.d.ts +32 -7
  90. package/lib/server/modules/order/index.js +298 -189
  91. package/lib/server/modules/order/types.d.ts +4 -0
  92. package/lib/server/modules/products/index.d.ts +5 -1
  93. package/lib/server/modules/products/index.js +21 -0
  94. package/lib/server/modules/products/types.d.ts +4 -0
  95. package/lib/solution/BaseSales/index.d.ts +42 -2
  96. package/lib/solution/BaseSales/index.js +535 -108
  97. package/lib/solution/BaseSales/types.d.ts +7 -1
  98. package/lib/solution/BaseSales/types.js +2 -1
  99. package/lib/solution/BaseSales/utils/cartPromotion.js +13 -1
  100. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -3
  101. package/lib/solution/BaseSales/utils/parseSalesResponse.js +4 -5
  102. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +14 -6
  103. package/lib/solution/BookingByStep/index.d.ts +1 -1
  104. package/lib/solution/BookingTicket/index.d.ts +8 -0
  105. package/lib/solution/BookingTicket/index.js +134 -8
  106. package/lib/solution/ShopDiscount/index.js +2 -1
  107. package/package.json +1 -1
@@ -35,6 +35,7 @@ module.exports = __toCommonJS(order_exports);
35
35
  var import_lodash_es = require("lodash-es");
36
36
  var import_dayjs = __toESM(require("dayjs"));
37
37
  var import_BaseModule = require("../../../modules/BaseModule");
38
+ var import_orderCollectionIdentity = require("../../../modules/Order/utils/orderCollectionIdentity");
38
39
  var import_types = require("./types");
39
40
  var INDEXDB_STORE_NAME = "orders";
40
41
  var ORDER_LAST_FULL_FETCH_AT_STORAGE_KEY = "server_order_last_full_fetch_at";
@@ -48,6 +49,13 @@ var ORDER_BUSINESS_WRITE_SOURCES = /* @__PURE__ */ new Set([
48
49
  "overwriteExistingOrder",
49
50
  "markOrderSyncedByExternalSaleNumber"
50
51
  ]);
52
+ var ORDER_MERGE_SELF_CHECK = false;
53
+ var ORDER_AUTHORITATIVE_COLLECTION_FIELDS = ["products", "bookings", "payments"];
54
+ var ORDER_COLLECTION_KIND_BY_FIELD = {
55
+ products: "product",
56
+ bookings: "booking",
57
+ payments: "payment"
58
+ };
51
59
  var OrderModule = class extends import_BaseModule.BaseModule {
52
60
  constructor(name, version) {
53
61
  super(name, version);
@@ -56,8 +64,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
56
64
  this.pendingSyncMessages = [];
57
65
  this.isProcessingSyncBatch = false;
58
66
  this.isIdlePhase = true;
59
- // Map<resource_id, OrderId[]> 资源到订单的倒排索引
60
- this.resourceIdIndex = /* @__PURE__ */ new Map();
61
67
  this.orderSQLiteSaveQueue = Promise.resolve();
62
68
  /** 按 storageKey 记录最近一次 SQLite 写入来源与时间,用于去重 */
63
69
  this.recentSqliteWriteByStorageKey = /* @__PURE__ */ new Map();
@@ -155,6 +161,96 @@ var OrderModule = class extends import_BaseModule.BaseModule {
155
161
  } catch {
156
162
  }
157
163
  }
164
+ /**
165
+ * 在订单进入内存或 SQLite 前统一压缩子项强身份重复并补齐协议 UID。
166
+ * 仅记录计数与订单标识,不输出商品、预约、支付或客户明细。
167
+ */
168
+ normalizeOrderCollectionsForIngress(order, source) {
169
+ const result = (0, import_orderCollectionIdentity.normalizeOrderCollections)(order);
170
+ const kinds = ["product", "booking", "payment"];
171
+ const hasChanges = kinds.some((kind) => {
172
+ const stats = result.stats[kind];
173
+ return stats.backfilledUidCount > 0 || stats.collapsedDuplicateCount > 0 || stats.uidConflictCount > 0 || stats.anonymousRowCount > 0;
174
+ });
175
+ if (hasChanges) {
176
+ const summarize = (stats) => ({
177
+ backfilled_uid_count: stats.backfilledUidCount,
178
+ collapsed_duplicate_count: stats.collapsedDuplicateCount,
179
+ uid_conflict_count: stats.uidConflictCount,
180
+ anonymous_row_count: stats.anonymousRowCount
181
+ });
182
+ const context = {
183
+ source,
184
+ order_id: (order == null ? void 0 : order.order_id) ?? null,
185
+ external_sale_number: (order == null ? void 0 : order.external_sale_number) ?? null,
186
+ products: summarize(result.stats.product),
187
+ bookings: summarize(result.stats.booking),
188
+ payments: summarize(result.stats.payment)
189
+ };
190
+ const hasUnsafeIdentity = kinds.some((kind) => {
191
+ const stats = result.stats[kind];
192
+ return stats.uidConflictCount > 0 || stats.anonymousRowCount > 0;
193
+ });
194
+ if (hasUnsafeIdentity) {
195
+ this.logWarning("订单子项身份存在冲突或匿名行", context);
196
+ } else {
197
+ this.logInfo("订单子项身份已规范化", context);
198
+ }
199
+ }
200
+ return result.order;
201
+ }
202
+ /**
203
+ * incoming 决定权威成员集合;同一持久化行仍从 existing 补齐 incoming 省略的本地展示字段。
204
+ */
205
+ mergeAuthoritativeOrderCollection(field, existing, incoming, uidRemaps = []) {
206
+ const kind = ORDER_COLLECTION_KIND_BY_FIELD[field];
207
+ const existingItems = Array.isArray(existing) ? existing : [];
208
+ return incoming.map((incomingItem) => {
209
+ const existingItem = existingItems.find(
210
+ (item) => (0, import_orderCollectionIdentity.isSameOrderCollectionItem)(kind, item, incomingItem)
211
+ );
212
+ if (!existingItem)
213
+ return (0, import_lodash_es.cloneDeep)(incomingItem);
214
+ const mergedItem = (0, import_orderCollectionIdentity.mergeOrderCollectionItems)(kind, existingItem, incomingItem);
215
+ const mergedUid = (0, import_orderCollectionIdentity.getOrderCollectionUid)(kind, mergedItem);
216
+ if (mergedUid) {
217
+ for (const from of /* @__PURE__ */ new Set([
218
+ (0, import_orderCollectionIdentity.getOrderCollectionReferenceUid)(kind, existingItem),
219
+ (0, import_orderCollectionIdentity.getOrderCollectionReferenceUid)(kind, incomingItem)
220
+ ])) {
221
+ if (!from || from === mergedUid)
222
+ continue;
223
+ uidRemaps.push({
224
+ kind,
225
+ from,
226
+ to: mergedUid,
227
+ persistentId: (0, import_orderCollectionIdentity.getOrderCollectionPersistentId)(kind, incomingItem) || (0, import_orderCollectionIdentity.getOrderCollectionPersistentId)(kind, existingItem)
228
+ });
229
+ }
230
+ }
231
+ return mergedItem;
232
+ });
233
+ }
234
+ reconcileOverwriteOrderCollections(existing, incoming) {
235
+ const reconciled = (0, import_lodash_es.cloneDeep)(incoming);
236
+ const existingRecord = existing;
237
+ const incomingRecord = incoming;
238
+ const uidRemaps = [];
239
+ for (const field of ORDER_AUTHORITATIVE_COLLECTION_FIELDS) {
240
+ if (!Array.isArray(incomingRecord[field]))
241
+ continue;
242
+ reconciled[field] = this.mergeAuthoritativeOrderCollection(
243
+ field,
244
+ existingRecord[field],
245
+ incomingRecord[field],
246
+ uidRemaps
247
+ );
248
+ }
249
+ return this.normalizeOrderCollectionsForIngress(
250
+ (0, import_orderCollectionIdentity.applyOrderCollectionUidRemaps)(reconciled, uidRemaps),
251
+ "overwriteExistingOrder.reconciled"
252
+ );
253
+ }
158
254
  /**
159
255
  * 记录订单最近一次 SQLite 写入,供 pubsub 回声去重使用。
160
256
  *
@@ -309,12 +405,11 @@ var OrderModule = class extends import_BaseModule.BaseModule {
309
405
  };
310
406
  }
311
407
  updateCreateStore() {
312
- var _a;
313
408
  const appPlugin = this.core.getPlugin("app");
314
409
  const app = appPlugin == null ? void 0 : appPlugin.getApp();
315
410
  if (app) {
316
411
  const coreData = app.data.store.getStore().getDataByModel("core");
317
- if (!((_a = this.store.createdAtQuery) == null ? void 0 : _a.sales_time_between) && (coreData == null ? void 0 : coreData.core)) {
412
+ if (coreData == null ? void 0 : coreData.core) {
318
413
  const today = (0, import_dayjs.default)();
319
414
  if (coreData == null ? void 0 : coreData.core.operating_day_boundary) {
320
415
  const operatingDayBoundary = coreData.core.operating_day_boundary;
@@ -481,20 +576,17 @@ var OrderModule = class extends import_BaseModule.BaseModule {
481
576
  return this.store.list;
482
577
  }
483
578
  }
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
- }
488
- normalizeOrderForMemoryList(order) {
489
- const externalSaleNumber = order == null ? void 0 : order.external_sale_number;
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) !== "") {
491
- return order;
579
+ normalizeOrderForMemoryList(order, source = "normalizeOrderForMemoryList") {
580
+ const normalizedOrder = this.normalizeOrderCollectionsForIngress(order, source);
581
+ const externalSaleNumber = normalizedOrder == null ? void 0 : normalizedOrder.external_sale_number;
582
+ if ((normalizedOrder == null ? void 0 : normalizedOrder.order_id) !== void 0 && (normalizedOrder == null ? void 0 : normalizedOrder.order_id) !== null && (normalizedOrder == null ? void 0 : normalizedOrder.order_id) !== "") {
583
+ return normalizedOrder;
492
584
  }
493
585
  if (externalSaleNumber === void 0 || externalSaleNumber === null || externalSaleNumber === "") {
494
- return order;
586
+ return normalizedOrder;
495
587
  }
496
588
  return {
497
- ...order,
589
+ ...normalizedOrder,
498
590
  order_id: externalSaleNumber
499
591
  };
500
592
  }
@@ -517,19 +609,29 @@ var OrderModule = class extends import_BaseModule.BaseModule {
517
609
  orderId,
518
610
  storeOrderCountBefore: this.store.list.length
519
611
  });
612
+ const normalizedFresh = this.normalizeOrderForMemoryList(
613
+ fresh,
614
+ "overwriteExistingOrder.incoming"
615
+ );
616
+ const existingOrder = this.store.map.get(key);
617
+ const reconciledFresh = this.reconcileOverwriteOrderCollections(
618
+ existingOrder,
619
+ normalizedFresh
620
+ );
520
621
  this.store.list = this.store.list.map((order) => {
521
622
  const id = order == null ? void 0 : order.order_id;
522
- if (id === void 0 || id === null)
523
- return order;
524
- return this.getIdKey(id) === key ? fresh : order;
623
+ if (id === void 0 || id === null) {
624
+ return this.normalizeOrderForMemoryList(order, "overwriteExistingOrder.existingMemory");
625
+ }
626
+ return this.getIdKey(id) === key ? reconciledFresh : this.normalizeOrderForMemoryList(order, "overwriteExistingOrder.existingMemory");
525
627
  });
526
628
  this.syncOrdersMap();
527
- await this.updateOrderInSQLite(fresh, "overwriteExistingOrder");
629
+ await this.updateOrderInSQLite(reconciledFresh, "overwriteExistingOrder");
528
630
  this.logInfo("overwriteExistingOrder-结束", {
529
631
  orderId,
530
632
  storeOrderCountAfter: this.store.list.length
531
633
  });
532
- await this.emitOrdersChanged([fresh], "overwriteExistingOrder");
634
+ await this.emitOrdersChanged([reconciledFresh], "overwriteExistingOrder");
533
635
  return { overwritten: true };
534
636
  }
535
637
  /**
@@ -601,11 +703,21 @@ var OrderModule = class extends import_BaseModule.BaseModule {
601
703
  const pendingMap = /* @__PURE__ */ new Map();
602
704
  const memoryPendingMap = /* @__PURE__ */ new Map();
603
705
  for (const order of pendingOrders) {
604
- const storageKey = this.getOrderStorageKey(order);
706
+ const normalizedOrder = this.normalizeOrderCollectionsForIngress(
707
+ order,
708
+ "upsertPendingSyncOrders.incoming"
709
+ );
710
+ const storageKey = this.getOrderStorageKey(normalizedOrder);
605
711
  if (!storageKey)
606
712
  continue;
607
- pendingMap.set(storageKey, order);
608
- memoryPendingMap.set(storageKey, this.normalizeOrderForMemoryList(order));
713
+ pendingMap.set(storageKey, normalizedOrder);
714
+ memoryPendingMap.set(
715
+ storageKey,
716
+ this.normalizeOrderForMemoryList(
717
+ normalizedOrder,
718
+ "upsertPendingSyncOrders.memory"
719
+ )
720
+ );
609
721
  }
610
722
  if (pendingMap.size === 0) {
611
723
  this.logError("upsertPendingSyncOrders-订单缺少可落库标识", {
@@ -614,25 +726,44 @@ var OrderModule = class extends import_BaseModule.BaseModule {
614
726
  return;
615
727
  }
616
728
  this.logInfo("upsertPendingSyncOrders-开始", { count: pendingMap.size });
617
- const patchedOrders = [...pendingMap.values()];
618
729
  const mergeActions = {};
619
- const updatedList = [...this.store.list];
730
+ const updatedList = this.store.list.map(
731
+ (order) => this.normalizeOrderForMemoryList(order, "upsertPendingSyncOrders.existingMemory")
732
+ );
733
+ const identityKeyToIndex = /* @__PURE__ */ new Map();
734
+ for (let index = 0; index < updatedList.length; index += 1) {
735
+ for (const key of this.getOrderIdentityMatchKeys(updatedList[index])) {
736
+ if (!identityKeyToIndex.has(key))
737
+ identityKeyToIndex.set(key, index);
738
+ }
739
+ }
620
740
  for (const [storageKey, pendingOrder] of memoryPendingMap.entries()) {
621
- const matchIndex = this.findOrderIndexByIdentity(updatedList, pendingOrder);
622
- if (matchIndex < 0)
741
+ const matchIndex = this.lookupOrderIndexByIdentityKeys(
742
+ identityKeyToIndex,
743
+ this.getOrderIdentityMatchKeys(pendingOrder)
744
+ );
745
+ if (matchIndex >= 0) {
746
+ const existingOrder = updatedList[matchIndex];
747
+ const mergedOrder = this.mergeOrderRecords(existingOrder, pendingOrder);
748
+ updatedList[matchIndex] = mergedOrder;
749
+ const pendingSnapshot = pendingMap.get(storageKey);
750
+ if (pendingSnapshot) {
751
+ pendingMap.set(
752
+ storageKey,
753
+ this.reconcileOverwriteOrderCollections(existingOrder, pendingSnapshot)
754
+ );
755
+ }
756
+ this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
757
+ mergeActions[storageKey] = "update";
623
758
  continue;
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()) {
759
+ }
760
+ const insertIndex = updatedList.length;
761
+ updatedList.push(pendingOrder);
762
+ this.registerOrderIdentityKeys(identityKeyToIndex, pendingOrder, insertIndex);
632
763
  mergeActions[storageKey] = "insert";
633
- updatedList.push(order);
634
764
  }
635
- this.store.list = this.compactOrderListByIdentity(updatedList, "upsertPendingSyncOrders.store").list;
765
+ const patchedOrders = [...pendingMap.values()];
766
+ this.store.list = this.runOrderStoreSelfCheck(updatedList, "upsertPendingSyncOrders.store");
636
767
  this.syncOrdersMap();
637
768
  await this.patchOrdersInSQLite(patchedOrders, "upsertPendingSyncOrders", mergeActions);
638
769
  this.logInfo("upsertPendingSyncOrders-结束", {
@@ -674,21 +805,11 @@ var OrderModule = class extends import_BaseModule.BaseModule {
674
805
  }
675
806
  syncOrdersMap() {
676
807
  this.store.map.clear();
677
- this.resourceIdIndex.clear();
678
808
  for (const order of this.store.list) {
679
809
  const primaryIdentity = this.getOrderMapKey(order);
680
810
  if (!primaryIdentity)
681
811
  continue;
682
812
  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
- }
692
813
  }
693
814
  }
694
815
  getOrderMapKey(order) {
@@ -735,24 +856,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
735
856
  }
736
857
  return keys;
737
858
  }
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
- }
756
859
  getOrderDateKey(order) {
757
860
  var _a, _b, _c, _d;
758
861
  const candidates = [
@@ -971,9 +1074,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
971
1074
  * 将增量订单合并到 store
972
1075
  */
973
1076
  async mergeOrdersToStore(freshOrders, source) {
1077
+ const storeOrderCountBefore = this.store.list.length;
974
1078
  this.logInfo("mergeOrdersToStore-开始", {
975
1079
  freshOrderCount: freshOrders.length,
976
- storeOrderCountBefore: this.store.list.length,
1080
+ storeOrderCountBefore,
977
1081
  source
978
1082
  });
979
1083
  this.logDuplicateOrders(`${source}.beforeMerge`, this.store.list);
@@ -991,20 +1095,48 @@ var OrderModule = class extends import_BaseModule.BaseModule {
991
1095
  const uniqueFreshCount = uniqueFreshOrders.length;
992
1096
  const patchedOrders = [];
993
1097
  const mergeActions = {};
994
- const updatedList = [...this.store.list];
1098
+ const updatedList = this.store.list.map(
1099
+ (order) => this.normalizeOrderForMemoryList(order, `${source}.existingMemory`)
1100
+ );
1101
+ const identityKeyToIndex = /* @__PURE__ */ new Map();
1102
+ for (let index = 0; index < updatedList.length; index += 1) {
1103
+ const identityKeys = this.getOrderIdentityMatchKeys(updatedList[index]);
1104
+ for (const key of identityKeys) {
1105
+ if (!identityKeyToIndex.has(key))
1106
+ identityKeyToIndex.set(key, index);
1107
+ }
1108
+ }
995
1109
  for (const fresh of uniqueFreshOrders) {
996
- const matchIndex = this.findOrderIndexByIdentity(updatedList, fresh);
1110
+ const freshIdentityKeys = this.getOrderIdentityMatchKeys(fresh);
1111
+ const matchIndex = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys);
997
1112
  const storageKey = this.getOrderStorageKey(fresh);
998
1113
  const mergeKey = storageKey || (fresh.order_id !== void 0 && fresh.order_id !== null ? this.getIdKey(fresh.order_id) : "");
999
1114
  if (matchIndex >= 0) {
1000
- const mergedOrder = this.mergeOrderRecords(updatedList[matchIndex], fresh);
1115
+ const existingOrder = updatedList[matchIndex];
1116
+ if (this.isPendingSyncOrder(existingOrder) && this.isRemoteEchoMergeSource(source)) {
1117
+ this.logInfo("mergeOrdersToStore-跳过 pending 订单远端覆盖", {
1118
+ source,
1119
+ order_id: existingOrder.order_id ?? fresh.order_id ?? null,
1120
+ external_sale_number: existingOrder.external_sale_number ?? fresh.external_sale_number ?? null,
1121
+ local_payment_status: existingOrder.payment_status ?? null,
1122
+ remote_payment_status: fresh.payment_status ?? null,
1123
+ local_need_sync: existingOrder.need_sync ?? null,
1124
+ remote_need_sync: fresh.need_sync ?? null,
1125
+ reason: "protect_pending_sync_order_from_remote_echo"
1126
+ });
1127
+ continue;
1128
+ }
1129
+ const mergedOrder = this.mergeOrderRecords(existingOrder, fresh);
1001
1130
  updatedList[matchIndex] = mergedOrder;
1131
+ this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex);
1002
1132
  patchedOrders.push(mergedOrder);
1003
1133
  if (mergeKey)
1004
1134
  mergeActions[mergeKey] = "update";
1005
1135
  continue;
1006
1136
  }
1137
+ const insertIndex = updatedList.length;
1007
1138
  updatedList.push(fresh);
1139
+ this.registerOrderIdentityKeys(identityKeyToIndex, fresh, insertIndex);
1008
1140
  patchedOrders.push(fresh);
1009
1141
  if (mergeKey)
1010
1142
  mergeActions[mergeKey] = "insert";
@@ -1016,7 +1148,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1016
1148
  updateCount,
1017
1149
  storeOrderCountAfter: this.store.list.length
1018
1150
  });
1019
- this.store.list = this.compactOrderListByIdentity(updatedList, `${source}.store`).list;
1151
+ this.store.list = this.runOrderStoreSelfCheck(updatedList, `${source}.store`);
1020
1152
  this.syncOrdersMap();
1021
1153
  await this.patchOrdersInSQLite(patchedOrders, source, mergeActions);
1022
1154
  this.logInfo("mergeOrdersToStore-结束", {
@@ -1027,6 +1159,53 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1027
1159
  });
1028
1160
  await this.emitOrdersChanged(patchedOrders, source);
1029
1161
  }
1162
+ /**
1163
+ * 在身份索引中查找与目标订单共享任一身份键的已存在订单下标。
1164
+ * 返回所有命中键里最小的下标,等价于原 findOrderIndexByIdentity 的「取首个匹配」语义。
1165
+ *
1166
+ * @example
1167
+ * const idx = this.lookupOrderIndexByIdentityKeys(identityKeyToIndex, freshIdentityKeys)
1168
+ */
1169
+ lookupOrderIndexByIdentityKeys(identityKeyToIndex, identityKeys) {
1170
+ let matchIndex = -1;
1171
+ for (const key of identityKeys) {
1172
+ const index = identityKeyToIndex.get(key);
1173
+ if (index === void 0)
1174
+ continue;
1175
+ if (matchIndex < 0 || index < matchIndex)
1176
+ matchIndex = index;
1177
+ }
1178
+ return matchIndex;
1179
+ }
1180
+ isRemoteEchoMergeSource(source) {
1181
+ return source === "pubsub.bodyUpsert" || source === "pubsub.httpRefresh";
1182
+ }
1183
+ /**
1184
+ * 将订单的全部身份键登记到索引并指向其在列表中的下标。
1185
+ * 合并后订单新增的身份字段(如挂单同步后拿到 order_id)也会被登记。
1186
+ *
1187
+ * @example
1188
+ * this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, matchIndex)
1189
+ */
1190
+ registerOrderIdentityKeys(identityKeyToIndex, order, index) {
1191
+ const identityKeys = this.getOrderIdentityMatchKeys(order);
1192
+ for (const key of identityKeys) {
1193
+ identityKeyToIndex.set(key, index);
1194
+ }
1195
+ }
1196
+ /**
1197
+ * 合并热路径去重兜底:默认直接返回原列表(增量索引已保证无重复)。
1198
+ * 仅当 ORDER_MERGE_SELF_CHECK 开启时,额外做一次全表压缩并在发现残留重复时告警。
1199
+ */
1200
+ runOrderStoreSelfCheck(orders, source) {
1201
+ if (!ORDER_MERGE_SELF_CHECK)
1202
+ return orders;
1203
+ const { list, removedCount } = this.compactOrderListByIdentity(orders, source);
1204
+ if (removedCount > 0) {
1205
+ this.logWarning("合并自检发现残留重复订单,已压缩", { source, removedCount });
1206
+ }
1207
+ return list;
1208
+ }
1030
1209
  normalizeOrderSyncMessage(res) {
1031
1210
  const data = (res == null ? void 0 : res.data) || res;
1032
1211
  if (!data)
@@ -1141,100 +1320,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1141
1320
  return right;
1142
1321
  return left;
1143
1322
  }
1144
- getProductIdentityKeys(product) {
1145
- var _a;
1146
- const record = product;
1147
- if (!record)
1148
- return [];
1149
- const keys = [];
1150
- const metadataUid = (_a = record.metadata) == null ? void 0 : _a.unique_identification_number;
1151
- if (!this.isBlankIdentityValue(metadataUid))
1152
- keys.push(`uid:${String(metadataUid)}`);
1153
- const topLevelUid = record.unique_identification_number;
1154
- if (!this.isBlankIdentityValue(topLevelUid))
1155
- keys.push(`uid:${String(topLevelUid)}`);
1156
- const orderDetailId = record.order_detail_id;
1157
- if (!this.isBlankIdentityValue(orderDetailId))
1158
- keys.push(`detail:${String(orderDetailId)}`);
1159
- const bookingUid = record.booking_uid;
1160
- if (!this.isBlankIdentityValue(bookingUid))
1161
- keys.push(`booking:${String(bookingUid)}`);
1162
- return keys;
1163
- }
1164
- mergeOrderProductLists(preferred, secondary) {
1165
- const preferredList = Array.isArray(preferred) ? preferred : [];
1166
- const secondaryList = Array.isArray(secondary) ? secondary : [];
1167
- if (secondaryList.length === 0)
1168
- return preferredList;
1169
- if (preferredList.length === 0)
1170
- return secondaryList;
1171
- const seenKeys = /* @__PURE__ */ new Set();
1172
- for (const item of preferredList) {
1173
- const keys = this.getProductIdentityKeys(item);
1174
- for (const key of keys)
1175
- seenKeys.add(key);
1176
- }
1177
- const result = [...preferredList];
1178
- for (const item of secondaryList) {
1179
- const keys = this.getProductIdentityKeys(item);
1180
- if (keys.length === 0 || keys.some((key) => seenKeys.has(key)))
1181
- continue;
1182
- for (const key of keys)
1183
- seenKeys.add(key);
1184
- result.push((0, import_lodash_es.cloneDeep)(item));
1185
- }
1186
- return result;
1187
- }
1188
- getPaymentMergeKey(payment) {
1189
- var _a;
1190
- const record = payment;
1191
- if (!record)
1192
- return "";
1193
- const uniquePaymentNumber = (_a = record.metadata) == null ? void 0 : _a.unique_payment_number;
1194
- if (!this.isBlankIdentityValue(uniquePaymentNumber)) {
1195
- return `unique:${String(uniquePaymentNumber)}`;
1196
- }
1197
- const orderPaymentId = record.order_payment_id;
1198
- if (!this.isBlankIdentityValue(orderPaymentId))
1199
- return `order_payment:${String(orderPaymentId)}`;
1200
- const code = record.code;
1201
- const customPaymentId = record.custom_payment_id;
1202
- if (!this.isBlankIdentityValue(code) && !this.isBlankIdentityValue(customPaymentId)) {
1203
- return `custom:${String(code)}#${String(customPaymentId)}`;
1204
- }
1205
- return "";
1206
- }
1207
- mergeOrderPaymentLists(existing, incoming) {
1208
- const existingList = Array.isArray(existing) ? existing : [];
1209
- const incomingList = Array.isArray(incoming) ? incoming : [];
1210
- if (incomingList.length === 0)
1211
- return (0, import_lodash_es.cloneDeep)(existingList);
1212
- if (existingList.length === 0)
1213
- return (0, import_lodash_es.cloneDeep)(incomingList);
1214
- const existingByKey = /* @__PURE__ */ new Map();
1215
- for (const payment of existingList) {
1216
- const key = this.getPaymentMergeKey(payment);
1217
- if (!key)
1218
- continue;
1219
- existingByKey.set(key, payment);
1220
- }
1221
- return incomingList.map((payment) => {
1222
- const incomingPayment = payment;
1223
- const key = this.getPaymentMergeKey(incomingPayment);
1224
- const existingPayment = key ? existingByKey.get(key) : void 0;
1225
- if (!existingPayment)
1226
- return (0, import_lodash_es.cloneDeep)(incomingPayment);
1227
- return {
1228
- ...(0, import_lodash_es.cloneDeep)(existingPayment),
1229
- ...(0, import_lodash_es.cloneDeep)(incomingPayment),
1230
- metadata: {
1231
- ...(0, import_lodash_es.cloneDeep)(existingPayment.metadata || {}),
1232
- ...(0, import_lodash_es.cloneDeep)(incomingPayment.metadata || {})
1233
- }
1234
- };
1235
- });
1236
- }
1237
- mergeOrderRecords(existing, incoming, options) {
1323
+ mergeOrderRecords(existing, incoming) {
1238
1324
  const preferred = this.pickPreferredOrder(existing, incoming);
1239
1325
  const secondary = preferred === existing ? incoming : existing;
1240
1326
  const merged = {
@@ -1255,8 +1341,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1255
1341
  "updated_at",
1256
1342
  "shop_discount",
1257
1343
  "summary",
1258
- "products",
1259
- "bookings",
1260
1344
  "note",
1261
1345
  "customer_id",
1262
1346
  "customer_name",
@@ -1270,8 +1354,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1270
1354
  const mergedRecord = merged;
1271
1355
  const preferredRecord = preferred;
1272
1356
  const secondaryRecord = secondary;
1357
+ const existingRecord = existing;
1273
1358
  const incomingRecord = incoming;
1274
- const previousRecord = preferred === incoming ? secondaryRecord : preferredRecord;
1359
+ const childUidRemaps = [];
1275
1360
  for (const field of fieldsToPreserve) {
1276
1361
  if (!this.isBlankIdentityValue(preferredRecord[field])) {
1277
1362
  mergedRecord[field] = preferredRecord[field];
@@ -1281,8 +1366,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1281
1366
  mergedRecord[field] = secondaryRecord[field];
1282
1367
  }
1283
1368
  }
1284
- const hasIncomingProducts = Object.prototype.hasOwnProperty.call(incomingRecord, "products");
1285
- const shouldUseIncomingProductsSnapshot = (options == null ? void 0 : options.preferIncomingProducts) === true && hasIncomingProducts && incomingRecord.products !== void 0 && incomingRecord.products !== null;
1286
1369
  for (const field of incomingPatchFields) {
1287
1370
  if (!Object.prototype.hasOwnProperty.call(incomingRecord, field))
1288
1371
  continue;
@@ -1292,21 +1375,29 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1292
1375
  continue;
1293
1376
  mergedRecord[field] = (0, import_lodash_es.cloneDeep)(incomingRecord[field]);
1294
1377
  }
1295
- mergedRecord.products = shouldUseIncomingProductsSnapshot ? (0, import_lodash_es.cloneDeep)(incomingRecord.products) : this.mergeOrderProductLists(preferredRecord.products, secondaryRecord.products);
1296
- if (Object.prototype.hasOwnProperty.call(incomingRecord, "payments")) {
1297
- if (Array.isArray(incomingRecord.payments)) {
1298
- mergedRecord.payments = this.mergeOrderPaymentLists(
1299
- previousRecord.payments,
1300
- incomingRecord.payments
1378
+ for (const field of ORDER_AUTHORITATIVE_COLLECTION_FIELDS) {
1379
+ if (Array.isArray(incomingRecord[field])) {
1380
+ mergedRecord[field] = this.mergeAuthoritativeOrderCollection(
1381
+ field,
1382
+ existingRecord[field],
1383
+ incomingRecord[field],
1384
+ childUidRemaps
1301
1385
  );
1302
- } else if (incomingRecord.payments !== void 0 && incomingRecord.payments !== null) {
1303
- mergedRecord.payments = (0, import_lodash_es.cloneDeep)(incomingRecord.payments);
1386
+ continue;
1387
+ }
1388
+ if (Object.prototype.hasOwnProperty.call(existingRecord, field)) {
1389
+ mergedRecord[field] = (0, import_lodash_es.cloneDeep)(existingRecord[field]);
1390
+ } else {
1391
+ delete mergedRecord[field];
1304
1392
  }
1305
1393
  }
1306
1394
  if (!this.isPendingSyncOrder(preferred) || !this.isPendingSyncOrder(secondary)) {
1307
1395
  merged.need_sync = 0;
1308
1396
  }
1309
- return merged;
1397
+ return this.normalizeOrderCollectionsForIngress(
1398
+ (0, import_orderCollectionIdentity.applyOrderCollectionUidRemaps)(merged, childUidRemaps),
1399
+ "mergeOrderRecords"
1400
+ );
1310
1401
  }
1311
1402
  summarizeDuplicateOrders(orders) {
1312
1403
  const groups = /* @__PURE__ */ new Map();
@@ -1343,12 +1434,16 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1343
1434
  const list = [];
1344
1435
  let removedCount = 0;
1345
1436
  for (const order of orders) {
1346
- const matchIndex = this.findOrderIndexByIdentity(list, order);
1437
+ const normalizedOrder = this.normalizeOrderCollectionsForIngress(
1438
+ order,
1439
+ `${source}.order`
1440
+ );
1441
+ const matchIndex = this.findOrderIndexByIdentity(list, normalizedOrder);
1347
1442
  if (matchIndex < 0) {
1348
- list.push(order);
1443
+ list.push(normalizedOrder);
1349
1444
  continue;
1350
1445
  }
1351
- list[matchIndex] = this.mergeOrderRecords(list[matchIndex], order);
1446
+ list[matchIndex] = this.mergeOrderRecords(list[matchIndex], normalizedOrder);
1352
1447
  removedCount += 1;
1353
1448
  }
1354
1449
  if (removedCount > 0) {
@@ -1428,7 +1523,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1428
1523
  return [];
1429
1524
  try {
1430
1525
  const orders = await this.dbManager.getAll(INDEXDB_STORE_NAME);
1431
- return orders || [];
1526
+ return (orders || []).map(
1527
+ (order) => this.normalizeOrderCollectionsForIngress(order, "loadOrdersFromSQLite")
1528
+ );
1432
1529
  } catch {
1433
1530
  return [];
1434
1531
  }
@@ -1437,16 +1534,24 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1437
1534
  return Number(order == null ? void 0 : order.need_sync) === 1;
1438
1535
  }
1439
1536
  normalizeRemoteSyncedOrder(order) {
1440
- if (this.isPendingSyncOrder(order))
1441
- return order;
1537
+ const normalizedOrder = this.normalizeOrderCollectionsForIngress(
1538
+ order,
1539
+ "normalizeRemoteSyncedOrder"
1540
+ );
1541
+ if (this.isPendingSyncOrder(normalizedOrder))
1542
+ return normalizedOrder;
1442
1543
  return {
1443
- ...order,
1544
+ ...normalizedOrder,
1444
1545
  need_sync: 0
1445
1546
  };
1446
1547
  }
1447
1548
  mergeRemoteSnapshotWithPendingOrders(remoteOrders, existingOrders) {
1448
1549
  const merged = remoteOrders.map((order) => this.normalizeRemoteSyncedOrder(order));
1449
- for (const order of existingOrders) {
1550
+ for (const rawOrder of existingOrders) {
1551
+ const order = this.normalizeOrderCollectionsForIngress(
1552
+ rawOrder,
1553
+ "mergeRemoteSnapshotWithPendingOrders.existingSQLite"
1554
+ );
1450
1555
  if (!this.isPendingSyncOrder(order))
1451
1556
  continue;
1452
1557
  const matchIndex = this.findOrderIndexByIdentity(merged, order);
@@ -1483,7 +1588,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1483
1588
  if (!this.dbManager || orders.length === 0) {
1484
1589
  return;
1485
1590
  }
1486
- const ordersSnapshot = (0, import_lodash_es.cloneDeep)(orders).filter((order) => this.getOrderStorageKey(order));
1591
+ const ordersSnapshot = (0, import_lodash_es.cloneDeep)(orders).map((order) => this.normalizeOrderCollectionsForIngress(order, `${source}.sqliteIngress`)).filter((order) => this.getOrderStorageKey(order));
1487
1592
  if (ordersSnapshot.length === 0) {
1488
1593
  return;
1489
1594
  }
@@ -1544,7 +1649,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1544
1649
  if (!this.dbManager || !this.getOrderStorageKey(order)) {
1545
1650
  return;
1546
1651
  }
1547
- const orderSnapshot = (0, import_lodash_es.cloneDeep)(order);
1652
+ const orderSnapshot = this.normalizeOrderCollectionsForIngress(
1653
+ (0, import_lodash_es.cloneDeep)(order),
1654
+ `${source}.sqliteIngress`
1655
+ );
1548
1656
  try {
1549
1657
  await this.runInOrderSQLiteSaveQueue(async () => {
1550
1658
  let writeMethod = "none";
@@ -1590,7 +1698,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1590
1698
  `${source}.snapshotNoDb`
1591
1699
  ).list;
1592
1700
  }
1593
- const remoteSnapshot = (0, import_lodash_es.cloneDeep)(orderList);
1701
+ const remoteSnapshot = (0, import_lodash_es.cloneDeep)(orderList).map(
1702
+ (order) => this.normalizeOrderCollectionsForIngress(order, `${source}.remoteIngress`)
1703
+ );
1594
1704
  let mergedSnapshot = this.compactOrderListByIdentity(
1595
1705
  remoteSnapshot.map((order) => this.normalizeRemoteSyncedOrder(order)),
1596
1706
  `${source}.remoteSnapshot`
@@ -1640,7 +1750,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1640
1750
  async clear() {
1641
1751
  this.store.list = [];
1642
1752
  this.store.map = /* @__PURE__ */ new Map();
1643
- this.resourceIdIndex.clear();
1644
1753
  if (this.dbManager) {
1645
1754
  await this.runInOrderSQLiteSaveQueue(async () => {
1646
1755
  await this.dbManager.clear(INDEXDB_STORE_NAME);