@pisell/pisellos 0.10.43 → 0.10.45

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 (118) hide show
  1. package/dist/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  2. package/dist/model/strategy/adapter/itemRule/adapter.js +135 -35
  3. package/dist/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  4. package/dist/model/strategy/adapter/itemRule/evaluator.js +25 -1
  5. package/dist/model/strategy/adapter/itemRule/type.d.ts +44 -0
  6. package/dist/model/strategy/adapter/itemRule/type.js +19 -1
  7. package/dist/model/strategy/adapter/promotion/index.js +9 -0
  8. package/dist/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  9. package/dist/model/strategy/adapter/walletPass/evaluator.js +4 -2
  10. package/dist/modules/Customer/index.js +2 -1
  11. package/dist/modules/Discount/availability.d.ts +7 -0
  12. package/dist/modules/Discount/availability.js +49 -0
  13. package/dist/modules/Discount/index.d.ts +1 -3
  14. package/dist/modules/Discount/index.js +12 -151
  15. package/dist/modules/Discount/types.d.ts +6 -0
  16. package/dist/modules/Order/index.d.ts +41 -9
  17. package/dist/modules/Order/index.js +1457 -872
  18. package/dist/modules/Order/salesNotes.d.ts +31 -0
  19. package/dist/modules/Order/salesNotes.js +492 -0
  20. package/dist/modules/Order/types.d.ts +105 -8
  21. package/dist/modules/Order/types.js +6 -1
  22. package/dist/modules/Order/utils.d.ts +4 -2
  23. package/dist/modules/Order/utils.js +224 -79
  24. package/dist/modules/Payment/cashRecommendationAlgorithm.d.ts +4 -5
  25. package/dist/modules/Payment/cashRecommendationAlgorithm.js +17 -367
  26. package/dist/modules/Product/index.d.ts +1 -1
  27. package/dist/modules/ProductList/index.d.ts +2 -1
  28. package/dist/modules/ProductList/index.js +100 -20
  29. package/dist/modules/ProductList/types.d.ts +10 -0
  30. package/dist/modules/Rules/index.js +50 -16
  31. package/dist/server/index.d.ts +7 -0
  32. package/dist/server/index.js +45 -3
  33. package/dist/server/modules/order/index.js +6 -0
  34. package/dist/server/modules/order/types.d.ts +9 -0
  35. package/dist/server/utils/small-ticket.d.ts +1 -0
  36. package/dist/server/utils/small-ticket.js +142 -14
  37. package/dist/solution/BaseSales/index.d.ts +15 -1
  38. package/dist/solution/BaseSales/index.js +1240 -877
  39. package/dist/solution/BaseSales/types.d.ts +2 -2
  40. package/dist/solution/BookingByStep/index.d.ts +1 -1
  41. package/dist/solution/BookingTicket/index.d.ts +1 -0
  42. package/dist/solution/BookingTicket/index.js +37 -16
  43. package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
  44. package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
  45. package/dist/solution/RegisterAndLogin/config.js +95 -40
  46. package/dist/solution/RegisterAndLogin/index.js +4 -1
  47. package/dist/solution/ScanOrder/index.d.ts +1 -0
  48. package/dist/solution/ScanOrder/index.js +31 -27
  49. package/dist/solution/ScanOrder/utils.d.ts +1 -0
  50. package/dist/solution/ScanOrder/utils.js +2 -0
  51. package/dist/solution/ShopDiscount/index.d.ts +1 -0
  52. package/dist/solution/ShopDiscount/index.js +59 -22
  53. package/dist/solution/ShopDiscount/utils.d.ts +4 -2
  54. package/dist/solution/ShopDiscount/utils.js +40 -21
  55. package/dist/solution/UnifiedBookingSales/index.d.ts +15 -2
  56. package/dist/solution/UnifiedBookingSales/index.js +777 -510
  57. package/dist/solution/UnifiedBookingSales/types.d.ts +14 -1
  58. package/dist/solution/VenueBooking/index.d.ts +1 -0
  59. package/dist/solution/VenueBooking/index.js +8 -4
  60. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  61. package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
  62. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  63. package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
  64. package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
  65. package/lib/model/strategy/adapter/itemRule/type.js +19 -1
  66. package/lib/model/strategy/adapter/walletPass/evaluator.d.ts +2 -0
  67. package/lib/model/strategy/adapter/walletPass/evaluator.js +2 -0
  68. package/lib/modules/Customer/index.js +1 -0
  69. package/lib/modules/Discount/availability.d.ts +7 -0
  70. package/lib/modules/Discount/availability.js +43 -0
  71. package/lib/modules/Discount/index.d.ts +1 -3
  72. package/lib/modules/Discount/index.js +10 -131
  73. package/lib/modules/Discount/types.d.ts +6 -0
  74. package/lib/modules/Order/index.d.ts +41 -9
  75. package/lib/modules/Order/index.js +456 -69
  76. package/lib/modules/Order/salesNotes.d.ts +31 -0
  77. package/lib/modules/Order/salesNotes.js +382 -0
  78. package/lib/modules/Order/types.d.ts +105 -8
  79. package/lib/modules/Order/types.js +5 -1
  80. package/lib/modules/Order/utils.d.ts +4 -2
  81. package/lib/modules/Order/utils.js +134 -3
  82. package/lib/modules/Payment/cashRecommendationAlgorithm.d.ts +4 -5
  83. package/lib/modules/Payment/cashRecommendationAlgorithm.js +9 -354
  84. package/lib/modules/Product/index.d.ts +1 -1
  85. package/lib/modules/ProductList/index.d.ts +2 -1
  86. package/lib/modules/ProductList/index.js +47 -2
  87. package/lib/modules/ProductList/types.d.ts +10 -0
  88. package/lib/modules/Rules/index.js +42 -5
  89. package/lib/server/index.d.ts +7 -0
  90. package/lib/server/index.js +39 -3
  91. package/lib/server/modules/order/index.js +6 -0
  92. package/lib/server/modules/order/types.d.ts +9 -0
  93. package/lib/server/utils/small-ticket.d.ts +1 -0
  94. package/lib/server/utils/small-ticket.js +136 -12
  95. package/lib/solution/BaseSales/index.d.ts +15 -1
  96. package/lib/solution/BaseSales/index.js +192 -22
  97. package/lib/solution/BaseSales/types.d.ts +2 -2
  98. package/lib/solution/BookingByStep/index.d.ts +1 -1
  99. package/lib/solution/BookingTicket/index.d.ts +1 -0
  100. package/lib/solution/BookingTicket/index.js +23 -5
  101. package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
  102. package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
  103. package/lib/solution/RegisterAndLogin/config.js +49 -8
  104. package/lib/solution/RegisterAndLogin/index.js +4 -1
  105. package/lib/solution/ScanOrder/index.d.ts +1 -0
  106. package/lib/solution/ScanOrder/index.js +5 -1
  107. package/lib/solution/ScanOrder/utils.d.ts +1 -0
  108. package/lib/solution/ScanOrder/utils.js +1 -0
  109. package/lib/solution/ShopDiscount/index.d.ts +1 -0
  110. package/lib/solution/ShopDiscount/index.js +29 -3
  111. package/lib/solution/ShopDiscount/utils.d.ts +4 -2
  112. package/lib/solution/ShopDiscount/utils.js +39 -23
  113. package/lib/solution/UnifiedBookingSales/index.d.ts +15 -2
  114. package/lib/solution/UnifiedBookingSales/index.js +125 -1
  115. package/lib/solution/UnifiedBookingSales/types.d.ts +14 -1
  116. package/lib/solution/VenueBooking/index.d.ts +1 -0
  117. package/lib/solution/VenueBooking/index.js +5 -1
  118. package/package.json +1 -1
@@ -9,7 +9,9 @@ var _types = require("./types");
9
9
  var _utils = require("./utils");
10
10
  var _utils2 = require("../Product/utils");
11
11
  var _dayjs = _interopRequireDefault(require("dayjs"));
12
+ var _availability = require("../Discount/availability");
12
13
  var _osPrivatePayload = require("../../utils/os-private-payload");
14
+ var _salesNotes = require("./salesNotes");
13
15
  var _paymentUtils = require("./payment-utils");
14
16
  var _paymentNumber = require("../../utils/payment-number");
15
17
  var _cartPromotion = require("../../solution/BaseSales/utils/cartPromotion");
@@ -126,6 +128,8 @@ class OrderModule extends _BaseModule.BaseModule {
126
128
  openCache = false;
127
129
  sessionStoragePersistEnabled = false;
128
130
  sessionStorageRestoreRecord = null;
131
+ /** 只允许当前订单最新的 prepare/config 请求回写客户资产态。 */
132
+ customerWalletPrepareConfigEpoch = 0;
129
133
  salesSummaryModuleName;
130
134
  rulesHooksOverride;
131
135
  moduleHooksOverride;
@@ -136,8 +140,10 @@ class OrderModule extends _BaseModule.BaseModule {
136
140
  promotionEvaluator = null;
137
141
  /** 赠品选择 resolver;由 SDK host bridge 注入 */
138
142
  giftSelectResolver = null;
139
- /** applyPromotion 递归保护 flag(写路径同步调 applyPromotion,禁止重入) */
143
+ /** 促销评估递归保护 flag(写路径同步评估促销,禁止重入) */
140
144
  isApplyingPromotion = false;
145
+ /** 保持旧事件语义:同步事件监听器内触发的订单写入不得递归评估促销。 */
146
+ isEmittingPromotionApplied = false;
141
147
  /** 商品曾应用过客户券卡,客户变更剥离后需强制跑一次 calcDiscount 复位价格 */
142
148
  hasActiveCustomerBoundDiscount = false;
143
149
  /** 最近一次 applyPromotion 的未满足促销提示 */
@@ -491,13 +497,24 @@ class OrderModule extends _BaseModule.BaseModule {
491
497
  this.store.rules = rules;
492
498
  }
493
499
  createDefaultRulesHooks() {
494
- return (0, _utils.createDefaultOrderRulesHooks)();
500
+ return (0, _utils.createDefaultOrderRulesHooks)(() => this.store.tempOrder?.bookings || []);
501
+ }
502
+
503
+ /**
504
+ * Stable across notifyTempOrderChanged's immutable copy, but changes when a
505
+ * different local/server order becomes current.
506
+ */
507
+ getDiscountConfigOrderIdentityKey(tempOrder) {
508
+ const identity = this.getOrderIdentity();
509
+ return JSON.stringify([identity?.orderId ?? tempOrder?.order_id ?? '', tempOrder?.request_unique_idempotency_token ?? '', identity?.externalSaleNumber ?? tempOrder?.external_sale_number ?? '', identity?.localId ?? tempOrder?._extend?.localId ?? this.cacheId ?? '']);
495
510
  }
496
511
 
497
512
  // ─── Discount: 公共 API ───
498
513
 
499
514
  async loadDiscountConfig(params) {
500
515
  const orderId = params.orderId || this.store.tempOrder?.order_id || undefined;
516
+ const requestEpoch = ++this.customerWalletPrepareConfigEpoch;
517
+ const requestOrderIdentityKey = this.getDiscountConfigOrderIdentityKey(this.store.tempOrder);
501
518
  const prepareConfigResult = await this.store.discount?.loadPrepareConfig({
502
519
  customer_id: params.customerId,
503
520
  action: params.action || (orderId ? 'edit' : 'create'),
@@ -508,18 +525,23 @@ class OrderModule extends _BaseModule.BaseModule {
508
525
  with_available_wallet_pass_flag: 1,
509
526
  with_virtual_currency: 1,
510
527
  with_entitlement_pass: 1,
528
+ filter_order_wallet_pass_usage: 0,
511
529
  request_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
512
530
  ...(orderId ? {
513
531
  order_id: orderId
514
532
  } : {})
515
533
  });
534
+ const isCurrentRequest = () => requestEpoch === this.customerWalletPrepareConfigEpoch && this.getDiscountConfigOrderIdentityKey(this.store.tempOrder) === requestOrderIdentityKey && String(this.store.tempOrder?.customer_id ?? '') === String(params.customerId);
535
+ if (!isCurrentRequest()) {
536
+ return this.getDiscountList();
537
+ }
516
538
  const discountList = prepareConfigResult?.discountList;
517
- this.store.availableWalletIds = prepareConfigResult?.availableWalletIds || [];
518
- this.store.virtualCurrencyList = prepareConfigResult?.virtualCurrencyList || [];
519
539
  if (prepareConfigResult) {
520
- this.writePointCardSnapshotFromPrepareConfig({
540
+ this.writeCustomerWalletSnapshotsFromPrepareConfig({
521
541
  customerId: params.customerId,
522
- availableWalletPassList: prepareConfigResult.availableWalletPassList || []
542
+ availableWalletIds: prepareConfigResult.availableWalletIds || [],
543
+ availableWalletPassList: prepareConfigResult.availableWalletPassList || [],
544
+ virtualCurrencyList: prepareConfigResult.virtualCurrencyList || []
523
545
  });
524
546
  }
525
547
  if (discountList) {
@@ -579,9 +601,12 @@ class OrderModule extends _BaseModule.BaseModule {
579
601
  return id === null || !refreshedScanDiscountIds.has(id);
580
602
  });
581
603
  const mergedDiscountList = [...retainedCommittedDiscounts, ...retainedScanDiscounts, ...runtimeDiscounts];
604
+ if (!isCurrentRequest()) return this.getDiscountList();
582
605
  await this.store.discount?.setOriginalDiscountList(mergedDiscountList);
606
+ if (!isCurrentRequest()) return this.getDiscountList();
583
607
  await this.store.discount?.setDiscountList(mergedDiscountList);
584
608
  }
609
+ if (!isCurrentRequest()) return this.getDiscountList();
585
610
  if (params.apply !== false && this.store.tempOrder?.products?.length) {
586
611
  this.applyDiscount();
587
612
  }
@@ -596,6 +621,9 @@ class OrderModule extends _BaseModule.BaseModule {
596
621
  getVirtualCurrencyList() {
597
622
  return (0, _lodashEs.cloneDeep)(this.store.virtualCurrencyList || []);
598
623
  }
624
+ getCustomerWalletPrepareConfigEpoch() {
625
+ return this.customerWalletPrepareConfigEpoch;
626
+ }
599
627
  async syncScannedDiscountsToOriginalDiscountList(discountList) {
600
628
  const scanDiscounts = (discountList || []).filter(discount => discount?.isScan);
601
629
  if (!scanDiscounts.length) return;
@@ -693,19 +721,31 @@ class OrderModule extends _BaseModule.BaseModule {
693
721
  isAvailable: false,
694
722
  discountList: this.getDiscountList()
695
723
  };
696
- const shouldRetainForLater = !isAvailable && unavailableReason === _types2.UnavailableReason.ProductNotApplicable && withScanList.some(isCustomerPromotionDiscount);
724
+ const scannedIds = new Set(withScanList.map(discount => String(discount.id)));
725
+ const evaluatedScannedDiscounts = (evaluatedDiscountList || withScanList).filter(discount => scannedIds.has(String(discount.id)) && isCustomerPromotionDiscount(discount) && !(0, _availability.getDiscountLifecycleReason)(discount));
726
+ const shouldRetainForLater = !isAvailable && evaluatedScannedDiscounts.length > 0;
697
727
  const normalizedNewDiscountList = newDiscountList ? markScannedDiscounts(newDiscountList, withScanList) : undefined;
698
- const retainedDiscountList = evaluatedDiscountList ? markScannedDiscounts(evaluatedDiscountList, withScanList) : normalizedNewDiscountList;
699
728
  let resolvedDiscountList = normalizedNewDiscountList || this.getDiscountList();
700
729
  if (isAvailable && normalizedNewDiscountList) {
701
730
  await this.store.discount?.setDiscountList(normalizedNewDiscountList);
702
731
  this.applyDiscount();
703
732
  await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
704
733
  resolvedDiscountList = this.getDiscountList();
705
- } else if (shouldRetainForLater && retainedDiscountList) {
706
- // 卡券有效但当前购物车没有适用商品:先保留在 Promotion 中,
707
- // 不执行 applyDiscount,避免修改商品价格或订单金额。
708
- await this.store.discount?.setDiscountList(retainedDiscountList);
734
+ } else if (shouldRetainForLater) {
735
+ // 只合并本次识别的卡券;不把试算产生的其他选券状态写回订单。
736
+ const retainedDiscounts = new Map(this.getDiscountList().map(discount => [String(discount.id), discount]));
737
+ evaluatedScannedDiscounts.forEach(discount => {
738
+ retainedDiscounts.set(String(discount.id), {
739
+ ...discount,
740
+ isScan: true,
741
+ isAvailable: false,
742
+ isSelected: false,
743
+ reasonCode: discount.reasonCode || 'product_not_applicable',
744
+ appliedProductDetails: [],
745
+ savedAmount: 0
746
+ });
747
+ });
748
+ await this.store.discount?.setDiscountList([...retainedDiscounts.values()]);
709
749
  await this.syncScannedDiscountsToOriginalDiscountList(this.getDiscountList());
710
750
  resolvedDiscountList = this.getDiscountList();
711
751
  }
@@ -714,7 +754,7 @@ class OrderModule extends _BaseModule.BaseModule {
714
754
  isAvailable: isAvailable || false,
715
755
  isAccepted: Boolean(isAvailable || shouldRetainForLater),
716
756
  discountList: resolvedDiscountList,
717
- scannedDiscountList: resultDiscountList,
757
+ scannedDiscountList: evaluatedScannedDiscounts,
718
758
  unavailableReason
719
759
  };
720
760
  }
@@ -808,7 +848,7 @@ class OrderModule extends _BaseModule.BaseModule {
808
848
  }
809
849
 
810
850
  /**
811
- * 应用购物车促销:计算 → 差异化赠品 → 写回 tempOrder.products → emit onPromotionApplied。
851
+ * 只执行购物车促销评估与赠品写回,不发布稳定态事件。
812
852
  *
813
853
  * 调用时机:写路径(add/update/remove/clear/setCustomer)末尾自动触发;外部一般无需手动调。
814
854
  *
@@ -816,26 +856,27 @@ class OrderModule extends _BaseModule.BaseModule {
816
856
  * - 不调用任何公开 CRUD API(如 removeProductFromOrder),全部内部 mutate tempOrder.products,避免事件风暴;
817
857
  * - 用 `isApplyingPromotion` 防递归;
818
858
  * - 多选项赠品依赖 giftSelectResolver,未注入则跳过且 console.warn;
819
- * - 不主动调用 applyDiscount / recalculateSummary,调用方负责跟进。
859
+ * - 不主动调用 applyDiscount / recalculateSummary,调用方负责跟进;
860
+ * - 返回的 payload 只能在 applyDiscount(商品写路径还需 summary/persist)完成后发布。
820
861
  */
821
- async applyPromotion() {
822
- if (this.isApplyingPromotion) {
823
- return;
862
+ async evaluatePromotion() {
863
+ if (this.isApplyingPromotion || this.isEmittingPromotionApplied) {
864
+ return null;
824
865
  }
825
866
  if (!this.promotionEvaluator) {
826
- return;
867
+ return null;
827
868
  }
828
869
  const tempOrder = this.store.tempOrder;
829
870
  if (!tempOrder) {
830
- return;
871
+ return null;
831
872
  }
832
873
  if (this.isActiveVirtualSettlementOrder(tempOrder)) {
833
874
  tempOrder.products = (0, _virtualSettlement.normalizeVirtualSettlementProducts)(tempOrder.products);
834
- return;
875
+ return null;
835
876
  }
836
877
  const strategyConfigs = this.promotionEvaluator.getStrategyConfigs?.();
837
878
  if (Array.isArray(strategyConfigs) && strategyConfigs.length === 0) {
838
- return;
879
+ return null;
839
880
  }
840
881
  this.isApplyingPromotion = true;
841
882
  try {
@@ -927,14 +968,46 @@ class OrderModule extends _BaseModule.BaseModule {
927
968
  })),
928
969
  products: tempOrder.products
929
970
  };
930
- this.effectsEmit('onPromotionApplied', payload);
971
+ return payload;
931
972
  } catch (error) {
932
973
  console.error('[OrderModule] applyPromotion failed', error);
974
+ return null;
933
975
  } finally {
934
976
  this.isApplyingPromotion = false;
935
977
  }
936
978
  }
937
979
 
980
+ /** 发布促销稳定态;products 必须读取折扣计算后的当前数组。 */
981
+ emitPromotionApplied(payload) {
982
+ if (!payload) return;
983
+ const products = this.store.tempOrder?.products || payload.products;
984
+ this.isEmittingPromotionApplied = true;
985
+ try {
986
+ void this.effectsEmit('onPromotionApplied', {
987
+ ...payload,
988
+ products
989
+ });
990
+ } finally {
991
+ // effectsEmit 同步调用监听器后返回 Promise;保持既有的非阻塞事件行为。
992
+ this.isEmittingPromotionApplied = false;
993
+ }
994
+ }
995
+
996
+ /** 公开入口:促销、折扣、summary 与持久化完成后发布稳定态事件。 */
997
+ async applyPromotion() {
998
+ const payload = await this.evaluatePromotion();
999
+ if (!payload) return;
1000
+ const tempOrder = this.store.tempOrder;
1001
+ if (!tempOrder) return;
1002
+ this.applyDiscount();
1003
+ this.sanitizeTempOrderProducts(tempOrder);
1004
+ await this.recalculateSummary({
1005
+ createIfMissing: true
1006
+ });
1007
+ this.persistTempOrder();
1008
+ this.emitPromotionApplied(payload);
1009
+ }
1010
+
938
1011
  /** 最近一次 applyPromotion 输出的未满足促销列表 */
939
1012
  getUnfulfilledPromotions() {
940
1013
  return this.lastUnfulfilledPromotions;
@@ -1038,6 +1111,7 @@ class OrderModule extends _BaseModule.BaseModule {
1038
1111
  this.store.tempOrder = {
1039
1112
  ...this.store.tempOrder,
1040
1113
  products: [...this.store.tempOrder.products],
1114
+ notes: [...(this.store.tempOrder.notes || [])],
1041
1115
  bookings: this.store.tempOrder.bookings ? [...this.store.tempOrder.bookings] : []
1042
1116
  };
1043
1117
  this.persistTempOrder();
@@ -1050,27 +1124,41 @@ class OrderModule extends _BaseModule.BaseModule {
1050
1124
  now: (0, _utils.formatDateTime)(new Date())
1051
1125
  });
1052
1126
  }
1053
- clearTempOrderPointCardSnapshot(tempOrder) {
1127
+ clearTempOrderCustomerWalletSnapshots(tempOrder) {
1054
1128
  const orderMetaList = (0, _utils.normalizeOrderMetaList)(tempOrder.order_meta_list);
1055
1129
  if (!orderMetaList) return;
1056
1130
  const nextOrderMetaList = {
1057
1131
  ...orderMetaList
1058
1132
  };
1059
1133
  delete nextOrderMetaList.point_cards_snapshot;
1134
+ delete nextOrderMetaList.virtual_currency_snapshot;
1060
1135
  if (Object.keys(nextOrderMetaList).length === 0) {
1061
1136
  delete tempOrder.order_meta_list;
1062
1137
  return;
1063
1138
  }
1064
1139
  tempOrder.order_meta_list = nextOrderMetaList;
1065
1140
  }
1066
- writePointCardSnapshotFromPrepareConfig(params) {
1141
+ clearCustomerWalletState(tempOrder) {
1142
+ this.clearCustomerWalletRuntimeState();
1143
+ this.clearTempOrderCustomerWalletSnapshots(tempOrder);
1144
+ }
1145
+ clearCustomerWalletRuntimeState() {
1146
+ this.customerWalletPrepareConfigEpoch += 1;
1147
+ this.store.availableWalletIds = [];
1148
+ this.store.virtualCurrencyList = [];
1149
+ }
1150
+ writeCustomerWalletSnapshotsFromPrepareConfig(params) {
1067
1151
  const tempOrder = this.store.tempOrder;
1068
1152
  if (!tempOrder) return;
1069
1153
  if (String(tempOrder.customer_id ?? '') !== String(params.customerId)) return;
1154
+ const virtualCurrencyList = Array.isArray(params.virtualCurrencyList) ? params.virtualCurrencyList : [];
1070
1155
  tempOrder.order_meta_list = {
1071
1156
  ...((0, _utils.normalizeOrderMetaList)(tempOrder.order_meta_list) || {}),
1072
- point_cards_snapshot: (0, _utils.buildPointCardSnapshotFromWalletPassList)(params.availableWalletPassList)
1157
+ point_cards_snapshot: (0, _utils.buildPointCardSnapshotFromWalletPassList)(params.availableWalletPassList),
1158
+ virtual_currency_snapshot: (0, _utils.buildVirtualCurrencySnapshotFromList)(virtualCurrencyList)
1073
1159
  };
1160
+ this.store.availableWalletIds = [...params.availableWalletIds];
1161
+ this.store.virtualCurrencyList = (0, _lodashEs.cloneDeep)(virtualCurrencyList);
1074
1162
  this.persistTempOrder();
1075
1163
  this.saveDraftInBackground();
1076
1164
  }
@@ -1855,6 +1943,7 @@ class OrderModule extends _BaseModule.BaseModule {
1855
1943
  const freshTempOrder = this.createDefaultTempOrderInstance();
1856
1944
  this.ensureExternalSaleNumber(freshTempOrder);
1857
1945
  this.store.tempOrder = freshTempOrder;
1946
+ this.clearCustomerWalletRuntimeState();
1858
1947
  this.store.summary = (0, _utils.createEmptySummary)();
1859
1948
  this.store.lastOrderInfo = undefined;
1860
1949
  this.store.syncState = undefined;
@@ -1875,6 +1964,7 @@ class OrderModule extends _BaseModule.BaseModule {
1875
1964
  ensureIdentity: false
1876
1965
  });
1877
1966
  this.store.tempOrder = nextTempOrder;
1967
+ this.clearCustomerWalletRuntimeState();
1878
1968
  if (options?.recalculateSummary !== false) {
1879
1969
  await this.recalculateSummary({
1880
1970
  createIfMissing: false
@@ -2398,6 +2488,179 @@ class OrderModule extends _BaseModule.BaseModule {
2398
2488
  getTempOrderNote() {
2399
2489
  return this.store.tempOrder?.note || '';
2400
2490
  }
2491
+ ensureSalesNotesRuntimeState(tempOrder) {
2492
+ const hadNotesSnapshot = Array.isArray(tempOrder.notes);
2493
+ if (!Array.isArray(tempOrder.notes)) tempOrder.notes = [];
2494
+ if (!tempOrder._extend || typeof tempOrder._extend !== 'object') {
2495
+ tempOrder._extend = {
2496
+ productsByUid: {}
2497
+ };
2498
+ }
2499
+ if (!tempOrder._extend.salesNotesState) {
2500
+ tempOrder._extend.salesNotesState = {
2501
+ loaded: hadNotesSnapshot,
2502
+ dirty: false
2503
+ };
2504
+ }
2505
+ return tempOrder._extend.salesNotesState;
2506
+ }
2507
+ ensureSalesNotesLoadedForMutation(tempOrder) {
2508
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2509
+ if (state.loaded) return;
2510
+ if (tempOrder.order_id === null || tempOrder.order_id === undefined) {
2511
+ state.loaded = true;
2512
+ tempOrder.notes = [];
2513
+ return;
2514
+ }
2515
+ throw new Error('[Order Notes] 当前订单 Notes 尚未完整加载,禁止提交部分快照');
2516
+ }
2517
+ normalizeSalesNoteUuid(value, field) {
2518
+ const uuid = String(value ?? '').trim();
2519
+ if (!uuid) throw new Error(`[Order Notes] ${field} 不能为空`);
2520
+ if (uuid.length > 255) {
2521
+ throw new Error(`[Order Notes] ${field} 不能超过 255 个字符`);
2522
+ }
2523
+ return uuid;
2524
+ }
2525
+ getSalesNotePrimaryRelation(note) {
2526
+ return note.note_relations.find(relation => relation.is_primary === 1) || null;
2527
+ }
2528
+ removeSalesNotesForMissingProductLines(tempOrder) {
2529
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2530
+ if (!state.loaded || !Array.isArray(tempOrder.notes) || tempOrder.notes.length === 0) {
2531
+ return;
2532
+ }
2533
+ const remainingProductUids = new Set((tempOrder.products || []).map(product => product.metadata?.unique_identification_number || product.unique_identification_number).filter(uid => typeof uid === 'string' && uid.length > 0));
2534
+ const nextNotes = tempOrder.notes.filter(note => {
2535
+ const primary = this.getSalesNotePrimaryRelation(note);
2536
+ if (!primary || primary.target_type !== 'order_detail') return true;
2537
+ return remainingProductUids.has(primary.target_uuid);
2538
+ });
2539
+ if (nextNotes.length === tempOrder.notes.length) return;
2540
+ tempOrder.notes = nextNotes;
2541
+ state.dirty = true;
2542
+ }
2543
+ setSalesNoteOnTempOrder(tempOrder, input) {
2544
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
2545
+ const note_type = String(input.note_type || '').trim();
2546
+ const policy = (0, _salesNotes.getSalesNotePolicy)(note_type);
2547
+ if (!policy) {
2548
+ throw new Error(`[Order Notes] 当前未注册备注类型: ${note_type || '(empty)'}`);
2549
+ }
2550
+ if (input.target?.target_type !== 'order_detail') {
2551
+ throw new Error('[Order Notes] 本期只支持 order_detail 作为主体');
2552
+ }
2553
+ const target_uuid = this.normalizeSalesNoteUuid(input.target.target_uuid, 'target_uuid');
2554
+ const targetProductExists = (tempOrder.products || []).some(product => {
2555
+ const productLineUid = product.metadata?.unique_identification_number || product.unique_identification_number;
2556
+ return String(productLineUid ?? '') === target_uuid;
2557
+ });
2558
+ if (!targetProductExists) {
2559
+ throw new Error('[Order Notes] 目标商品行不存在');
2560
+ }
2561
+ const text = (0, _salesNotes.buildSalesNoteText)(input.text);
2562
+ const notes = tempOrder.notes || [];
2563
+ // 当前 allergy policy 固定同一商品行只保留一条;业务调用方不参与去重规则。
2564
+ const existingIndex = policy.uniquePerPrimaryTarget ? notes.findIndex(note => {
2565
+ const primary = this.getSalesNotePrimaryRelation(note);
2566
+ return note.note_type === note_type && primary?.target_type === 'order_detail' && primary.target_uuid === target_uuid;
2567
+ }) : -1;
2568
+ const existing = existingIndex >= 0 ? notes[existingIndex] : null;
2569
+ const existingPrimary = existing?.note_relations.find(relation => relation.is_primary === 1 && relation.target_type === 'order_detail');
2570
+ const existingOrderRelation = existing?.note_relations.find(relation => relation.is_primary === 0 && relation.target_type === 'order');
2571
+ const note = {
2572
+ uuid: existing?.uuid || (0, _utils.createUuidV4)(),
2573
+ note_type: policy.note_type,
2574
+ content: {
2575
+ text
2576
+ },
2577
+ importance: policy.importance,
2578
+ note_relations: [{
2579
+ uuid: existingPrimary?.uuid || (0, _utils.createUuidV4)(),
2580
+ relation_type: policy.primary_relation_type,
2581
+ target_type: 'order_detail',
2582
+ target_uuid,
2583
+ is_primary: 1
2584
+ }, {
2585
+ uuid: existingOrderRelation?.uuid || (0, _utils.createUuidV4)(),
2586
+ relation_type: policy.secondary_order_relation_type,
2587
+ target_type: 'order',
2588
+ target_uuid: String(tempOrder.shop_order_number ?? ''),
2589
+ is_primary: 0
2590
+ }]
2591
+ };
2592
+ const duplicateIndex = notes.findIndex((item, index) => item.uuid === note.uuid && index !== existingIndex);
2593
+ if (duplicateIndex >= 0) {
2594
+ throw new Error('[Order Notes] uuid 已被其他 Note 使用');
2595
+ }
2596
+ tempOrder.notes = existingIndex >= 0 ? notes.map((item, index) => index === existingIndex ? note : item) : [...notes, note];
2597
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2598
+ state.loaded = true;
2599
+ state.dirty = true;
2600
+ return note;
2601
+ }
2602
+ getSalesNotes() {
2603
+ const tempOrder = this.ensureTempOrder();
2604
+ this.ensureSalesNotesRuntimeState(tempOrder);
2605
+ return (0, _lodashEs.cloneDeep)(tempOrder.notes || []);
2606
+ }
2607
+ createSalesNotesMutationSnapshot() {
2608
+ const tempOrder = this.ensureTempOrder();
2609
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2610
+ return {
2611
+ orderIdentity: {
2612
+ orderId: tempOrder.order_id ?? null,
2613
+ externalSaleNumber: String(tempOrder.external_sale_number || '')
2614
+ },
2615
+ notes: (0, _lodashEs.cloneDeep)(tempOrder.notes || []),
2616
+ state: (0, _lodashEs.cloneDeep)(state)
2617
+ };
2618
+ }
2619
+ restoreSalesNotesMutationSnapshot(snapshot) {
2620
+ if (!snapshot || !Array.isArray(snapshot.notes) || !snapshot.orderIdentity || typeof snapshot.orderIdentity.externalSaleNumber !== 'string' || typeof snapshot.state?.loaded !== 'boolean' || typeof snapshot.state?.dirty !== 'boolean') {
2621
+ throw new Error('[Order Notes] 无效的 mutation snapshot');
2622
+ }
2623
+ const tempOrder = this.store.tempOrder;
2624
+ if (!tempOrder) throw new Error('[Order Notes] 当前订单不存在');
2625
+ const snapshotOrderId = snapshot.orderIdentity.orderId;
2626
+ const currentOrderId = tempOrder.order_id;
2627
+ const hasSnapshotOrderId = snapshotOrderId !== null && snapshotOrderId !== undefined && String(snapshotOrderId).trim().length > 0;
2628
+ const hasCurrentOrderId = currentOrderId !== null && currentOrderId !== undefined && String(currentOrderId).trim().length > 0;
2629
+ const isSameOrder = hasSnapshotOrderId && hasCurrentOrderId ? String(snapshotOrderId) === String(currentOrderId) : Boolean(snapshot.orderIdentity.externalSaleNumber) && snapshot.orderIdentity.externalSaleNumber === String(tempOrder.external_sale_number || '');
2630
+ if (!isSameOrder) {
2631
+ throw new Error('[Order Notes] 当前订单已切换,拒绝恢复旧订单快照');
2632
+ }
2633
+ this.ensureSalesNotesRuntimeState(tempOrder);
2634
+ tempOrder.notes = (0, _lodashEs.cloneDeep)(snapshot.notes);
2635
+ tempOrder._extend.salesNotesState = (0, _lodashEs.cloneDeep)(snapshot.state);
2636
+ this.notifyTempOrderChanged();
2637
+ }
2638
+ async setSalesNote(input) {
2639
+ const tempOrder = this.ensureTempOrder();
2640
+ const note = this.setSalesNoteOnTempOrder(tempOrder, input);
2641
+ this.notifyTempOrderChanged();
2642
+ return (0, _lodashEs.cloneDeep)(note);
2643
+ }
2644
+ async removeSalesNote(uuid) {
2645
+ const tempOrder = this.ensureTempOrder();
2646
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
2647
+ const normalizedUuid = this.normalizeSalesNoteUuid(uuid, 'uuid');
2648
+ const notes = tempOrder.notes || [];
2649
+ const nextNotes = notes.filter(note => note.uuid !== normalizedUuid);
2650
+ if (nextNotes.length === notes.length) return;
2651
+ tempOrder.notes = nextNotes;
2652
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2653
+ state.dirty = true;
2654
+ this.notifyTempOrderChanged();
2655
+ }
2656
+ async clearSalesNotes() {
2657
+ const tempOrder = this.ensureTempOrder();
2658
+ tempOrder.notes = [];
2659
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2660
+ state.loaded = true;
2661
+ state.dirty = true;
2662
+ this.notifyTempOrderChanged();
2663
+ }
2401
2664
  updateTempOrderShopDiscount(amount) {
2402
2665
  const tempOrder = this.ensureTempOrder();
2403
2666
  const discount = _decimal.default.max(new _decimal.default(Number(amount) || 0), 0).toDecimalPlaces(2).toFixed(2);
@@ -2437,6 +2700,31 @@ class OrderModule extends _BaseModule.BaseModule {
2437
2700
  this.persistTempOrder();
2438
2701
  return tempOrder.buzzer;
2439
2702
  }
2703
+ updateTempOrderShopServiceData(patch) {
2704
+ const tempOrder = this.ensureTempOrder();
2705
+ const metadata = tempOrder.metadata && typeof tempOrder.metadata === 'object' && !Array.isArray(tempOrder.metadata) ? tempOrder.metadata : {};
2706
+ const currentShopServiceData = metadata.shop_service_data && typeof metadata.shop_service_data === 'object' && !Array.isArray(metadata.shop_service_data) ? metadata.shop_service_data : {};
2707
+ const shopServiceData = {
2708
+ ...currentShopServiceData,
2709
+ ...patch
2710
+ };
2711
+ tempOrder.metadata = {
2712
+ ...metadata,
2713
+ shop_service_data: shopServiceData
2714
+ };
2715
+ this.persistTempOrder();
2716
+ return shopServiceData;
2717
+ }
2718
+ updateTempOrderPickupReferenceMode(mode) {
2719
+ return this.updateTempOrderShopServiceData({
2720
+ pickup_reference_mode: mode
2721
+ });
2722
+ }
2723
+ updateTempOrderServiceType(serviceType) {
2724
+ return this.updateTempOrderShopServiceData({
2725
+ service_type: serviceType
2726
+ });
2727
+ }
2440
2728
  updateTempOrderContactsInfo(contactsInfo) {
2441
2729
  const tempOrder = this.ensureTempOrder();
2442
2730
  tempOrder.contacts_info = contactsInfo && typeof contactsInfo === 'object' && !Array.isArray(contactsInfo) ? contactsInfo : null;
@@ -2547,8 +2835,7 @@ class OrderModule extends _BaseModule.BaseModule {
2547
2835
  });
2548
2836
  const nextCustomerId = tempOrder.customer_id ?? null;
2549
2837
  if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
2550
- this.store.availableWalletIds = [];
2551
- this.clearTempOrderPointCardSnapshot(tempOrder);
2838
+ this.clearCustomerWalletState(tempOrder);
2552
2839
  }
2553
2840
  this.persistTempOrder();
2554
2841
  return {
@@ -2584,9 +2871,7 @@ class OrderModule extends _BaseModule.BaseModule {
2584
2871
  }
2585
2872
  const nextCustomerId = tempOrder.customer_id ?? null;
2586
2873
  if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
2587
- this.store.availableWalletIds = [];
2588
- this.store.virtualCurrencyList = [];
2589
- this.clearTempOrderPointCardSnapshot(tempOrder);
2874
+ this.clearCustomerWalletState(tempOrder);
2590
2875
  (0, _utils.clearTempOrderHolderAssignments)(tempOrder);
2591
2876
  this.clearCustomerBoundDiscounts(tempOrder);
2592
2877
  this.applyDiscount();
@@ -2624,9 +2909,7 @@ class OrderModule extends _BaseModule.BaseModule {
2624
2909
  tempOrder.email = '';
2625
2910
  tempOrder.customer = null;
2626
2911
  if (tempOrder._extend) delete tempOrder._extend.customerSnapshot;
2627
- this.store.availableWalletIds = [];
2628
- this.store.virtualCurrencyList = [];
2629
- this.clearTempOrderPointCardSnapshot(tempOrder);
2912
+ this.clearCustomerWalletState(tempOrder);
2630
2913
  this.clearCustomerBoundDiscounts(tempOrder);
2631
2914
  this.applyDiscount();
2632
2915
  this.persistTempOrder();
@@ -3034,9 +3317,7 @@ class OrderModule extends _BaseModule.BaseModule {
3034
3317
  if (!options?.skipEditDiscountConfigRefresh && !isVirtualSettlement) {
3035
3318
  await this.ensureEditDiscountConfigForProductChange(tempOrder);
3036
3319
  }
3037
- if (!options?.skipDiscountRecalculation && !isVirtualSettlement) {
3038
- await this.applyPromotion();
3039
- }
3320
+ const promotionPayload = !options?.skipDiscountRecalculation && !isVirtualSettlement ? await this.evaluatePromotion() : null;
3040
3321
  if (!options?.skipDiscountRecalculation && !isVirtualSettlement) {
3041
3322
  this.applyDiscount();
3042
3323
  }
@@ -3045,6 +3326,7 @@ class OrderModule extends _BaseModule.BaseModule {
3045
3326
  createIfMissing: true
3046
3327
  });
3047
3328
  this.persistTempOrder();
3329
+ this.emitPromotionApplied(promotionPayload);
3048
3330
  }
3049
3331
 
3050
3332
  /**
@@ -3127,6 +3409,7 @@ class OrderModule extends _BaseModule.BaseModule {
3127
3409
  const matchedProduct = matchedIndex === -1 ? null : tempOrder.products[matchedIndex];
3128
3410
  const existedFingerprint = matchedProduct ? (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(matchedProduct), matchedProduct.product_bundle) : '';
3129
3411
  const commitProductLine = shouldMerge => {
3412
+ let committedProduct;
3130
3413
  if (matchedIndex === -1 || !matchedProduct || !shouldMerge) {
3131
3414
  this.captureProductRuntime(tempOrder, productToAdd, normalizedIncoming);
3132
3415
  if (linked) {
@@ -3142,6 +3425,7 @@ class OrderModule extends _BaseModule.BaseModule {
3142
3425
  } else {
3143
3426
  tempOrder.products = [...tempOrder.products, normalizedIncoming];
3144
3427
  }
3428
+ committedProduct = normalizedIncoming;
3145
3429
  } else {
3146
3430
  const targetProduct = matchedProduct;
3147
3431
  const targetUid = targetProduct.metadata?.unique_identification_number;
@@ -3177,6 +3461,7 @@ class OrderModule extends _BaseModule.BaseModule {
3177
3461
  num: nextNum
3178
3462
  };
3179
3463
  this.captureProductRuntime(tempOrder, productToAdd, tempOrder.products[matchedIndex], targetUid);
3464
+ committedProduct = tempOrder.products[matchedIndex];
3180
3465
  if (matchedIndex !== tempOrder.products.length - 1) {
3181
3466
  const mergedProduct = tempOrder.products[matchedIndex];
3182
3467
  tempOrder.products = [...tempOrder.products.slice(0, matchedIndex), ...tempOrder.products.slice(matchedIndex + 1), mergedProduct];
@@ -3185,10 +3470,10 @@ class OrderModule extends _BaseModule.BaseModule {
3185
3470
  if (linked) {
3186
3471
  tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
3187
3472
  }
3473
+ return committedProduct;
3188
3474
  };
3189
3475
  if (!matchedProduct) {
3190
- commitProductLine(false);
3191
- return;
3476
+ return commitProductLine(false);
3192
3477
  }
3193
3478
  const shouldMerge = this.shouldMergeProductToOrder({
3194
3479
  incomingProduct: productToAdd,
@@ -3203,7 +3488,7 @@ class OrderModule extends _BaseModule.BaseModule {
3203
3488
  if (shouldMerge && typeof shouldMerge === 'object' && typeof shouldMerge.then === 'function') {
3204
3489
  return Promise.resolve(shouldMerge).then(commitProductLine);
3205
3490
  }
3206
- commitProductLine(shouldMerge);
3491
+ return commitProductLine(shouldMerge);
3207
3492
  }
3208
3493
 
3209
3494
  /**
@@ -3276,34 +3561,99 @@ class OrderModule extends _BaseModule.BaseModule {
3276
3561
  */
3277
3562
  async addProductToOrder(product, booking, options) {
3278
3563
  const tempOrder = this.ensureTempOrder();
3564
+ const salesNoteDrafts = (options?.salesNotes || []).map(draft => ({
3565
+ note_type: String(draft.note_type || '').trim(),
3566
+ text: {
3567
+ kind: 'resolved',
3568
+ text: (0, _salesNotes.buildSalesNoteText)(draft.text)
3569
+ }
3570
+ }));
3571
+ const mutationSnapshot = salesNoteDrafts.length > 0 ? {
3572
+ products: (0, _lodashEs.cloneDeep)(tempOrder.products || []),
3573
+ bookings: (0, _lodashEs.cloneDeep)(tempOrder.bookings || []),
3574
+ notes: (0, _lodashEs.cloneDeep)(tempOrder.notes || []),
3575
+ extend: (0, _lodashEs.cloneDeep)(tempOrder._extend || {})
3576
+ } : null;
3577
+ const attachSalesNoteDrafts = orderProduct => {
3578
+ if (!salesNoteDrafts.length) return;
3579
+ const productLineUid = orderProduct.metadata?.unique_identification_number || orderProduct.unique_identification_number;
3580
+ if (!productLineUid) {
3581
+ throw new Error('[Order Notes] 商品行缺少 unique_identification_number');
3582
+ }
3583
+ salesNoteDrafts.forEach(draft => {
3584
+ this.setSalesNoteOnTempOrder(tempOrder, {
3585
+ note_type: draft.note_type,
3586
+ target: {
3587
+ target_type: 'order_detail',
3588
+ target_uuid: String(productLineUid)
3589
+ },
3590
+ text: draft.text
3591
+ });
3592
+ });
3593
+ };
3594
+ const rollbackProductAndNotesMutation = () => {
3595
+ if (!mutationSnapshot) return;
3596
+ tempOrder.products = mutationSnapshot.products;
3597
+ tempOrder.bookings = mutationSnapshot.bookings;
3598
+ tempOrder.notes = mutationSnapshot.notes;
3599
+ tempOrder._extend = mutationSnapshot.extend;
3600
+ };
3279
3601
  const insertAfterProductUid = options?.insertAfterProductUid;
3280
3602
  const anchorIndex = booking && insertAfterProductUid ? tempOrder.products.findIndex(line => {
3281
3603
  const uid = line.metadata?.unique_identification_number || line.unique_identification_number;
3282
3604
  return uid !== undefined && uid !== null && String(uid) === String(insertAfterProductUid);
3283
3605
  }) : -1;
3284
3606
  let insertAtIndex = anchorIndex >= 0 ? anchorIndex + 1 : undefined;
3285
- if (booking) {
3286
- const productRecord = product;
3287
- const splitCount = (0, _utils3.getSafeProductNum)(productRecord.num ?? productRecord.product_quantity ?? 1);
3288
- if (splitCount > 1) {
3289
- for (let i = 0; i < splitCount; i += 1) {
3290
- const splitLine = this.createSplitBookingLine(productRecord, booking, i, splitCount);
3291
- const appendResult = this.appendProductLineToTempOrder(tempOrder, splitLine.product, splitLine.booking, {
3607
+ try {
3608
+ if (booking) {
3609
+ const productRecord = product;
3610
+ const splitCount = (0, _utils3.getSafeProductNum)(productRecord.num ?? productRecord.product_quantity ?? 1);
3611
+ if (splitCount > 1) {
3612
+ for (let i = 0; i < splitCount; i += 1) {
3613
+ const splitLine = this.createSplitBookingLine(productRecord, booking, i, splitCount);
3614
+ if (i > 0) {
3615
+ delete splitLine.product.unique_identification_number;
3616
+ delete splitLine.product.identity_key;
3617
+ if (splitLine.product.metadata) {
3618
+ splitLine.product.metadata = {
3619
+ ...splitLine.product.metadata
3620
+ };
3621
+ delete splitLine.product.metadata.unique_identification_number;
3622
+ delete splitLine.product.metadata.identity_key;
3623
+ }
3624
+ delete splitLine.booking.booking_uid;
3625
+ if (splitLine.booking.metadata) {
3626
+ splitLine.booking.metadata = {
3627
+ ...splitLine.booking.metadata
3628
+ };
3629
+ delete splitLine.booking.metadata.unique_identification_number;
3630
+ }
3631
+ }
3632
+ const committedProduct = await this.appendProductLineToTempOrder(tempOrder, splitLine.product, splitLine.booking, {
3633
+ insertAtIndex,
3634
+ disableMerge: options?.disableMerge
3635
+ });
3636
+ attachSalesNoteDrafts(committedProduct);
3637
+ if (insertAtIndex !== undefined) insertAtIndex += 1;
3638
+ }
3639
+ } else {
3640
+ const committedProduct = await this.appendProductLineToTempOrder(tempOrder, product, (0, _lodashEs.cloneDeep)(booking), {
3292
3641
  insertAtIndex,
3293
3642
  disableMerge: options?.disableMerge
3294
3643
  });
3295
- if (appendResult) await appendResult;
3296
- if (insertAtIndex !== undefined) insertAtIndex += 1;
3644
+ attachSalesNoteDrafts(committedProduct);
3297
3645
  }
3298
- await this.finalizeProductOrderMutation(tempOrder);
3299
- return tempOrder.products;
3646
+ } else {
3647
+ const committedProduct = await this.appendProductLineToTempOrder(tempOrder, product, undefined, {
3648
+ insertAtIndex,
3649
+ disableMerge: options?.disableMerge
3650
+ });
3651
+ attachSalesNoteDrafts(committedProduct);
3300
3652
  }
3653
+ } catch (error) {
3654
+ rollbackProductAndNotesMutation();
3655
+ throw error;
3301
3656
  }
3302
- const appendResult = this.appendProductLineToTempOrder(tempOrder, product, booking, {
3303
- insertAtIndex,
3304
- disableMerge: options?.disableMerge
3305
- });
3306
- if (appendResult) await appendResult;
3307
3657
  await this.finalizeProductOrderMutation(tempOrder);
3308
3658
  this.logInfo('addProductToOrder success', {
3309
3659
  product_id: product.product_id,
@@ -3626,7 +3976,7 @@ class OrderModule extends _BaseModule.BaseModule {
3626
3976
  async updateOrderProduct(params) {
3627
3977
  const tempOrder = this.ensureTempOrder();
3628
3978
  const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params);
3629
- await this.applyPromotion();
3979
+ const promotionPayload = await this.evaluatePromotion();
3630
3980
  this.applyDiscount({
3631
3981
  reapplyBookingDiscountProductUids: params.allow_booking_discount_reapply && updatedProductUid ? [String(updatedProductUid)] : undefined
3632
3982
  });
@@ -3635,6 +3985,7 @@ class OrderModule extends _BaseModule.BaseModule {
3635
3985
  createIfMissing: true
3636
3986
  });
3637
3987
  this.persistTempOrder();
3988
+ this.emitPromotionApplied(promotionPayload);
3638
3989
  this.logInfo('updateOrderProduct success', {
3639
3990
  params
3640
3991
  });
@@ -3729,8 +4080,9 @@ class OrderModule extends _BaseModule.BaseModule {
3729
4080
  }
3730
4081
  tempOrder.products[productIndex] = normalizedProduct;
3731
4082
  this.prepareVirtualSettlementTempOrder(tempOrder);
3732
- if (!this.isActiveVirtualSettlementOrder(tempOrder)) {
3733
- await this.applyPromotion();
4083
+ const isVirtualSettlement = this.isActiveVirtualSettlementOrder(tempOrder);
4084
+ const promotionPayload = !isVirtualSettlement ? await this.evaluatePromotion() : null;
4085
+ if (!isVirtualSettlement) {
3734
4086
  this.applyDiscount();
3735
4087
  }
3736
4088
  this.sanitizeTempOrderProducts(tempOrder);
@@ -3738,6 +4090,7 @@ class OrderModule extends _BaseModule.BaseModule {
3738
4090
  createIfMissing: true
3739
4091
  });
3740
4092
  this.persistTempOrder();
4093
+ this.emitPromotionApplied(promotionPayload);
3741
4094
  this.logInfo('updateOrderProductQuantity success', {
3742
4095
  params
3743
4096
  });
@@ -3782,7 +4135,7 @@ class OrderModule extends _BaseModule.BaseModule {
3782
4135
  */
3783
4136
  async finalizeAfterProductsMutation(tempOrder, options) {
3784
4137
  this.syncTempOrderHolderFromBookings(tempOrder);
3785
- await this.applyPromotion();
4138
+ const promotionPayload = await this.evaluatePromotion();
3786
4139
  this.applyDiscount({
3787
4140
  reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids
3788
4141
  });
@@ -3791,6 +4144,7 @@ class OrderModule extends _BaseModule.BaseModule {
3791
4144
  createIfMissing: true
3792
4145
  });
3793
4146
  this.persistTempOrder();
4147
+ this.emitPromotionApplied(promotionPayload);
3794
4148
  }
3795
4149
 
3796
4150
  /**
@@ -3801,10 +4155,12 @@ class OrderModule extends _BaseModule.BaseModule {
3801
4155
  */
3802
4156
  async removeProductsFromOrder(identities, options) {
3803
4157
  const tempOrder = this.ensureTempOrder();
3804
- this.repairDuplicateProductLineIdentities(tempOrder);
3805
4158
  if (!identities.length) {
3806
4159
  return tempOrder.products;
3807
4160
  }
4161
+ // 商品删除会同步删除以商品行为主关系的 Notes;快照不完整时禁止产生孤儿 Relation。
4162
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
4163
+ this.repairDuplicateProductLineIdentities(tempOrder);
3808
4164
  const productsBeforeRemove = tempOrder.products || [];
3809
4165
  const bookingsBeforeRemove = tempOrder.bookings || [];
3810
4166
  const preserveDisplayPosition = options?.preserveDisplayPosition;
@@ -3864,6 +4220,7 @@ class OrderModule extends _BaseModule.BaseModule {
3864
4220
  tempOrder.products = nextProductIndex >= 0 ? [...productsWithoutRemaining.slice(0, nextProductIndex), remainingProduct, ...productsWithoutRemaining.slice(nextProductIndex)] : [...productsWithoutRemaining, remainingProduct];
3865
4221
  }
3866
4222
  }
4223
+ this.removeSalesNotesForMissingProductLines(tempOrder);
3867
4224
  await this.finalizeAfterProductsMutation(tempOrder);
3868
4225
  this.logInfo('removeProductsFromOrder success', {
3869
4226
  identities
@@ -3876,13 +4233,15 @@ class OrderModule extends _BaseModule.BaseModule {
3876
4233
 
3877
4234
  /**
3878
4235
  * 仅清空购物车行(products / bookings),不重置整单。
3879
- * 用于「仅清空商品」;客户、备注、支付等协议字段保持不变。
4236
+ * 普通整单 note 保持不变;以商品行为主体的 Sales Notes 同步删除。
3880
4237
  */
3881
4238
  async clearOrderCartLines() {
3882
4239
  const tempOrder = this.ensureTempOrder();
4240
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
3883
4241
  tempOrder.products = [];
3884
4242
  tempOrder.bookings = [];
3885
4243
  (0, _virtualSettlement.releaseTempOrderSettlementState)(tempOrder);
4244
+ this.removeSalesNotesForMissingProductLines(tempOrder);
3886
4245
  this.syncTempOrderHolderFromBookings(tempOrder);
3887
4246
  if (tempOrder._extend && typeof tempOrder._extend === 'object') {
3888
4247
  tempOrder._extend = {
@@ -3890,13 +4249,14 @@ class OrderModule extends _BaseModule.BaseModule {
3890
4249
  productsByUid: {}
3891
4250
  };
3892
4251
  }
3893
- await this.applyPromotion();
4252
+ const promotionPayload = await this.evaluatePromotion();
3894
4253
  this.applyDiscount();
3895
4254
  this.sanitizeTempOrderProducts(tempOrder);
3896
4255
  await this.recalculateSummary({
3897
4256
  createIfMissing: true
3898
4257
  });
3899
4258
  this.persistTempOrder();
4259
+ this.emitPromotionApplied(promotionPayload);
3900
4260
  this.logInfo('clearOrderCartLines success', {});
3901
4261
  return tempOrder;
3902
4262
  }
@@ -3906,8 +4266,10 @@ class OrderModule extends _BaseModule.BaseModule {
3906
4266
  */
3907
4267
  async clearOrderForSettlementSwitch() {
3908
4268
  const tempOrder = this.ensureTempOrder();
4269
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
3909
4270
  tempOrder.products = [];
3910
4271
  tempOrder.bookings = [];
4272
+ this.removeSalesNotesForMissingProductLines(tempOrder);
3911
4273
  tempOrder.discount_list = [];
3912
4274
  tempOrder.payments = [];
3913
4275
  tempOrder.refunds = [];
@@ -4050,6 +4412,7 @@ class OrderModule extends _BaseModule.BaseModule {
4050
4412
  })) || this.store.summary || (0, _utils.createEmptySummary)();
4051
4413
  const effectiveCacheId = params?.cacheId ?? this.cacheId;
4052
4414
  const hasPaymentStatusOverride = params?.paymentStatus !== undefined;
4415
+ const shouldOverridePaymentStatus = hasPaymentStatusOverride && !isOnlineStorePlatform && !shouldConfirmPendingVoucherPayments;
4053
4416
  const hasSmallTicketDataFlagOverride = params?.smallTicketDataFlag !== undefined;
4054
4417
  const enhancePayload = hasPaymentOverride || hasPaymentStatusOverride || hasSmallTicketDataFlagOverride || params?.enhancePayload ? (payload, ctx) => {
4055
4418
  const nextPayload = {
@@ -4057,7 +4420,7 @@ class OrderModule extends _BaseModule.BaseModule {
4057
4420
  ...(hasPaymentOverride ? {
4058
4421
  payments: preparedPaymentOverride || []
4059
4422
  } : {}),
4060
- ...(hasPaymentStatusOverride && !isOnlineStorePlatform && !shouldConfirmPendingVoucherPayments ? {
4423
+ ...(shouldOverridePaymentStatus ? {
4061
4424
  payment_status: params?.paymentStatus
4062
4425
  } : {}),
4063
4426
  ...(hasSmallTicketDataFlagOverride ? {
@@ -4124,6 +4487,12 @@ class OrderModule extends _BaseModule.BaseModule {
4124
4487
  this.logSubmitBackendRejected(result, payload);
4125
4488
  return result;
4126
4489
  }
4490
+ const isLocalPendingSubmit = this.isLocalPendingSubmitResult(result);
4491
+ // need_sync=1 仅表示已进入本地同步队列,不能视为远端已确认 Notes。
4492
+ if (Object.prototype.hasOwnProperty.call(payload, 'notes') && !isLocalPendingSubmit) {
4493
+ const salesNotesState = this.ensureSalesNotesRuntimeState(tempOrder);
4494
+ salesNotesState.dirty = false;
4495
+ }
4127
4496
  const submittedOrderId = this.extractOrderIdFromSubmitResult(result);
4128
4497
  this.logInfo('runSubmitTempOrder: 提交成功', {
4129
4498
  submittedOrderId,
@@ -4137,9 +4506,10 @@ class OrderModule extends _BaseModule.BaseModule {
4137
4506
  tempOrder.order_id = submittedOrderId;
4138
4507
  const resultRecord = result;
4139
4508
  await this.markLocalOrderSynced(submittedOrderId, resultRecord?.data || resultRecord || {});
4140
- } else if (this.isLocalPendingSubmitResult(result)) {
4509
+ }
4510
+ if (isLocalPendingSubmit) {
4141
4511
  this.store.syncState = 'local';
4142
- } else {
4512
+ } else if (submittedOrderId === null || submittedOrderId === undefined) {
4143
4513
  this.store.syncState = 'submitted';
4144
4514
  }
4145
4515
  return result;
@@ -4535,7 +4905,7 @@ class OrderModule extends _BaseModule.BaseModule {
4535
4905
  params.forceRemote = false;
4536
4906
  }
4537
4907
  const res = await this.request.get(`/order/sales/${encodeURIComponent(lookup)}`, {
4538
- with: ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info', 'order_meta_list', 'refunds'],
4908
+ with: ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info', 'order_meta_list', 'refunds', 'notes'],
4539
4909
  ...(params.forceRemote ? {
4540
4910
  forceRemote: true
4541
4911
  } : {})
@@ -4588,9 +4958,11 @@ class OrderModule extends _BaseModule.BaseModule {
4588
4958
  const isMergedSalesDetailHydrate = options?.source === 'mergedSalesDetail';
4589
4959
  const nextTempOrder = this.normalizeTempOrderForRuntime(raw, {
4590
4960
  // merge 前已只给远端商品补标记;这里不能把本地未提交商品一并变成 saved item。
4591
- makeEditMark: !isSessionStorageHydrate && !isMergedSalesDetailHydrate
4961
+ makeEditMark: !isSessionStorageHydrate && !isMergedSalesDetailHydrate,
4962
+ allowUnresolvedSalesNotes: isSessionStorageHydrate
4592
4963
  });
4593
4964
  this.store.tempOrder = nextTempOrder;
4965
+ this.clearCustomerWalletRuntimeState();
4594
4966
  if (isSessionStorageHydrate) {
4595
4967
  // 会话快照是未提交购物车,不应获得编辑订单的 saved/edit 标记与服务端基线。
4596
4968
  const restoredSessionDiscounts = Array.isArray(raw.discount_list) ? (0, _lodashEs.cloneDeep)(raw.discount_list) : [];
@@ -4810,6 +5182,17 @@ class OrderModule extends _BaseModule.BaseModule {
4810
5182
  nextTempOrder.payments = Array.isArray(raw.payments) ? raw.payments.map(p => ({
4811
5183
  ...(p || {})
4812
5184
  })) : [];
5185
+ const rawHasSalesNotes = Object.prototype.hasOwnProperty.call(raw, 'notes');
5186
+ const restoredSalesNotesState = raw._extend?.salesNotesState;
5187
+ const allowUnresolvedSalesNotes = options?.allowUnresolvedSalesNotes === true || restoredSalesNotesState?.dirty === true || Number(raw.is_draft_order || 0) === 1;
5188
+ const decodedSalesNotes = rawHasSalesNotes ? (0, _salesNotes.decodeSalesNotesSnapshot)(raw.notes, {
5189
+ allowUnresolvedOrderTarget: allowUnresolvedSalesNotes
5190
+ }) : {
5191
+ notes: [],
5192
+ complete: false
5193
+ };
5194
+ const hasUnresolvedSalesNoteOrderTarget = decodedSalesNotes.notes.some(note => note.note_relations.some(relation => relation.is_primary === 0 && relation.relation_type === 'related_to' && relation.target_type === 'order' && relation.target_uuid === ''));
5195
+ nextTempOrder.notes = decodedSalesNotes.notes;
4813
5196
  nextTempOrder.surcharges = Array.isArray(raw.surcharges) ? raw.surcharges.map(s => ({
4814
5197
  ...(s || {})
4815
5198
  })) : [];
@@ -4830,6 +5213,10 @@ class OrderModule extends _BaseModule.BaseModule {
4830
5213
  productsByUid: {}
4831
5214
  };
4832
5215
  const tempOrderExtend = nextTempOrder._extend;
5216
+ tempOrderExtend.salesNotesState = {
5217
+ loaded: rawHasSalesNotes ? decodedSalesNotes.complete : restoredSalesNotesState?.loaded === true,
5218
+ dirty: decodedSalesNotes.complete && (restoredSalesNotesState?.dirty === true || allowUnresolvedSalesNotes && hasUnresolvedSalesNoteOrderTarget)
5219
+ };
4833
5220
  const productsByUid = tempOrderExtend.productsByUid || {};
4834
5221
  for (const [index, product] of nextTempOrder.products.entries()) {
4835
5222
  const uid = product.metadata?.unique_identification_number;
@@ -5021,7 +5408,7 @@ class OrderModule extends _BaseModule.BaseModule {
5021
5408
  }
5022
5409
  async getOrderInfo(order_id) {
5023
5410
  const res = await this.request.get(`/order/sales/${order_id}`, {
5024
- with: ['products', 'bookings']
5411
+ with: ['products', 'bookings', 'notes']
5025
5412
  }, {
5026
5413
  osServer: true
5027
5414
  });