@pisell/pisellos 2.3.66 → 2.3.68

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 (47) hide show
  1. package/dist/model/strategy/adapter/promotion/index.js +0 -9
  2. package/dist/modules/BaseModule.d.ts +1 -0
  3. package/dist/modules/BaseModule.js +24 -28
  4. package/dist/modules/Order/index.d.ts +22 -2
  5. package/dist/modules/Order/index.js +313 -142
  6. package/dist/modules/Order/types.d.ts +13 -1
  7. package/dist/modules/Order/utils/orderCollectionIdentity.d.ts +15 -0
  8. package/dist/modules/Order/utils/orderCollectionIdentity.js +75 -0
  9. package/dist/modules/Order/utils.js +0 -1
  10. package/dist/modules/Payment/index.d.ts +4 -0
  11. package/dist/modules/Payment/index.js +14 -4
  12. package/dist/modules/Payment/walletpass.js +21 -7
  13. package/dist/modules/Rules/index.js +46 -31
  14. package/dist/solution/BaseSales/index.d.ts +3 -1
  15. package/dist/solution/BaseSales/index.js +99 -82
  16. package/dist/solution/BaseSales/utils.js +31 -20
  17. package/dist/solution/BookingByStep/index.d.ts +1 -1
  18. package/dist/solution/BookingTicket/index.d.ts +4 -2
  19. package/dist/solution/BookingTicket/index.js +89 -65
  20. package/dist/solution/ScanOrder/utils.js +31 -20
  21. package/dist/solution/UnifiedBookingSales/index.d.ts +25 -0
  22. package/dist/solution/UnifiedBookingSales/index.js +640 -321
  23. package/dist/solution/UnifiedBookingSales/types.d.ts +7 -0
  24. package/lib/model/strategy/adapter/promotion/index.js +0 -51
  25. package/lib/modules/BaseModule.d.ts +1 -0
  26. package/lib/modules/BaseModule.js +24 -37
  27. package/lib/modules/Order/index.d.ts +22 -2
  28. package/lib/modules/Order/index.js +212 -54
  29. package/lib/modules/Order/types.d.ts +13 -1
  30. package/lib/modules/Order/utils/orderCollectionIdentity.d.ts +15 -0
  31. package/lib/modules/Order/utils/orderCollectionIdentity.js +70 -0
  32. package/lib/modules/Order/utils.js +0 -1
  33. package/lib/modules/Payment/index.d.ts +4 -0
  34. package/lib/modules/Payment/index.js +7 -0
  35. package/lib/modules/Payment/walletpass.js +12 -0
  36. package/lib/modules/Rules/index.js +17 -1
  37. package/lib/solution/BaseSales/index.d.ts +3 -1
  38. package/lib/solution/BaseSales/index.js +29 -16
  39. package/lib/solution/BaseSales/utils.js +29 -18
  40. package/lib/solution/BookingByStep/index.d.ts +1 -1
  41. package/lib/solution/BookingTicket/index.d.ts +4 -2
  42. package/lib/solution/BookingTicket/index.js +35 -12
  43. package/lib/solution/ScanOrder/utils.js +29 -18
  44. package/lib/solution/UnifiedBookingSales/index.d.ts +25 -0
  45. package/lib/solution/UnifiedBookingSales/index.js +188 -0
  46. package/lib/solution/UnifiedBookingSales/types.d.ts +7 -0
  47. package/package.json +1 -1
@@ -66,8 +66,7 @@ function isSameDiscountList(left, right) {
66
66
  );
67
67
  }
68
68
  function isManualProductDiscountProduct(product) {
69
- var _a, _b;
70
- return ((_a = product == null ? void 0 : product.metadata) == null ? void 0 : _a.is_manual_discount) === true || ((_b = product == null ? void 0 : product.metadata) == null ? void 0 : _b.is_manual_discount) === 1 || ((product == null ? void 0 : product.discount_list) || []).some((item) => (item == null ? void 0 : item.type) === "product");
69
+ return (0, import_manualProductDiscount.hasManualProductDiscountFlag)(product == null ? void 0 : product.metadata) || ((product == null ? void 0 : product.discount_list) || []).some((item) => (item == null ? void 0 : item.type) === "product");
71
70
  }
72
71
  var ORDER_PAYMENT_STATUS_PAID = "paid";
73
72
  var ORDER_PAYMENT_STATUS_PENDING = "payment_pending";
@@ -114,6 +113,9 @@ var OrderModule = class extends import_BaseModule.BaseModule {
114
113
  super(name || "order", version);
115
114
  this.defaultName = "order";
116
115
  this.defaultVersion = "1.0.0";
116
+ this.openCache = false;
117
+ this.sessionStoragePersistEnabled = false;
118
+ this.sessionStorageRestoreRecord = null;
117
119
  // ─── 促销/赠品 ────────────────────────────────────
118
120
  /** 评估器引用;由 SalesSdkProvider 注入,未注入时 applyPromotion 静默跳过 */
119
121
  this.promotionEvaluator = null;
@@ -330,11 +332,32 @@ var OrderModule = class extends import_BaseModule.BaseModule {
330
332
  return product;
331
333
  });
332
334
  }
335
+ syncSessionStorageOptions(otherParams) {
336
+ this.cacheId = otherParams.cacheId;
337
+ this.fatherModule = otherParams.fatherModule;
338
+ this.openCache = Boolean(
339
+ this.cacheId && otherParams.openCache !== false
340
+ );
341
+ this.sessionStoragePersistEnabled = Boolean(
342
+ this.openCache && otherParams.enableSessionStoragePersist === true
343
+ );
344
+ }
333
345
  async initialize(core, options) {
334
346
  var _a, _b, _c, _d;
335
347
  this.core = core;
336
348
  this.store = options.store;
337
- if (!this.store.tempOrder) {
349
+ const otherParams = options.otherParams || {};
350
+ this.syncSessionStorageOptions(otherParams);
351
+ if (this.sessionStoragePersistEnabled) {
352
+ const cachedTempOrder = this.store.tempOrder;
353
+ this.sessionStorageRestoreRecord = (0, import_utils.isTempOrder)(cachedTempOrder) ? (0, import_lodash_es.cloneDeep)(cachedTempOrder) : null;
354
+ this.store.tempOrder = null;
355
+ this.store.summary = (0, import_utils.createEmptySummary)();
356
+ this.store.availableWalletIds = [];
357
+ this.store.lastOrderInfo = void 0;
358
+ this.store.syncState = void 0;
359
+ this.store.discountList = [];
360
+ } else if (!this.store.tempOrder) {
338
361
  this.store.tempOrder = null;
339
362
  }
340
363
  if (!this.store.summary) {
@@ -351,8 +374,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
351
374
  const app = (_a = appPlugin == null ? void 0 : appPlugin.getApp) == null ? void 0 : _a.call(appPlugin);
352
375
  this.logger = app == null ? void 0 : app.logger;
353
376
  this.window = this.core.getPlugin("window");
354
- const otherParams = options.otherParams || {};
355
- this.cacheId = otherParams.cacheId;
356
377
  this.salesSummaryModuleName = otherParams.salesSummaryModuleName;
357
378
  this.rulesHooksOverride = (_b = otherParams.rules) == null ? void 0 : _b.hooks;
358
379
  this.moduleHooksOverride = options.hooks;
@@ -363,6 +384,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
363
384
  }
364
385
  async destroy() {
365
386
  var _a, _b;
387
+ this.sessionStorageRestoreRecord = null;
366
388
  const childModules = [(_a = this.store) == null ? void 0 : _a.discount, (_b = this.store) == null ? void 0 : _b.rules];
367
389
  for (const childModule of childModules) {
368
390
  if (!childModule || typeof childModule.destroy !== "function")
@@ -392,7 +414,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
392
414
  updateOtherParams(params) {
393
415
  var _a, _b, _c;
394
416
  const otherParams = params || {};
395
- this.cacheId = otherParams.cacheId;
417
+ this.syncSessionStorageOptions(otherParams);
396
418
  this.salesSummaryModuleName = otherParams.salesSummaryModuleName;
397
419
  this.rulesHooksOverride = (_a = otherParams.rules) == null ? void 0 : _a.hooks;
398
420
  this.orderHooks = this.moduleHooksOverride || ((_b = otherParams.order) == null ? void 0 : _b.hooks) || ((_c = otherParams.hooks) == null ? void 0 : _c.order);
@@ -437,14 +459,14 @@ var OrderModule = class extends import_BaseModule.BaseModule {
437
459
  // ─── Discount: 子模块注册 ───
438
460
  registerDiscountModules(options) {
439
461
  let targetCacheData = {};
440
- if (this.cacheId && this.window) {
441
- const sessionData = this.window.sessionStorage.getItem(this.name);
442
- if (sessionData) {
443
- try {
462
+ if (!this.sessionStoragePersistEnabled && this.openCache && this.cacheId && this.window) {
463
+ try {
464
+ const sessionData = this.window.sessionStorage.getItem(this.name);
465
+ if (sessionData) {
444
466
  const data = JSON.parse(sessionData);
445
467
  targetCacheData = (data == null ? void 0 : data[this.cacheId]) || {};
446
- } catch {
447
468
  }
469
+ } catch {
448
470
  }
449
471
  }
450
472
  const discount = new import_Discount.DiscountModule(`${this.name}_discount`);
@@ -452,7 +474,8 @@ var OrderModule = class extends import_BaseModule.BaseModule {
452
474
  initialState: targetCacheData == null ? void 0 : targetCacheData[discount.name],
453
475
  otherParams: {
454
476
  fatherModule: this.name,
455
- openCache: !!this.cacheId,
477
+ // Unified 的单快照由 Order 管理;其他 Solution 保持原有 Discount 缓存语义。
478
+ openCache: this.openCache && !this.sessionStoragePersistEnabled,
456
479
  cacheId: this.cacheId
457
480
  }
458
481
  });
@@ -870,7 +893,8 @@ var OrderModule = class extends import_BaseModule.BaseModule {
870
893
  }
871
894
  /**
872
895
  * 控制 IndexedDB 草稿(saveDraft)是否写入。
873
- * localStorage tempOrder 持久化已废弃,此方法仅影响 saveDraft。
896
+ * 此开关仅影响 saveDraft;可选 sessionStorage 会话恢复由
897
+ * enableSessionStoragePersist 独立控制。
874
898
  *
875
899
  * @example
876
900
  * order.setEnableTempOrderPersist(false); // BigSale 弹窗:跳过 ~1s 的 dbUpdate
@@ -882,8 +906,70 @@ var OrderModule = class extends import_BaseModule.BaseModule {
882
906
  isTempOrderPersistEnabled() {
883
907
  return this.draftPersistEnabled;
884
908
  }
885
- /** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化;草稿保存请使用 IndexedDB saveDraft。 */
909
+ /**
910
+ * 在 UnifiedBookingSales 显式开启会话恢复时,将完整 tempOrder 写入
911
+ * sessionStorage 的 cacheId 分桶。写入失败由 BaseModule 静默降级。
912
+ */
886
913
  persistTempOrder() {
914
+ if (!this.sessionStoragePersistEnabled || !this.store.tempOrder)
915
+ return;
916
+ this.checkSaveCache({
917
+ cacheId: this.cacheId,
918
+ fatherModule: this.fatherModule,
919
+ store: { tempOrder: this.store.tempOrder },
920
+ cacheKey: ["tempOrder"]
921
+ });
922
+ }
923
+ /**
924
+ * 终止当前模块实例的 sessionStorage 订单恢复会话。
925
+ *
926
+ * 支付跳转等终态场景需要同时删除当前 cacheId 的 tempOrder 快照,并关闭
927
+ * 本实例后续异步任务的再次写入;新模块实例初始化时会按 otherParams 重新启用。
928
+ */
929
+ clearSessionStoragePersistedTempOrder() {
930
+ var _a;
931
+ this.sessionStorageRestoreRecord = null;
932
+ if (!this.sessionStoragePersistEnabled)
933
+ return;
934
+ this.sessionStoragePersistEnabled = false;
935
+ if (!this.cacheId || !this.fatherModule || !((_a = this.window) == null ? void 0 : _a.sessionStorage)) {
936
+ return;
937
+ }
938
+ const sessionStorage = this.window.sessionStorage;
939
+ const rawCacheData = sessionStorage.getItem(this.fatherModule);
940
+ if (!rawCacheData)
941
+ return;
942
+ let parsedCacheData;
943
+ try {
944
+ parsedCacheData = JSON.parse(rawCacheData);
945
+ } catch {
946
+ return;
947
+ }
948
+ if (!parsedCacheData || typeof parsedCacheData !== "object" || Array.isArray(parsedCacheData)) {
949
+ return;
950
+ }
951
+ const cacheRoot = parsedCacheData;
952
+ const cacheBucket = cacheRoot[this.cacheId];
953
+ if (!cacheBucket || typeof cacheBucket !== "object" || Array.isArray(cacheBucket)) {
954
+ return;
955
+ }
956
+ const moduleBucket = cacheBucket[this.name];
957
+ if (!moduleBucket || typeof moduleBucket !== "object" || Array.isArray(moduleBucket)) {
958
+ return;
959
+ }
960
+ delete moduleBucket.tempOrder;
961
+ if (Object.keys(moduleBucket).length === 0) {
962
+ delete cacheBucket[this.name];
963
+ }
964
+ if (Object.keys(cacheBucket).length === 0) {
965
+ delete cacheRoot[this.cacheId];
966
+ }
967
+ sessionStorage.setItem(this.fatherModule, JSON.stringify(cacheRoot));
968
+ }
969
+ consumeSessionStorageRestore() {
970
+ const record = this.sessionStorageRestoreRecord;
971
+ this.sessionStorageRestoreRecord = null;
972
+ return record ? (0, import_lodash_es.cloneDeep)(record) : null;
887
973
  }
888
974
  notifyTempOrderChanged() {
889
975
  if (!this.store.tempOrder)
@@ -1084,7 +1170,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1084
1170
  paidAmount: netPaidAmount
1085
1171
  });
1086
1172
  const isOrderFullyPaid = orderExpectedAmount.lte(0) ? true : netPaidAmount.gte(orderExpectedAmount);
1087
- const paymentStage = this.isDepositOrder(tempOrder, depositAmount) && !isDepositFullyPaid ? "deposit" : "normal";
1173
+ const paymentStage = this.isDepositOrder(tempOrder, depositAmount) && !isDepositFullyPaid && !this.hasPaidNormalPayment(paymentList) ? "deposit" : "normal";
1088
1174
  return {
1089
1175
  isFullyPaid: isOrderFullyPaid,
1090
1176
  isOrderFullyPaid,
@@ -1105,12 +1191,22 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1105
1191
  return sum.plus(this.calculatePaymentGapAmount(paymentRecord));
1106
1192
  }, new import_decimal.default(0));
1107
1193
  }
1194
+ hasPaidNormalPayment(payments) {
1195
+ return (payments || []).some((payment) => {
1196
+ const paymentRecord = payment;
1197
+ if (!isPaidPaymentRecord(paymentRecord))
1198
+ return false;
1199
+ return paymentRecord.order_payment_type !== "deposit" && paymentRecord.type !== "deposit";
1200
+ });
1201
+ }
1108
1202
  resolveNextOrderPaymentType(tempOrder) {
1109
1203
  const depositAmount = new import_decimal.default(tempOrder.deposit_amount || "0.00");
1110
1204
  if (!this.isDepositOrder(tempOrder, depositAmount))
1111
1205
  return "normal";
1112
1206
  if (depositAmount.lte(0))
1113
1207
  return "normal";
1208
+ if (this.hasPaidNormalPayment(tempOrder.payments || []))
1209
+ return "normal";
1114
1210
  const depositPaidAmount = this.calculateDepositPaidAmount(tempOrder.payments || []);
1115
1211
  return depositPaidAmount.lt(depositAmount) ? "deposit" : "normal";
1116
1212
  }
@@ -1408,24 +1504,28 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1408
1504
  }
1409
1505
  }
1410
1506
  calculateSummaryAmountGap(params) {
1411
- const { tempOrder, summary, orderPaidAmount } = params;
1507
+ const { tempOrder, summary, orderPaidAmount, payments } = params;
1412
1508
  const refundAmount = this.getSummaryRefundAmount(summary);
1413
1509
  const netOrderPaidAmount = import_decimal.default.max(orderPaidAmount.minus(refundAmount), 0);
1414
1510
  const targetAmount = this.calculatePaymentTargetAmount(
1415
1511
  tempOrder,
1416
1512
  summary,
1417
- netOrderPaidAmount
1513
+ netOrderPaidAmount,
1514
+ payments
1418
1515
  );
1419
1516
  return targetAmount.minus(netOrderPaidAmount).toFixed(2);
1420
1517
  }
1421
1518
  getSummaryRefundAmount(summary) {
1422
1519
  return import_decimal.default.max(new import_decimal.default((summary == null ? void 0 : summary.total_refund_amount) || 0), 0);
1423
1520
  }
1424
- calculatePaymentTargetAmount(tempOrder, summary, paidAmount = new import_decimal.default(0)) {
1521
+ calculatePaymentTargetAmount(tempOrder, summary, paidAmount = new import_decimal.default(0), payments = tempOrder.payments || []) {
1425
1522
  const depositAmount = this.getDepositAmount(tempOrder, summary);
1426
1523
  if (!this.isDepositOrder(tempOrder, depositAmount)) {
1427
1524
  return this.getOrderExpectedAmount(summary);
1428
1525
  }
1526
+ if (this.hasPaidNormalPayment(payments)) {
1527
+ return this.getOrderExpectedAmount(summary);
1528
+ }
1429
1529
  if (this.isDepositCovered({ tempOrder, summary, paidAmount })) {
1430
1530
  return this.getOrderExpectedAmount(summary);
1431
1531
  }
@@ -1605,19 +1705,21 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1605
1705
  return snapshot;
1606
1706
  }
1607
1707
  isSnapshotPayableAmountUnchanged(tempOrder, snapshot, snapshotSummary) {
1608
- const currentPaidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
1708
+ const currentPayments = tempOrder.payments || [];
1709
+ const currentPaidPaymentSummary = this.calculatePaidPaymentSummary(currentPayments);
1609
1710
  const currentAmountGap = this.calculateSummaryAmountGap({
1610
1711
  tempOrder,
1611
1712
  summary: snapshotSummary,
1612
- orderPaidAmount: currentPaidPaymentSummary.gapPaidAmount
1713
+ orderPaidAmount: currentPaidPaymentSummary.gapPaidAmount,
1714
+ payments: currentPayments
1613
1715
  });
1614
- const snapshotPaidPaymentSummary = this.calculatePaidPaymentSummary(
1615
- Array.isArray(snapshot.payments) ? snapshot.payments : []
1616
- );
1716
+ const snapshotPayments = Array.isArray(snapshot.payments) ? snapshot.payments : [];
1717
+ const snapshotPaidPaymentSummary = this.calculatePaidPaymentSummary(snapshotPayments);
1617
1718
  const snapshotAmountGap = this.calculateSummaryAmountGap({
1618
1719
  tempOrder,
1619
1720
  summary: snapshotSummary,
1620
- orderPaidAmount: snapshotPaidPaymentSummary.gapPaidAmount
1721
+ orderPaidAmount: snapshotPaidPaymentSummary.gapPaidAmount,
1722
+ payments: snapshotPayments
1621
1723
  });
1622
1724
  return currentAmountGap === snapshotAmountGap;
1623
1725
  }
@@ -1659,6 +1761,12 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1659
1761
  const isPayableAmountUnchanged = this.isSnapshotPayableAmountUnchanged(tempOrder, snapshot, summary);
1660
1762
  if (!isPayableAmountUnchanged)
1661
1763
  return null;
1764
+ const currentPaidPaymentSummary = this.calculatePaidPaymentSummary(tempOrder.payments || []);
1765
+ summary.amount_gap = this.calculateSummaryAmountGap({
1766
+ tempOrder,
1767
+ summary,
1768
+ orderPaidAmount: currentPaidPaymentSummary.gapPaidAmount
1769
+ });
1662
1770
  if (Array.isArray(snapshot.products)) {
1663
1771
  tempOrder.products = (0, import_lodash_es.cloneDeep)(snapshot.products);
1664
1772
  }
@@ -1738,6 +1846,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1738
1846
  restoreOrder() {
1739
1847
  var _a, _b;
1740
1848
  this.logInfo("restoreOrder start", {});
1849
+ this.sessionStorageRestoreRecord = null;
1741
1850
  const freshTempOrder = this.createDefaultTempOrderInstance();
1742
1851
  this.ensureExternalSaleNumber(freshTempOrder);
1743
1852
  this.store.tempOrder = freshTempOrder;
@@ -1756,6 +1865,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1756
1865
  if (!(0, import_utils.isTempOrder)(tempOrder)) {
1757
1866
  throw new Error("无效的 tempOrder 数据");
1758
1867
  }
1868
+ this.sessionStorageRestoreRecord = null;
1759
1869
  const nextTempOrder = this.normalizeTempOrderForRuntime(
1760
1870
  (0, import_lodash_es.cloneDeep)(tempOrder),
1761
1871
  { ensureIdentity: false }
@@ -1894,6 +2004,23 @@ var OrderModule = class extends import_BaseModule.BaseModule {
1894
2004
  }
1895
2005
  return tempOrder._extend;
1896
2006
  }
2007
+ repairDuplicateProductLineIdentities(tempOrder) {
2008
+ var _a;
2009
+ const result = (0, import_orderCollectionIdentity.repairDuplicateProductLineIdentities)(
2010
+ tempOrder.products,
2011
+ (_a = tempOrder._extend) == null ? void 0 : _a.productsByUid
2012
+ );
2013
+ if (!result.repairs.length)
2014
+ return;
2015
+ tempOrder.products = result.products;
2016
+ if (result.productsByUid) {
2017
+ this.ensureExtend(tempOrder).productsByUid = result.productsByUid;
2018
+ }
2019
+ this.logWarning("Repaired duplicate product line identities", {
2020
+ repairCount: result.repairs.length,
2021
+ repairs: result.repairs
2022
+ });
2023
+ }
1897
2024
  captureProductRuntime(tempOrder, rawProduct, normalizedProduct, existedUid) {
1898
2025
  var _a;
1899
2026
  const uid = existedUid || this.getProductUid(normalizedProduct);
@@ -2949,15 +3076,18 @@ var OrderModule = class extends import_BaseModule.BaseModule {
2949
3076
  normalizedIncoming
2950
3077
  );
2951
3078
  const hasIncomingProductNote = this.hasOrderProductLineNote(normalizedIncoming);
3079
+ const hasIncomingManualProductDiscount = isManualProductDiscountProduct(normalizedIncoming);
2952
3080
  const hasIncomingBookingUid = this.hasLinkedBookingUid(productToAdd) || this.hasLinkedBookingUid(normalizedIncoming);
2953
3081
  const isIncomingCustomItem = ((_e = (_d = productToAdd.metadata) == null ? void 0 : _d.origin) == null ? void 0 : _e.isCustomItem) === true || ((_f = productToAdd._origin) == null ? void 0 : _f.isCustomItem) === true;
2954
- const shouldForceNewLine = !!booking || hasIncomingProductNote || hasIncomingBookingUid || isIncomingCustomItem || isWeighingProduct;
3082
+ const shouldForceNewLine = !!booking || hasIncomingProductNote || hasIncomingManualProductDiscount || hasIncomingBookingUid || isIncomingCustomItem || isWeighingProduct;
2955
3083
  const matchedIndex = hasIncomingGoodPass || shouldForceNewLine ? -1 : tempOrder.products.findIndex((item) => {
2956
3084
  var _a2;
2957
3085
  if (this.hasGoodPassDiscount(item))
2958
3086
  return false;
2959
3087
  if (this.hasOrderProductLineNote(item))
2960
3088
  return false;
3089
+ if (isManualProductDiscountProduct(item))
3090
+ return false;
2961
3091
  if (item.order_detail_id !== void 0 && item.order_detail_id !== null)
2962
3092
  return false;
2963
3093
  if ((_a2 = item.metadata) == null ? void 0 : _a2.is_edit_for_runtime)
@@ -3019,17 +3149,10 @@ var OrderModule = class extends import_BaseModule.BaseModule {
3019
3149
  normalizedProduct.discount_list
3020
3150
  );
3021
3151
  const nextNum = (0, import_utils3.getSafeProductNum)(targetProduct.num + normalizedProduct.num);
3022
- const targetIsManualProductDiscount = isManualProductDiscountProduct(targetProduct);
3023
- const nextDiscountList = targetIsManualProductDiscount ? (0, import_utils.normalizeOrderProductDiscountList)(
3024
- (0, import_manualProductDiscount.syncManualProductDiscountProductNum)(
3025
- targetProduct.discount_list,
3026
- nextNum
3027
- )
3028
- ) : normalizedProduct.discount_list;
3029
3152
  tempOrder.products[matchedIndex] = {
3030
3153
  ...targetProduct,
3031
3154
  ...normalizedProduct,
3032
- discount_list: nextDiscountList,
3155
+ discount_list: normalizedProduct.discount_list,
3033
3156
  metadata: {
3034
3157
  ...targetProduct.metadata || {},
3035
3158
  ...normalizedProduct.metadata || {},
@@ -3040,16 +3163,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
3040
3163
  ...normalizedProduct.product_sku || { option: [] },
3041
3164
  option: (0, import_utils.getProductSkuOptions)(normalizedProduct)
3042
3165
  },
3043
- ...targetIsManualProductDiscount ? {
3044
- selling_price: targetProduct.selling_price,
3045
- original_price: targetProduct.original_price,
3046
- payment_price: targetProduct.payment_price,
3047
- metadata: {
3048
- ...normalizedProduct.metadata || {},
3049
- ...targetProduct.metadata || {},
3050
- unique_identification_number: targetUid
3051
- }
3052
- } : {},
3053
3166
  note: targetProduct.note,
3054
3167
  order_detail_id: targetProduct.order_detail_id,
3055
3168
  num: nextNum
@@ -3522,6 +3635,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
3522
3635
  product_bundle
3523
3636
  } = params;
3524
3637
  const tempOrder = this.ensureTempOrder();
3638
+ this.repairDuplicateProductLineIdentities(tempOrder);
3525
3639
  const identityLookup = {
3526
3640
  product_id,
3527
3641
  product_variant_id
@@ -3632,6 +3746,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
3632
3746
  async removeProductsFromOrder(identities, options) {
3633
3747
  var _a, _b, _c, _d;
3634
3748
  const tempOrder = this.ensureTempOrder();
3749
+ this.repairDuplicateProductLineIdentities(tempOrder);
3635
3750
  if (!identities.length) {
3636
3751
  return tempOrder.products;
3637
3752
  }
@@ -4368,24 +4483,39 @@ var OrderModule = class extends import_BaseModule.BaseModule {
4368
4483
  * 例如数组字段、商品行唯一值、价格 metadata、summary 派生状态和 _extend。
4369
4484
  */
4370
4485
  async hydrateTempOrderFromRecord(record, options) {
4371
- var _a, _b, _c;
4486
+ var _a, _b, _c, _d, _e, _f;
4372
4487
  const sourceRaw = record && typeof record === "object" && record.data && record.order_id == null ? record.data : record || {};
4488
+ const duplicateProductIdentityRepair = (0, import_orderCollectionIdentity.repairDuplicateProductLineIdentities)(
4489
+ Array.isArray(sourceRaw.products) ? sourceRaw.products : [],
4490
+ (_a = sourceRaw._extend) == null ? void 0 : _a.productsByUid
4491
+ );
4492
+ const duplicateIdentityReadySource = duplicateProductIdentityRepair.repairs.length ? {
4493
+ ...sourceRaw,
4494
+ products: duplicateProductIdentityRepair.products,
4495
+ ...duplicateProductIdentityRepair.productsByUid ? {
4496
+ _extend: {
4497
+ ...sourceRaw._extend || {},
4498
+ productsByUid: duplicateProductIdentityRepair.productsByUid
4499
+ }
4500
+ } : {}
4501
+ } : sourceRaw;
4373
4502
  const identityReadySource = this.seedAnonymousBookingUidsFromProducts(
4374
- sourceRaw
4503
+ duplicateIdentityReadySource
4375
4504
  );
4376
4505
  const normalizedCollections = (0, import_orderCollectionIdentity.normalizeOrderCollections)(identityReadySource);
4377
4506
  const raw = normalizedCollections.order;
4378
- const hasIdentityChanges = Object.values(normalizedCollections.stats).some(
4507
+ const hasIdentityChanges = duplicateProductIdentityRepair.repairs.length > 0 || Object.values(normalizedCollections.stats).some(
4379
4508
  (stats) => stats.backfilledUidCount > 0 || stats.collapsedDuplicateCount > 0 || stats.uidConflictCount > 0 || stats.anonymousRowCount > 0
4380
4509
  );
4381
4510
  if (hasIdentityChanges) {
4382
4511
  const identityLogMetadata = {
4383
4512
  collections: normalizedCollections.stats,
4384
- uidRemapCount: normalizedCollections.uidRemaps.length
4513
+ uidRemapCount: normalizedCollections.uidRemaps.length,
4514
+ duplicateProductUidRepairCount: duplicateProductIdentityRepair.repairs.length
4385
4515
  };
4386
4516
  const hasIdentityWarnings = Object.values(normalizedCollections.stats).some(
4387
4517
  (stats) => stats.uidConflictCount > 0 || stats.anonymousRowCount > 0
4388
- );
4518
+ ) || duplicateProductIdentityRepair.repairs.length > 0;
4389
4519
  if (hasIdentityWarnings) {
4390
4520
  this.logWarning(
4391
4521
  "Normalized hydrated order collection identities",
@@ -4398,8 +4528,37 @@ var OrderModule = class extends import_BaseModule.BaseModule {
4398
4528
  );
4399
4529
  }
4400
4530
  }
4401
- const nextTempOrder = this.normalizeTempOrderForRuntime(raw, { makeEditMark: true });
4402
- const sourceLastOrderInfo = raw.lastOrderInfo || raw;
4531
+ const isSessionStorageHydrate = (options == null ? void 0 : options.source) === "sessionStorage";
4532
+ const nextTempOrder = this.normalizeTempOrderForRuntime(raw, {
4533
+ makeEditMark: !isSessionStorageHydrate
4534
+ });
4535
+ this.store.tempOrder = nextTempOrder;
4536
+ if (isSessionStorageHydrate) {
4537
+ const restoredSessionDiscounts = Array.isArray(raw.discount_list) ? (0, import_lodash_es.cloneDeep)(raw.discount_list) : [];
4538
+ const nextExtend = { ...nextTempOrder._extend || {} };
4539
+ delete nextExtend.originalSalesSnapshot;
4540
+ nextTempOrder._extend = nextExtend;
4541
+ nextTempOrder.products.forEach((product) => {
4542
+ if (product.metadata) {
4543
+ delete product.metadata.is_edit_for_runtime;
4544
+ }
4545
+ });
4546
+ nextTempOrder.discount_list = restoredSessionDiscounts;
4547
+ this.store.summary = (0, import_utils.createEmptySummary)();
4548
+ this.store.lastOrderInfo = void 0;
4549
+ await ((_b = this.store.discount) == null ? void 0 : _b.setOriginalDiscountList(
4550
+ (0, import_lodash_es.cloneDeep)(restoredSessionDiscounts)
4551
+ ));
4552
+ await ((_c = this.store.discount) == null ? void 0 : _c.setDiscountList(
4553
+ (0, import_lodash_es.cloneDeep)(restoredSessionDiscounts)
4554
+ ));
4555
+ if (options == null ? void 0 : options.recalcOnHydrate) {
4556
+ await this.recalculateSummary({ createIfMissing: false });
4557
+ }
4558
+ this.persistTempOrder();
4559
+ return nextTempOrder;
4560
+ }
4561
+ const sourceLastOrderInfo = sourceRaw.lastOrderInfo || sourceRaw;
4403
4562
  const isDraftOrder = Number(nextTempOrder.is_draft_order || 0) === 1;
4404
4563
  const syntheticIdentityOptions = {
4405
4564
  isDraftOrder,
@@ -4407,7 +4566,6 @@ var OrderModule = class extends import_BaseModule.BaseModule {
4407
4566
  };
4408
4567
  const rawSummary = raw.summary && typeof raw.summary === "object" ? raw.summary : {};
4409
4568
  const summarySurcharges = Array.isArray(rawSummary.surcharges) ? rawSummary.surcharges.map((s) => ({ ...s || {} })) : (0, import_lodash_es.cloneDeep)(nextTempOrder.surcharges || []);
4410
- this.store.tempOrder = nextTempOrder;
4411
4569
  this.store.summary = {
4412
4570
  ...(0, import_utils.createEmptySummary)(),
4413
4571
  ...rawSummary,
@@ -4434,7 +4592,7 @@ var OrderModule = class extends import_BaseModule.BaseModule {
4434
4592
  this.store.syncState = "local";
4435
4593
  }
4436
4594
  const hydratedEditDiscounts = this.collectHydratedEditDiscounts(nextTempOrder.products);
4437
- const currentDiscounts = typeof ((_a = this.store.discount) == null ? void 0 : _a.getDiscountList) === "function" ? this.store.discount.getDiscountList() || [] : [];
4595
+ const currentDiscounts = typeof ((_d = this.store.discount) == null ? void 0 : _d.getDiscountList) === "function" ? this.store.discount.getDiscountList() || [] : [];
4438
4596
  const currentScanDiscounts = currentDiscounts.filter((discount) => discount == null ? void 0 : discount.isScan);
4439
4597
  const hydratedDiscountIds = new Set(
4440
4598
  hydratedEditDiscounts.map((discount) => discount == null ? void 0 : discount.id)
@@ -4452,8 +4610,8 @@ var OrderModule = class extends import_BaseModule.BaseModule {
4452
4610
  nextTempOrder.products,
4453
4611
  nextDiscounts
4454
4612
  );
4455
- await ((_b = this.store.discount) == null ? void 0 : _b.setOriginalDiscountList(nextDiscounts));
4456
- await ((_c = this.store.discount) == null ? void 0 : _c.setDiscountList(nextDiscounts));
4613
+ await ((_e = this.store.discount) == null ? void 0 : _e.setOriginalDiscountList(nextDiscounts));
4614
+ await ((_f = this.store.discount) == null ? void 0 : _f.setDiscountList(nextDiscounts));
4457
4615
  }
4458
4616
  if (options == null ? void 0 : options.recalcOnHydrate) {
4459
4617
  await this.recalculateSummary({ createIfMissing: false });
@@ -579,6 +579,10 @@ export interface LoadSalesDetailParams {
579
579
  * 存在时 loadSalesDetail 会跳过远端 lookup,直接复用 hydrate 后半段逻辑。
580
580
  */
581
581
  preloadedSalesDetail?: Record<string, any>;
582
+ /**
583
+ * sessionStorage 快照复用详情 hydrate 时使用;不带该值时保持既有编辑订单语义。
584
+ */
585
+ hydrateSource?: 'sessionStorage';
582
586
  forceRemote?: boolean;
583
587
  merge?: boolean;
584
588
  }
@@ -749,8 +753,15 @@ export interface OrderModuleAPI {
749
753
  removeProductFromOrder: (identity: OrderProductIdentity, options?: RemoveProductsFromOrderOptions) => Promise<OrderProduct[]>;
750
754
  /** 批量删除购物车行,末尾仅触发一次促销重算与 summary 刷新 */
751
755
  removeProductsFromOrder: (identities: OrderProductIdentity[], options?: RemoveProductsFromOrderOptions) => Promise<OrderProduct[]>;
752
- /** @deprecated no-op;OrderModule 不再负责 tempOrder localStorage 持久化。 */
756
+ /** 在显式开启时保存当前 tempOrder sessionStorage 会话快照。 */
753
757
  persistTempOrder: () => void;
758
+ /**
759
+ * 删除当前 cacheId 的 tempOrder 会话快照,并阻止当前模块实例再次写入。
760
+ * 新模块实例初始化时会按 otherParams 重新启用持久化。
761
+ */
762
+ clearSessionStoragePersistedTempOrder: () => void;
763
+ /** 取出一次待恢复的 sessionStorage tempOrder 快照。 */
764
+ consumeSessionStorageRestore: () => Record<string, any> | null;
754
765
  /**
755
766
  * 控制 IndexedDB 草稿(saveDraft)开关;localStorage tempOrder 持久化已废弃。
756
767
  * BigSale 弹窗等场景传 false 可跳过 saveDraft。
@@ -808,6 +819,7 @@ export interface OrderModuleAPI {
808
819
  saveDraft: () => Promise<void>;
809
820
  hydrateTempOrderFromRecord: (record: Record<string, any>, options?: {
810
821
  recalcOnHydrate?: boolean;
822
+ source?: 'salesDetail' | 'sessionStorage';
811
823
  }) => Promise<OrderTempOrder>;
812
824
  syncPaymentsToOrder: <T = any>(params: SyncPaymentsToOrderParams) => Promise<SyncPaymentsToOrderResult<T>>;
813
825
  getSalesOrderByLookup: (params: SalesOrderLookupParams) => Promise<any>;
@@ -24,6 +24,16 @@ export interface NormalizeOrderCollectionsResult<T> {
24
24
  stats: Record<OrderCollectionKind, OrderCollectionIdentityStats>;
25
25
  uidRemaps: OrderCollectionUidRemap[];
26
26
  }
27
+ export interface ProductLineIdentityRepair {
28
+ index: number;
29
+ from: string;
30
+ to: string;
31
+ }
32
+ export interface RepairDuplicateProductLineIdentitiesResult<T> {
33
+ products: T[];
34
+ productsByUid?: Record<string, Record<string, any>>;
35
+ repairs: ProductLineIdentityRepair[];
36
+ }
27
37
  export declare function createUuidV4(): string;
28
38
  export declare function isBlankOrderCollectionIdentity(value: unknown): boolean;
29
39
  export declare function getOrderCollectionPersistentId(kind: OrderCollectionKind, item: unknown): string | null;
@@ -31,6 +41,11 @@ export declare function getOrderCollectionUid(kind: OrderCollectionKind, item: u
31
41
  export declare function buildOrderCollectionFallbackUid(kind: OrderCollectionKind, persistentId: string | number): string;
32
42
  export declare function getOrderCollectionReferenceUid(kind: OrderCollectionKind, item: unknown): string | null;
33
43
  export declare function setOrderCollectionUid<T>(kind: OrderCollectionKind, item: T, uid: string): T;
44
+ /**
45
+ * Rules 会把同一商品按商品券拆成多条真实订单行。拆行结果会继承原商品 UID,
46
+ * 因此必须在通用 collection normalization 之前为后续行派生稳定身份,避免被折叠。
47
+ */
48
+ export declare function repairDuplicateProductLineIdentities<T extends Record<string, any>>(products: T[] | null | undefined, productsByUid?: Record<string, Record<string, any>> | null): RepairDuplicateProductLineIdentitiesResult<T>;
34
49
  /**
35
50
  * 持久化行 ID 是服务端事实主键;只有至少一侧尚未持久化时,才允许用 runtime UID 对齐。
36
51
  */
@@ -30,6 +30,7 @@ __export(orderCollectionIdentity_exports, {
30
30
  mergeOrderCollectionItems: () => mergeOrderCollectionItems,
31
31
  normalizeOrderCollection: () => normalizeOrderCollection,
32
32
  normalizeOrderCollections: () => normalizeOrderCollections,
33
+ repairDuplicateProductLineIdentities: () => repairDuplicateProductLineIdentities,
33
34
  setOrderCollectionUid: () => setOrderCollectionUid
34
35
  });
35
36
  module.exports = __toCommonJS(orderCollectionIdentity_exports);
@@ -105,6 +106,74 @@ function setOrderCollectionUid(kind, item, uid) {
105
106
  delete record[uidField];
106
107
  return record;
107
108
  }
109
+ function getGoodPassResourceId(product) {
110
+ var _a;
111
+ const goodPass = (product.discount_list || []).find((item) => {
112
+ var _a2, _b;
113
+ const type = (item == null ? void 0 : item.type) || (item == null ? void 0 : item.tag) || ((_a2 = item == null ? void 0 : item.discount) == null ? void 0 : _a2.type) || ((_b = item == null ? void 0 : item.discount) == null ? void 0 : _b.tag);
114
+ return type === "good_pass";
115
+ });
116
+ if (!goodPass)
117
+ return null;
118
+ const resourceId = ((_a = goodPass.discount) == null ? void 0 : _a.resource_id) ?? goodPass.resource_id ?? goodPass.discount_id ?? goodPass.id;
119
+ return isBlankOrderCollectionIdentity(resourceId) ? null : String(resourceId);
120
+ }
121
+ function buildSplitProductUid(originalUid, product, duplicateOrdinal) {
122
+ const encodedOriginalUid = encodeURIComponent(originalUid);
123
+ const resourceId = getGoodPassResourceId(product);
124
+ if (resourceId) {
125
+ return `os-split:product:${encodedOriginalUid}:good-pass:${encodeURIComponent(resourceId)}:${duplicateOrdinal}`;
126
+ }
127
+ return `os-split:product:${encodedOriginalUid}:duplicate:${duplicateOrdinal}`;
128
+ }
129
+ function repairDuplicateProductLineIdentities(products, productsByUid) {
130
+ const repairedProducts = (0, import_lodash_es.cloneDeep)(products || []);
131
+ const repairedProductsByUid = productsByUid ? (0, import_lodash_es.cloneDeep)(productsByUid) : void 0;
132
+ const occupiedUids = new Set(
133
+ repairedProducts.map((product) => getOrderCollectionUid("product", product)).filter((uid) => Boolean(uid))
134
+ );
135
+ Object.keys(repairedProductsByUid || {}).forEach((uid) => occupiedUids.add(uid));
136
+ const occurrences = /* @__PURE__ */ new Map();
137
+ const repairs = [];
138
+ repairedProducts.forEach((product, index) => {
139
+ const originalUid = getOrderCollectionUid("product", product);
140
+ if (!originalUid)
141
+ return;
142
+ const occurrence = (occurrences.get(originalUid) || 0) + 1;
143
+ occurrences.set(originalUid, occurrence);
144
+ if (occurrence === 1)
145
+ return;
146
+ const duplicateOrdinal = occurrence - 1;
147
+ const baseCandidate = buildSplitProductUid(
148
+ originalUid,
149
+ product,
150
+ duplicateOrdinal
151
+ );
152
+ let nextUid = baseCandidate;
153
+ let conflictOrdinal = 1;
154
+ while (occupiedUids.has(nextUid)) {
155
+ nextUid = `${baseCandidate}:conflict:${conflictOrdinal}`;
156
+ conflictOrdinal += 1;
157
+ }
158
+ const repairedProduct = setOrderCollectionUid("product", product, nextUid);
159
+ repairedProduct.identity_key = nextUid;
160
+ repairedProducts[index] = repairedProduct;
161
+ occupiedUids.add(nextUid);
162
+ repairs.push({ index, from: originalUid, to: nextUid });
163
+ const runtimeSource = repairedProductsByUid == null ? void 0 : repairedProductsByUid[originalUid];
164
+ if (runtimeSource && repairedProductsByUid) {
165
+ repairedProductsByUid[nextUid] = {
166
+ ...(0, import_lodash_es.cloneDeep)(runtimeSource),
167
+ ...runtimeSource.rowKey !== void 0 ? { rowKey: nextUid } : {}
168
+ };
169
+ }
170
+ });
171
+ return {
172
+ products: repairedProducts,
173
+ ...repairedProductsByUid ? { productsByUid: repairedProductsByUid } : {},
174
+ repairs
175
+ };
176
+ }
108
177
  function isSameOrderCollectionItem(kind, left, right) {
109
178
  const leftPersistentId = getOrderCollectionPersistentId(kind, left);
110
179
  const rightPersistentId = getOrderCollectionPersistentId(kind, right);
@@ -414,5 +483,6 @@ function normalizeOrderCollections(sourceOrder, options) {
414
483
  mergeOrderCollectionItems,
415
484
  normalizeOrderCollection,
416
485
  normalizeOrderCollections,
486
+ repairDuplicateProductLineIdentities,
417
487
  setOrderCollectionUid
418
488
  });