@pisell/pisellos 0.10.43 → 0.10.44

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 (87) 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/modules/Order/index.d.ts +41 -9
  8. package/dist/modules/Order/index.js +1438 -878
  9. package/dist/modules/Order/salesNotes.d.ts +31 -0
  10. package/dist/modules/Order/salesNotes.js +492 -0
  11. package/dist/modules/Order/types.d.ts +105 -8
  12. package/dist/modules/Order/types.js +6 -1
  13. package/dist/modules/Order/utils.d.ts +3 -1
  14. package/dist/modules/Order/utils.js +191 -71
  15. package/dist/modules/Payment/cashRecommendationAlgorithm.d.ts +4 -5
  16. package/dist/modules/Payment/cashRecommendationAlgorithm.js +17 -367
  17. package/dist/modules/ProductList/index.d.ts +2 -1
  18. package/dist/modules/ProductList/index.js +100 -20
  19. package/dist/modules/ProductList/types.d.ts +10 -0
  20. package/dist/server/index.d.ts +7 -0
  21. package/dist/server/index.js +45 -3
  22. package/dist/server/modules/order/index.js +6 -0
  23. package/dist/server/modules/order/types.d.ts +9 -0
  24. package/dist/server/utils/small-ticket.d.ts +1 -0
  25. package/dist/server/utils/small-ticket.js +142 -14
  26. package/dist/solution/BaseSales/index.d.ts +15 -1
  27. package/dist/solution/BaseSales/index.js +1240 -877
  28. package/dist/solution/BookingByStep/index.d.ts +1 -1
  29. package/dist/solution/BookingTicket/index.d.ts +1 -0
  30. package/dist/solution/BookingTicket/index.js +37 -16
  31. package/dist/solution/BookingTicket/utils/orderCustomer.js +3 -2
  32. package/dist/solution/RegisterAndLogin/config.d.ts +9 -3
  33. package/dist/solution/RegisterAndLogin/config.js +95 -40
  34. package/dist/solution/RegisterAndLogin/index.js +4 -1
  35. package/dist/solution/ScanOrder/index.d.ts +1 -0
  36. package/dist/solution/ScanOrder/index.js +31 -27
  37. package/dist/solution/ScanOrder/utils.d.ts +1 -0
  38. package/dist/solution/ScanOrder/utils.js +2 -0
  39. package/dist/solution/UnifiedBookingSales/index.d.ts +15 -2
  40. package/dist/solution/UnifiedBookingSales/index.js +777 -510
  41. package/dist/solution/UnifiedBookingSales/types.d.ts +14 -1
  42. package/dist/solution/VenueBooking/index.d.ts +1 -0
  43. package/dist/solution/VenueBooking/index.js +8 -4
  44. package/lib/model/strategy/adapter/itemRule/adapter.d.ts +4 -1
  45. package/lib/model/strategy/adapter/itemRule/adapter.js +73 -4
  46. package/lib/model/strategy/adapter/itemRule/evaluator.d.ts +8 -1
  47. package/lib/model/strategy/adapter/itemRule/evaluator.js +23 -1
  48. package/lib/model/strategy/adapter/itemRule/type.d.ts +44 -0
  49. package/lib/model/strategy/adapter/itemRule/type.js +19 -1
  50. package/lib/modules/Order/index.d.ts +41 -9
  51. package/lib/modules/Order/index.js +434 -61
  52. package/lib/modules/Order/salesNotes.d.ts +31 -0
  53. package/lib/modules/Order/salesNotes.js +382 -0
  54. package/lib/modules/Order/types.d.ts +105 -8
  55. package/lib/modules/Order/types.js +5 -1
  56. package/lib/modules/Order/utils.d.ts +3 -1
  57. package/lib/modules/Order/utils.js +114 -1
  58. package/lib/modules/Payment/cashRecommendationAlgorithm.d.ts +4 -5
  59. package/lib/modules/Payment/cashRecommendationAlgorithm.js +9 -354
  60. package/lib/modules/ProductList/index.d.ts +2 -1
  61. package/lib/modules/ProductList/index.js +47 -2
  62. package/lib/modules/ProductList/types.d.ts +10 -0
  63. package/lib/server/index.d.ts +7 -0
  64. package/lib/server/index.js +39 -3
  65. package/lib/server/modules/order/index.js +6 -0
  66. package/lib/server/modules/order/types.d.ts +9 -0
  67. package/lib/server/utils/small-ticket.d.ts +1 -0
  68. package/lib/server/utils/small-ticket.js +136 -12
  69. package/lib/solution/BaseSales/index.d.ts +15 -1
  70. package/lib/solution/BaseSales/index.js +192 -22
  71. package/lib/solution/BookingByStep/index.d.ts +1 -1
  72. package/lib/solution/BookingTicket/index.d.ts +1 -0
  73. package/lib/solution/BookingTicket/index.js +23 -5
  74. package/lib/solution/BookingTicket/utils/orderCustomer.js +2 -1
  75. package/lib/solution/RegisterAndLogin/config.d.ts +9 -3
  76. package/lib/solution/RegisterAndLogin/config.js +49 -8
  77. package/lib/solution/RegisterAndLogin/index.js +4 -1
  78. package/lib/solution/ScanOrder/index.d.ts +1 -0
  79. package/lib/solution/ScanOrder/index.js +5 -1
  80. package/lib/solution/ScanOrder/utils.d.ts +1 -0
  81. package/lib/solution/ScanOrder/utils.js +1 -0
  82. package/lib/solution/UnifiedBookingSales/index.d.ts +15 -2
  83. package/lib/solution/UnifiedBookingSales/index.js +125 -1
  84. package/lib/solution/UnifiedBookingSales/types.d.ts +14 -1
  85. package/lib/solution/VenueBooking/index.d.ts +1 -0
  86. package/lib/solution/VenueBooking/index.js +5 -1
  87. package/package.json +1 -1
@@ -10,6 +10,7 @@ var _utils = require("./utils");
10
10
  var _utils2 = require("../Product/utils");
11
11
  var _dayjs = _interopRequireDefault(require("dayjs"));
12
12
  var _osPrivatePayload = require("../../utils/os-private-payload");
13
+ var _salesNotes = require("./salesNotes");
13
14
  var _paymentUtils = require("./payment-utils");
14
15
  var _paymentNumber = require("../../utils/payment-number");
15
16
  var _cartPromotion = require("../../solution/BaseSales/utils/cartPromotion");
@@ -126,6 +127,8 @@ class OrderModule extends _BaseModule.BaseModule {
126
127
  openCache = false;
127
128
  sessionStoragePersistEnabled = false;
128
129
  sessionStorageRestoreRecord = null;
130
+ /** 只允许当前订单最新的 prepare/config 请求回写客户资产态。 */
131
+ customerWalletPrepareConfigEpoch = 0;
129
132
  salesSummaryModuleName;
130
133
  rulesHooksOverride;
131
134
  moduleHooksOverride;
@@ -136,8 +139,10 @@ class OrderModule extends _BaseModule.BaseModule {
136
139
  promotionEvaluator = null;
137
140
  /** 赠品选择 resolver;由 SDK host bridge 注入 */
138
141
  giftSelectResolver = null;
139
- /** applyPromotion 递归保护 flag(写路径同步调 applyPromotion,禁止重入) */
142
+ /** 促销评估递归保护 flag(写路径同步评估促销,禁止重入) */
140
143
  isApplyingPromotion = false;
144
+ /** 保持旧事件语义:同步事件监听器内触发的订单写入不得递归评估促销。 */
145
+ isEmittingPromotionApplied = false;
141
146
  /** 商品曾应用过客户券卡,客户变更剥离后需强制跑一次 calcDiscount 复位价格 */
142
147
  hasActiveCustomerBoundDiscount = false;
143
148
  /** 最近一次 applyPromotion 的未满足促销提示 */
@@ -494,10 +499,21 @@ class OrderModule extends _BaseModule.BaseModule {
494
499
  return (0, _utils.createDefaultOrderRulesHooks)();
495
500
  }
496
501
 
502
+ /**
503
+ * Stable across notifyTempOrderChanged's immutable copy, but changes when a
504
+ * different local/server order becomes current.
505
+ */
506
+ getDiscountConfigOrderIdentityKey(tempOrder) {
507
+ const identity = this.getOrderIdentity();
508
+ 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 ?? '']);
509
+ }
510
+
497
511
  // ─── Discount: 公共 API ───
498
512
 
499
513
  async loadDiscountConfig(params) {
500
514
  const orderId = params.orderId || this.store.tempOrder?.order_id || undefined;
515
+ const requestEpoch = ++this.customerWalletPrepareConfigEpoch;
516
+ const requestOrderIdentityKey = this.getDiscountConfigOrderIdentityKey(this.store.tempOrder);
501
517
  const prepareConfigResult = await this.store.discount?.loadPrepareConfig({
502
518
  customer_id: params.customerId,
503
519
  action: params.action || (orderId ? 'edit' : 'create'),
@@ -513,13 +529,17 @@ class OrderModule extends _BaseModule.BaseModule {
513
529
  order_id: orderId
514
530
  } : {})
515
531
  });
532
+ const isCurrentRequest = () => requestEpoch === this.customerWalletPrepareConfigEpoch && this.getDiscountConfigOrderIdentityKey(this.store.tempOrder) === requestOrderIdentityKey && String(this.store.tempOrder?.customer_id ?? '') === String(params.customerId);
533
+ if (!isCurrentRequest()) {
534
+ return this.getDiscountList();
535
+ }
516
536
  const discountList = prepareConfigResult?.discountList;
517
- this.store.availableWalletIds = prepareConfigResult?.availableWalletIds || [];
518
- this.store.virtualCurrencyList = prepareConfigResult?.virtualCurrencyList || [];
519
537
  if (prepareConfigResult) {
520
- this.writePointCardSnapshotFromPrepareConfig({
538
+ this.writeCustomerWalletSnapshotsFromPrepareConfig({
521
539
  customerId: params.customerId,
522
- availableWalletPassList: prepareConfigResult.availableWalletPassList || []
540
+ availableWalletIds: prepareConfigResult.availableWalletIds || [],
541
+ availableWalletPassList: prepareConfigResult.availableWalletPassList || [],
542
+ virtualCurrencyList: prepareConfigResult.virtualCurrencyList || []
523
543
  });
524
544
  }
525
545
  if (discountList) {
@@ -579,9 +599,12 @@ class OrderModule extends _BaseModule.BaseModule {
579
599
  return id === null || !refreshedScanDiscountIds.has(id);
580
600
  });
581
601
  const mergedDiscountList = [...retainedCommittedDiscounts, ...retainedScanDiscounts, ...runtimeDiscounts];
602
+ if (!isCurrentRequest()) return this.getDiscountList();
582
603
  await this.store.discount?.setOriginalDiscountList(mergedDiscountList);
604
+ if (!isCurrentRequest()) return this.getDiscountList();
583
605
  await this.store.discount?.setDiscountList(mergedDiscountList);
584
606
  }
607
+ if (!isCurrentRequest()) return this.getDiscountList();
585
608
  if (params.apply !== false && this.store.tempOrder?.products?.length) {
586
609
  this.applyDiscount();
587
610
  }
@@ -596,6 +619,9 @@ class OrderModule extends _BaseModule.BaseModule {
596
619
  getVirtualCurrencyList() {
597
620
  return (0, _lodashEs.cloneDeep)(this.store.virtualCurrencyList || []);
598
621
  }
622
+ getCustomerWalletPrepareConfigEpoch() {
623
+ return this.customerWalletPrepareConfigEpoch;
624
+ }
599
625
  async syncScannedDiscountsToOriginalDiscountList(discountList) {
600
626
  const scanDiscounts = (discountList || []).filter(discount => discount?.isScan);
601
627
  if (!scanDiscounts.length) return;
@@ -808,7 +834,7 @@ class OrderModule extends _BaseModule.BaseModule {
808
834
  }
809
835
 
810
836
  /**
811
- * 应用购物车促销:计算 → 差异化赠品 → 写回 tempOrder.products → emit onPromotionApplied。
837
+ * 只执行购物车促销评估与赠品写回,不发布稳定态事件。
812
838
  *
813
839
  * 调用时机:写路径(add/update/remove/clear/setCustomer)末尾自动触发;外部一般无需手动调。
814
840
  *
@@ -816,26 +842,27 @@ class OrderModule extends _BaseModule.BaseModule {
816
842
  * - 不调用任何公开 CRUD API(如 removeProductFromOrder),全部内部 mutate tempOrder.products,避免事件风暴;
817
843
  * - 用 `isApplyingPromotion` 防递归;
818
844
  * - 多选项赠品依赖 giftSelectResolver,未注入则跳过且 console.warn;
819
- * - 不主动调用 applyDiscount / recalculateSummary,调用方负责跟进。
845
+ * - 不主动调用 applyDiscount / recalculateSummary,调用方负责跟进;
846
+ * - 返回的 payload 只能在 applyDiscount(商品写路径还需 summary/persist)完成后发布。
820
847
  */
821
- async applyPromotion() {
822
- if (this.isApplyingPromotion) {
823
- return;
848
+ async evaluatePromotion() {
849
+ if (this.isApplyingPromotion || this.isEmittingPromotionApplied) {
850
+ return null;
824
851
  }
825
852
  if (!this.promotionEvaluator) {
826
- return;
853
+ return null;
827
854
  }
828
855
  const tempOrder = this.store.tempOrder;
829
856
  if (!tempOrder) {
830
- return;
857
+ return null;
831
858
  }
832
859
  if (this.isActiveVirtualSettlementOrder(tempOrder)) {
833
860
  tempOrder.products = (0, _virtualSettlement.normalizeVirtualSettlementProducts)(tempOrder.products);
834
- return;
861
+ return null;
835
862
  }
836
863
  const strategyConfigs = this.promotionEvaluator.getStrategyConfigs?.();
837
864
  if (Array.isArray(strategyConfigs) && strategyConfigs.length === 0) {
838
- return;
865
+ return null;
839
866
  }
840
867
  this.isApplyingPromotion = true;
841
868
  try {
@@ -927,14 +954,46 @@ class OrderModule extends _BaseModule.BaseModule {
927
954
  })),
928
955
  products: tempOrder.products
929
956
  };
930
- this.effectsEmit('onPromotionApplied', payload);
957
+ return payload;
931
958
  } catch (error) {
932
959
  console.error('[OrderModule] applyPromotion failed', error);
960
+ return null;
933
961
  } finally {
934
962
  this.isApplyingPromotion = false;
935
963
  }
936
964
  }
937
965
 
966
+ /** 发布促销稳定态;products 必须读取折扣计算后的当前数组。 */
967
+ emitPromotionApplied(payload) {
968
+ if (!payload) return;
969
+ const products = this.store.tempOrder?.products || payload.products;
970
+ this.isEmittingPromotionApplied = true;
971
+ try {
972
+ void this.effectsEmit('onPromotionApplied', {
973
+ ...payload,
974
+ products
975
+ });
976
+ } finally {
977
+ // effectsEmit 同步调用监听器后返回 Promise;保持既有的非阻塞事件行为。
978
+ this.isEmittingPromotionApplied = false;
979
+ }
980
+ }
981
+
982
+ /** 公开入口:促销、折扣、summary 与持久化完成后发布稳定态事件。 */
983
+ async applyPromotion() {
984
+ const payload = await this.evaluatePromotion();
985
+ if (!payload) return;
986
+ const tempOrder = this.store.tempOrder;
987
+ if (!tempOrder) return;
988
+ this.applyDiscount();
989
+ this.sanitizeTempOrderProducts(tempOrder);
990
+ await this.recalculateSummary({
991
+ createIfMissing: true
992
+ });
993
+ this.persistTempOrder();
994
+ this.emitPromotionApplied(payload);
995
+ }
996
+
938
997
  /** 最近一次 applyPromotion 输出的未满足促销列表 */
939
998
  getUnfulfilledPromotions() {
940
999
  return this.lastUnfulfilledPromotions;
@@ -1038,6 +1097,7 @@ class OrderModule extends _BaseModule.BaseModule {
1038
1097
  this.store.tempOrder = {
1039
1098
  ...this.store.tempOrder,
1040
1099
  products: [...this.store.tempOrder.products],
1100
+ notes: [...(this.store.tempOrder.notes || [])],
1041
1101
  bookings: this.store.tempOrder.bookings ? [...this.store.tempOrder.bookings] : []
1042
1102
  };
1043
1103
  this.persistTempOrder();
@@ -1050,27 +1110,41 @@ class OrderModule extends _BaseModule.BaseModule {
1050
1110
  now: (0, _utils.formatDateTime)(new Date())
1051
1111
  });
1052
1112
  }
1053
- clearTempOrderPointCardSnapshot(tempOrder) {
1113
+ clearTempOrderCustomerWalletSnapshots(tempOrder) {
1054
1114
  const orderMetaList = (0, _utils.normalizeOrderMetaList)(tempOrder.order_meta_list);
1055
1115
  if (!orderMetaList) return;
1056
1116
  const nextOrderMetaList = {
1057
1117
  ...orderMetaList
1058
1118
  };
1059
1119
  delete nextOrderMetaList.point_cards_snapshot;
1120
+ delete nextOrderMetaList.virtual_currency_snapshot;
1060
1121
  if (Object.keys(nextOrderMetaList).length === 0) {
1061
1122
  delete tempOrder.order_meta_list;
1062
1123
  return;
1063
1124
  }
1064
1125
  tempOrder.order_meta_list = nextOrderMetaList;
1065
1126
  }
1066
- writePointCardSnapshotFromPrepareConfig(params) {
1127
+ clearCustomerWalletState(tempOrder) {
1128
+ this.clearCustomerWalletRuntimeState();
1129
+ this.clearTempOrderCustomerWalletSnapshots(tempOrder);
1130
+ }
1131
+ clearCustomerWalletRuntimeState() {
1132
+ this.customerWalletPrepareConfigEpoch += 1;
1133
+ this.store.availableWalletIds = [];
1134
+ this.store.virtualCurrencyList = [];
1135
+ }
1136
+ writeCustomerWalletSnapshotsFromPrepareConfig(params) {
1067
1137
  const tempOrder = this.store.tempOrder;
1068
1138
  if (!tempOrder) return;
1069
1139
  if (String(tempOrder.customer_id ?? '') !== String(params.customerId)) return;
1140
+ const virtualCurrencyList = Array.isArray(params.virtualCurrencyList) ? params.virtualCurrencyList : [];
1070
1141
  tempOrder.order_meta_list = {
1071
1142
  ...((0, _utils.normalizeOrderMetaList)(tempOrder.order_meta_list) || {}),
1072
- point_cards_snapshot: (0, _utils.buildPointCardSnapshotFromWalletPassList)(params.availableWalletPassList)
1143
+ point_cards_snapshot: (0, _utils.buildPointCardSnapshotFromWalletPassList)(params.availableWalletPassList),
1144
+ virtual_currency_snapshot: (0, _utils.buildVirtualCurrencySnapshotFromList)(virtualCurrencyList)
1073
1145
  };
1146
+ this.store.availableWalletIds = [...params.availableWalletIds];
1147
+ this.store.virtualCurrencyList = (0, _lodashEs.cloneDeep)(virtualCurrencyList);
1074
1148
  this.persistTempOrder();
1075
1149
  this.saveDraftInBackground();
1076
1150
  }
@@ -1855,6 +1929,7 @@ class OrderModule extends _BaseModule.BaseModule {
1855
1929
  const freshTempOrder = this.createDefaultTempOrderInstance();
1856
1930
  this.ensureExternalSaleNumber(freshTempOrder);
1857
1931
  this.store.tempOrder = freshTempOrder;
1932
+ this.clearCustomerWalletRuntimeState();
1858
1933
  this.store.summary = (0, _utils.createEmptySummary)();
1859
1934
  this.store.lastOrderInfo = undefined;
1860
1935
  this.store.syncState = undefined;
@@ -1875,6 +1950,7 @@ class OrderModule extends _BaseModule.BaseModule {
1875
1950
  ensureIdentity: false
1876
1951
  });
1877
1952
  this.store.tempOrder = nextTempOrder;
1953
+ this.clearCustomerWalletRuntimeState();
1878
1954
  if (options?.recalculateSummary !== false) {
1879
1955
  await this.recalculateSummary({
1880
1956
  createIfMissing: false
@@ -2398,6 +2474,179 @@ class OrderModule extends _BaseModule.BaseModule {
2398
2474
  getTempOrderNote() {
2399
2475
  return this.store.tempOrder?.note || '';
2400
2476
  }
2477
+ ensureSalesNotesRuntimeState(tempOrder) {
2478
+ const hadNotesSnapshot = Array.isArray(tempOrder.notes);
2479
+ if (!Array.isArray(tempOrder.notes)) tempOrder.notes = [];
2480
+ if (!tempOrder._extend || typeof tempOrder._extend !== 'object') {
2481
+ tempOrder._extend = {
2482
+ productsByUid: {}
2483
+ };
2484
+ }
2485
+ if (!tempOrder._extend.salesNotesState) {
2486
+ tempOrder._extend.salesNotesState = {
2487
+ loaded: hadNotesSnapshot,
2488
+ dirty: false
2489
+ };
2490
+ }
2491
+ return tempOrder._extend.salesNotesState;
2492
+ }
2493
+ ensureSalesNotesLoadedForMutation(tempOrder) {
2494
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2495
+ if (state.loaded) return;
2496
+ if (tempOrder.order_id === null || tempOrder.order_id === undefined) {
2497
+ state.loaded = true;
2498
+ tempOrder.notes = [];
2499
+ return;
2500
+ }
2501
+ throw new Error('[Order Notes] 当前订单 Notes 尚未完整加载,禁止提交部分快照');
2502
+ }
2503
+ normalizeSalesNoteUuid(value, field) {
2504
+ const uuid = String(value ?? '').trim();
2505
+ if (!uuid) throw new Error(`[Order Notes] ${field} 不能为空`);
2506
+ if (uuid.length > 255) {
2507
+ throw new Error(`[Order Notes] ${field} 不能超过 255 个字符`);
2508
+ }
2509
+ return uuid;
2510
+ }
2511
+ getSalesNotePrimaryRelation(note) {
2512
+ return note.note_relations.find(relation => relation.is_primary === 1) || null;
2513
+ }
2514
+ removeSalesNotesForMissingProductLines(tempOrder) {
2515
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2516
+ if (!state.loaded || !Array.isArray(tempOrder.notes) || tempOrder.notes.length === 0) {
2517
+ return;
2518
+ }
2519
+ 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));
2520
+ const nextNotes = tempOrder.notes.filter(note => {
2521
+ const primary = this.getSalesNotePrimaryRelation(note);
2522
+ if (!primary || primary.target_type !== 'order_detail') return true;
2523
+ return remainingProductUids.has(primary.target_uuid);
2524
+ });
2525
+ if (nextNotes.length === tempOrder.notes.length) return;
2526
+ tempOrder.notes = nextNotes;
2527
+ state.dirty = true;
2528
+ }
2529
+ setSalesNoteOnTempOrder(tempOrder, input) {
2530
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
2531
+ const note_type = String(input.note_type || '').trim();
2532
+ const policy = (0, _salesNotes.getSalesNotePolicy)(note_type);
2533
+ if (!policy) {
2534
+ throw new Error(`[Order Notes] 当前未注册备注类型: ${note_type || '(empty)'}`);
2535
+ }
2536
+ if (input.target?.target_type !== 'order_detail') {
2537
+ throw new Error('[Order Notes] 本期只支持 order_detail 作为主体');
2538
+ }
2539
+ const target_uuid = this.normalizeSalesNoteUuid(input.target.target_uuid, 'target_uuid');
2540
+ const targetProductExists = (tempOrder.products || []).some(product => {
2541
+ const productLineUid = product.metadata?.unique_identification_number || product.unique_identification_number;
2542
+ return String(productLineUid ?? '') === target_uuid;
2543
+ });
2544
+ if (!targetProductExists) {
2545
+ throw new Error('[Order Notes] 目标商品行不存在');
2546
+ }
2547
+ const text = (0, _salesNotes.buildSalesNoteText)(input.text);
2548
+ const notes = tempOrder.notes || [];
2549
+ // 当前 allergy policy 固定同一商品行只保留一条;业务调用方不参与去重规则。
2550
+ const existingIndex = policy.uniquePerPrimaryTarget ? notes.findIndex(note => {
2551
+ const primary = this.getSalesNotePrimaryRelation(note);
2552
+ return note.note_type === note_type && primary?.target_type === 'order_detail' && primary.target_uuid === target_uuid;
2553
+ }) : -1;
2554
+ const existing = existingIndex >= 0 ? notes[existingIndex] : null;
2555
+ const existingPrimary = existing?.note_relations.find(relation => relation.is_primary === 1 && relation.target_type === 'order_detail');
2556
+ const existingOrderRelation = existing?.note_relations.find(relation => relation.is_primary === 0 && relation.target_type === 'order');
2557
+ const note = {
2558
+ uuid: existing?.uuid || (0, _utils.createUuidV4)(),
2559
+ note_type: policy.note_type,
2560
+ content: {
2561
+ text
2562
+ },
2563
+ importance: policy.importance,
2564
+ note_relations: [{
2565
+ uuid: existingPrimary?.uuid || (0, _utils.createUuidV4)(),
2566
+ relation_type: policy.primary_relation_type,
2567
+ target_type: 'order_detail',
2568
+ target_uuid,
2569
+ is_primary: 1
2570
+ }, {
2571
+ uuid: existingOrderRelation?.uuid || (0, _utils.createUuidV4)(),
2572
+ relation_type: policy.secondary_order_relation_type,
2573
+ target_type: 'order',
2574
+ target_uuid: String(tempOrder.shop_order_number ?? ''),
2575
+ is_primary: 0
2576
+ }]
2577
+ };
2578
+ const duplicateIndex = notes.findIndex((item, index) => item.uuid === note.uuid && index !== existingIndex);
2579
+ if (duplicateIndex >= 0) {
2580
+ throw new Error('[Order Notes] uuid 已被其他 Note 使用');
2581
+ }
2582
+ tempOrder.notes = existingIndex >= 0 ? notes.map((item, index) => index === existingIndex ? note : item) : [...notes, note];
2583
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2584
+ state.loaded = true;
2585
+ state.dirty = true;
2586
+ return note;
2587
+ }
2588
+ getSalesNotes() {
2589
+ const tempOrder = this.ensureTempOrder();
2590
+ this.ensureSalesNotesRuntimeState(tempOrder);
2591
+ return (0, _lodashEs.cloneDeep)(tempOrder.notes || []);
2592
+ }
2593
+ createSalesNotesMutationSnapshot() {
2594
+ const tempOrder = this.ensureTempOrder();
2595
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2596
+ return {
2597
+ orderIdentity: {
2598
+ orderId: tempOrder.order_id ?? null,
2599
+ externalSaleNumber: String(tempOrder.external_sale_number || '')
2600
+ },
2601
+ notes: (0, _lodashEs.cloneDeep)(tempOrder.notes || []),
2602
+ state: (0, _lodashEs.cloneDeep)(state)
2603
+ };
2604
+ }
2605
+ restoreSalesNotesMutationSnapshot(snapshot) {
2606
+ if (!snapshot || !Array.isArray(snapshot.notes) || !snapshot.orderIdentity || typeof snapshot.orderIdentity.externalSaleNumber !== 'string' || typeof snapshot.state?.loaded !== 'boolean' || typeof snapshot.state?.dirty !== 'boolean') {
2607
+ throw new Error('[Order Notes] 无效的 mutation snapshot');
2608
+ }
2609
+ const tempOrder = this.store.tempOrder;
2610
+ if (!tempOrder) throw new Error('[Order Notes] 当前订单不存在');
2611
+ const snapshotOrderId = snapshot.orderIdentity.orderId;
2612
+ const currentOrderId = tempOrder.order_id;
2613
+ const hasSnapshotOrderId = snapshotOrderId !== null && snapshotOrderId !== undefined && String(snapshotOrderId).trim().length > 0;
2614
+ const hasCurrentOrderId = currentOrderId !== null && currentOrderId !== undefined && String(currentOrderId).trim().length > 0;
2615
+ const isSameOrder = hasSnapshotOrderId && hasCurrentOrderId ? String(snapshotOrderId) === String(currentOrderId) : Boolean(snapshot.orderIdentity.externalSaleNumber) && snapshot.orderIdentity.externalSaleNumber === String(tempOrder.external_sale_number || '');
2616
+ if (!isSameOrder) {
2617
+ throw new Error('[Order Notes] 当前订单已切换,拒绝恢复旧订单快照');
2618
+ }
2619
+ this.ensureSalesNotesRuntimeState(tempOrder);
2620
+ tempOrder.notes = (0, _lodashEs.cloneDeep)(snapshot.notes);
2621
+ tempOrder._extend.salesNotesState = (0, _lodashEs.cloneDeep)(snapshot.state);
2622
+ this.notifyTempOrderChanged();
2623
+ }
2624
+ async setSalesNote(input) {
2625
+ const tempOrder = this.ensureTempOrder();
2626
+ const note = this.setSalesNoteOnTempOrder(tempOrder, input);
2627
+ this.notifyTempOrderChanged();
2628
+ return (0, _lodashEs.cloneDeep)(note);
2629
+ }
2630
+ async removeSalesNote(uuid) {
2631
+ const tempOrder = this.ensureTempOrder();
2632
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
2633
+ const normalizedUuid = this.normalizeSalesNoteUuid(uuid, 'uuid');
2634
+ const notes = tempOrder.notes || [];
2635
+ const nextNotes = notes.filter(note => note.uuid !== normalizedUuid);
2636
+ if (nextNotes.length === notes.length) return;
2637
+ tempOrder.notes = nextNotes;
2638
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2639
+ state.dirty = true;
2640
+ this.notifyTempOrderChanged();
2641
+ }
2642
+ async clearSalesNotes() {
2643
+ const tempOrder = this.ensureTempOrder();
2644
+ tempOrder.notes = [];
2645
+ const state = this.ensureSalesNotesRuntimeState(tempOrder);
2646
+ state.loaded = true;
2647
+ state.dirty = true;
2648
+ this.notifyTempOrderChanged();
2649
+ }
2401
2650
  updateTempOrderShopDiscount(amount) {
2402
2651
  const tempOrder = this.ensureTempOrder();
2403
2652
  const discount = _decimal.default.max(new _decimal.default(Number(amount) || 0), 0).toDecimalPlaces(2).toFixed(2);
@@ -2437,6 +2686,31 @@ class OrderModule extends _BaseModule.BaseModule {
2437
2686
  this.persistTempOrder();
2438
2687
  return tempOrder.buzzer;
2439
2688
  }
2689
+ updateTempOrderShopServiceData(patch) {
2690
+ const tempOrder = this.ensureTempOrder();
2691
+ const metadata = tempOrder.metadata && typeof tempOrder.metadata === 'object' && !Array.isArray(tempOrder.metadata) ? tempOrder.metadata : {};
2692
+ const currentShopServiceData = metadata.shop_service_data && typeof metadata.shop_service_data === 'object' && !Array.isArray(metadata.shop_service_data) ? metadata.shop_service_data : {};
2693
+ const shopServiceData = {
2694
+ ...currentShopServiceData,
2695
+ ...patch
2696
+ };
2697
+ tempOrder.metadata = {
2698
+ ...metadata,
2699
+ shop_service_data: shopServiceData
2700
+ };
2701
+ this.persistTempOrder();
2702
+ return shopServiceData;
2703
+ }
2704
+ updateTempOrderPickupReferenceMode(mode) {
2705
+ return this.updateTempOrderShopServiceData({
2706
+ pickup_reference_mode: mode
2707
+ });
2708
+ }
2709
+ updateTempOrderServiceType(serviceType) {
2710
+ return this.updateTempOrderShopServiceData({
2711
+ service_type: serviceType
2712
+ });
2713
+ }
2440
2714
  updateTempOrderContactsInfo(contactsInfo) {
2441
2715
  const tempOrder = this.ensureTempOrder();
2442
2716
  tempOrder.contacts_info = contactsInfo && typeof contactsInfo === 'object' && !Array.isArray(contactsInfo) ? contactsInfo : null;
@@ -2547,8 +2821,7 @@ class OrderModule extends _BaseModule.BaseModule {
2547
2821
  });
2548
2822
  const nextCustomerId = tempOrder.customer_id ?? null;
2549
2823
  if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
2550
- this.store.availableWalletIds = [];
2551
- this.clearTempOrderPointCardSnapshot(tempOrder);
2824
+ this.clearCustomerWalletState(tempOrder);
2552
2825
  }
2553
2826
  this.persistTempOrder();
2554
2827
  return {
@@ -2584,9 +2857,7 @@ class OrderModule extends _BaseModule.BaseModule {
2584
2857
  }
2585
2858
  const nextCustomerId = tempOrder.customer_id ?? null;
2586
2859
  if (String(previousCustomerId ?? '') !== String(nextCustomerId ?? '')) {
2587
- this.store.availableWalletIds = [];
2588
- this.store.virtualCurrencyList = [];
2589
- this.clearTempOrderPointCardSnapshot(tempOrder);
2860
+ this.clearCustomerWalletState(tempOrder);
2590
2861
  (0, _utils.clearTempOrderHolderAssignments)(tempOrder);
2591
2862
  this.clearCustomerBoundDiscounts(tempOrder);
2592
2863
  this.applyDiscount();
@@ -2624,9 +2895,7 @@ class OrderModule extends _BaseModule.BaseModule {
2624
2895
  tempOrder.email = '';
2625
2896
  tempOrder.customer = null;
2626
2897
  if (tempOrder._extend) delete tempOrder._extend.customerSnapshot;
2627
- this.store.availableWalletIds = [];
2628
- this.store.virtualCurrencyList = [];
2629
- this.clearTempOrderPointCardSnapshot(tempOrder);
2898
+ this.clearCustomerWalletState(tempOrder);
2630
2899
  this.clearCustomerBoundDiscounts(tempOrder);
2631
2900
  this.applyDiscount();
2632
2901
  this.persistTempOrder();
@@ -3034,9 +3303,7 @@ class OrderModule extends _BaseModule.BaseModule {
3034
3303
  if (!options?.skipEditDiscountConfigRefresh && !isVirtualSettlement) {
3035
3304
  await this.ensureEditDiscountConfigForProductChange(tempOrder);
3036
3305
  }
3037
- if (!options?.skipDiscountRecalculation && !isVirtualSettlement) {
3038
- await this.applyPromotion();
3039
- }
3306
+ const promotionPayload = !options?.skipDiscountRecalculation && !isVirtualSettlement ? await this.evaluatePromotion() : null;
3040
3307
  if (!options?.skipDiscountRecalculation && !isVirtualSettlement) {
3041
3308
  this.applyDiscount();
3042
3309
  }
@@ -3045,6 +3312,7 @@ class OrderModule extends _BaseModule.BaseModule {
3045
3312
  createIfMissing: true
3046
3313
  });
3047
3314
  this.persistTempOrder();
3315
+ this.emitPromotionApplied(promotionPayload);
3048
3316
  }
3049
3317
 
3050
3318
  /**
@@ -3127,6 +3395,7 @@ class OrderModule extends _BaseModule.BaseModule {
3127
3395
  const matchedProduct = matchedIndex === -1 ? null : tempOrder.products[matchedIndex];
3128
3396
  const existedFingerprint = matchedProduct ? (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(matchedProduct), matchedProduct.product_bundle) : '';
3129
3397
  const commitProductLine = shouldMerge => {
3398
+ let committedProduct;
3130
3399
  if (matchedIndex === -1 || !matchedProduct || !shouldMerge) {
3131
3400
  this.captureProductRuntime(tempOrder, productToAdd, normalizedIncoming);
3132
3401
  if (linked) {
@@ -3142,6 +3411,7 @@ class OrderModule extends _BaseModule.BaseModule {
3142
3411
  } else {
3143
3412
  tempOrder.products = [...tempOrder.products, normalizedIncoming];
3144
3413
  }
3414
+ committedProduct = normalizedIncoming;
3145
3415
  } else {
3146
3416
  const targetProduct = matchedProduct;
3147
3417
  const targetUid = targetProduct.metadata?.unique_identification_number;
@@ -3177,6 +3447,7 @@ class OrderModule extends _BaseModule.BaseModule {
3177
3447
  num: nextNum
3178
3448
  };
3179
3449
  this.captureProductRuntime(tempOrder, productToAdd, tempOrder.products[matchedIndex], targetUid);
3450
+ committedProduct = tempOrder.products[matchedIndex];
3180
3451
  if (matchedIndex !== tempOrder.products.length - 1) {
3181
3452
  const mergedProduct = tempOrder.products[matchedIndex];
3182
3453
  tempOrder.products = [...tempOrder.products.slice(0, matchedIndex), ...tempOrder.products.slice(matchedIndex + 1), mergedProduct];
@@ -3185,10 +3456,10 @@ class OrderModule extends _BaseModule.BaseModule {
3185
3456
  if (linked) {
3186
3457
  tempOrder.bookings = [...(tempOrder.bookings || []), linked.booking];
3187
3458
  }
3459
+ return committedProduct;
3188
3460
  };
3189
3461
  if (!matchedProduct) {
3190
- commitProductLine(false);
3191
- return;
3462
+ return commitProductLine(false);
3192
3463
  }
3193
3464
  const shouldMerge = this.shouldMergeProductToOrder({
3194
3465
  incomingProduct: productToAdd,
@@ -3203,7 +3474,7 @@ class OrderModule extends _BaseModule.BaseModule {
3203
3474
  if (shouldMerge && typeof shouldMerge === 'object' && typeof shouldMerge.then === 'function') {
3204
3475
  return Promise.resolve(shouldMerge).then(commitProductLine);
3205
3476
  }
3206
- commitProductLine(shouldMerge);
3477
+ return commitProductLine(shouldMerge);
3207
3478
  }
3208
3479
 
3209
3480
  /**
@@ -3276,34 +3547,99 @@ class OrderModule extends _BaseModule.BaseModule {
3276
3547
  */
3277
3548
  async addProductToOrder(product, booking, options) {
3278
3549
  const tempOrder = this.ensureTempOrder();
3550
+ const salesNoteDrafts = (options?.salesNotes || []).map(draft => ({
3551
+ note_type: String(draft.note_type || '').trim(),
3552
+ text: {
3553
+ kind: 'resolved',
3554
+ text: (0, _salesNotes.buildSalesNoteText)(draft.text)
3555
+ }
3556
+ }));
3557
+ const mutationSnapshot = salesNoteDrafts.length > 0 ? {
3558
+ products: (0, _lodashEs.cloneDeep)(tempOrder.products || []),
3559
+ bookings: (0, _lodashEs.cloneDeep)(tempOrder.bookings || []),
3560
+ notes: (0, _lodashEs.cloneDeep)(tempOrder.notes || []),
3561
+ extend: (0, _lodashEs.cloneDeep)(tempOrder._extend || {})
3562
+ } : null;
3563
+ const attachSalesNoteDrafts = orderProduct => {
3564
+ if (!salesNoteDrafts.length) return;
3565
+ const productLineUid = orderProduct.metadata?.unique_identification_number || orderProduct.unique_identification_number;
3566
+ if (!productLineUid) {
3567
+ throw new Error('[Order Notes] 商品行缺少 unique_identification_number');
3568
+ }
3569
+ salesNoteDrafts.forEach(draft => {
3570
+ this.setSalesNoteOnTempOrder(tempOrder, {
3571
+ note_type: draft.note_type,
3572
+ target: {
3573
+ target_type: 'order_detail',
3574
+ target_uuid: String(productLineUid)
3575
+ },
3576
+ text: draft.text
3577
+ });
3578
+ });
3579
+ };
3580
+ const rollbackProductAndNotesMutation = () => {
3581
+ if (!mutationSnapshot) return;
3582
+ tempOrder.products = mutationSnapshot.products;
3583
+ tempOrder.bookings = mutationSnapshot.bookings;
3584
+ tempOrder.notes = mutationSnapshot.notes;
3585
+ tempOrder._extend = mutationSnapshot.extend;
3586
+ };
3279
3587
  const insertAfterProductUid = options?.insertAfterProductUid;
3280
3588
  const anchorIndex = booking && insertAfterProductUid ? tempOrder.products.findIndex(line => {
3281
3589
  const uid = line.metadata?.unique_identification_number || line.unique_identification_number;
3282
3590
  return uid !== undefined && uid !== null && String(uid) === String(insertAfterProductUid);
3283
3591
  }) : -1;
3284
3592
  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, {
3593
+ try {
3594
+ if (booking) {
3595
+ const productRecord = product;
3596
+ const splitCount = (0, _utils3.getSafeProductNum)(productRecord.num ?? productRecord.product_quantity ?? 1);
3597
+ if (splitCount > 1) {
3598
+ for (let i = 0; i < splitCount; i += 1) {
3599
+ const splitLine = this.createSplitBookingLine(productRecord, booking, i, splitCount);
3600
+ if (i > 0) {
3601
+ delete splitLine.product.unique_identification_number;
3602
+ delete splitLine.product.identity_key;
3603
+ if (splitLine.product.metadata) {
3604
+ splitLine.product.metadata = {
3605
+ ...splitLine.product.metadata
3606
+ };
3607
+ delete splitLine.product.metadata.unique_identification_number;
3608
+ delete splitLine.product.metadata.identity_key;
3609
+ }
3610
+ delete splitLine.booking.booking_uid;
3611
+ if (splitLine.booking.metadata) {
3612
+ splitLine.booking.metadata = {
3613
+ ...splitLine.booking.metadata
3614
+ };
3615
+ delete splitLine.booking.metadata.unique_identification_number;
3616
+ }
3617
+ }
3618
+ const committedProduct = await this.appendProductLineToTempOrder(tempOrder, splitLine.product, splitLine.booking, {
3619
+ insertAtIndex,
3620
+ disableMerge: options?.disableMerge
3621
+ });
3622
+ attachSalesNoteDrafts(committedProduct);
3623
+ if (insertAtIndex !== undefined) insertAtIndex += 1;
3624
+ }
3625
+ } else {
3626
+ const committedProduct = await this.appendProductLineToTempOrder(tempOrder, product, (0, _lodashEs.cloneDeep)(booking), {
3292
3627
  insertAtIndex,
3293
3628
  disableMerge: options?.disableMerge
3294
3629
  });
3295
- if (appendResult) await appendResult;
3296
- if (insertAtIndex !== undefined) insertAtIndex += 1;
3630
+ attachSalesNoteDrafts(committedProduct);
3297
3631
  }
3298
- await this.finalizeProductOrderMutation(tempOrder);
3299
- return tempOrder.products;
3632
+ } else {
3633
+ const committedProduct = await this.appendProductLineToTempOrder(tempOrder, product, undefined, {
3634
+ insertAtIndex,
3635
+ disableMerge: options?.disableMerge
3636
+ });
3637
+ attachSalesNoteDrafts(committedProduct);
3300
3638
  }
3639
+ } catch (error) {
3640
+ rollbackProductAndNotesMutation();
3641
+ throw error;
3301
3642
  }
3302
- const appendResult = this.appendProductLineToTempOrder(tempOrder, product, booking, {
3303
- insertAtIndex,
3304
- disableMerge: options?.disableMerge
3305
- });
3306
- if (appendResult) await appendResult;
3307
3643
  await this.finalizeProductOrderMutation(tempOrder);
3308
3644
  this.logInfo('addProductToOrder success', {
3309
3645
  product_id: product.product_id,
@@ -3626,7 +3962,7 @@ class OrderModule extends _BaseModule.BaseModule {
3626
3962
  async updateOrderProduct(params) {
3627
3963
  const tempOrder = this.ensureTempOrder();
3628
3964
  const updatedProductUid = this.applyOrderProductUpdateToTempOrder(tempOrder, params);
3629
- await this.applyPromotion();
3965
+ const promotionPayload = await this.evaluatePromotion();
3630
3966
  this.applyDiscount({
3631
3967
  reapplyBookingDiscountProductUids: params.allow_booking_discount_reapply && updatedProductUid ? [String(updatedProductUid)] : undefined
3632
3968
  });
@@ -3635,6 +3971,7 @@ class OrderModule extends _BaseModule.BaseModule {
3635
3971
  createIfMissing: true
3636
3972
  });
3637
3973
  this.persistTempOrder();
3974
+ this.emitPromotionApplied(promotionPayload);
3638
3975
  this.logInfo('updateOrderProduct success', {
3639
3976
  params
3640
3977
  });
@@ -3729,8 +4066,9 @@ class OrderModule extends _BaseModule.BaseModule {
3729
4066
  }
3730
4067
  tempOrder.products[productIndex] = normalizedProduct;
3731
4068
  this.prepareVirtualSettlementTempOrder(tempOrder);
3732
- if (!this.isActiveVirtualSettlementOrder(tempOrder)) {
3733
- await this.applyPromotion();
4069
+ const isVirtualSettlement = this.isActiveVirtualSettlementOrder(tempOrder);
4070
+ const promotionPayload = !isVirtualSettlement ? await this.evaluatePromotion() : null;
4071
+ if (!isVirtualSettlement) {
3734
4072
  this.applyDiscount();
3735
4073
  }
3736
4074
  this.sanitizeTempOrderProducts(tempOrder);
@@ -3738,6 +4076,7 @@ class OrderModule extends _BaseModule.BaseModule {
3738
4076
  createIfMissing: true
3739
4077
  });
3740
4078
  this.persistTempOrder();
4079
+ this.emitPromotionApplied(promotionPayload);
3741
4080
  this.logInfo('updateOrderProductQuantity success', {
3742
4081
  params
3743
4082
  });
@@ -3782,7 +4121,7 @@ class OrderModule extends _BaseModule.BaseModule {
3782
4121
  */
3783
4122
  async finalizeAfterProductsMutation(tempOrder, options) {
3784
4123
  this.syncTempOrderHolderFromBookings(tempOrder);
3785
- await this.applyPromotion();
4124
+ const promotionPayload = await this.evaluatePromotion();
3786
4125
  this.applyDiscount({
3787
4126
  reapplyBookingDiscountProductUids: options?.reapplyBookingDiscountProductUids
3788
4127
  });
@@ -3791,6 +4130,7 @@ class OrderModule extends _BaseModule.BaseModule {
3791
4130
  createIfMissing: true
3792
4131
  });
3793
4132
  this.persistTempOrder();
4133
+ this.emitPromotionApplied(promotionPayload);
3794
4134
  }
3795
4135
 
3796
4136
  /**
@@ -3801,10 +4141,12 @@ class OrderModule extends _BaseModule.BaseModule {
3801
4141
  */
3802
4142
  async removeProductsFromOrder(identities, options) {
3803
4143
  const tempOrder = this.ensureTempOrder();
3804
- this.repairDuplicateProductLineIdentities(tempOrder);
3805
4144
  if (!identities.length) {
3806
4145
  return tempOrder.products;
3807
4146
  }
4147
+ // 商品删除会同步删除以商品行为主关系的 Notes;快照不完整时禁止产生孤儿 Relation。
4148
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
4149
+ this.repairDuplicateProductLineIdentities(tempOrder);
3808
4150
  const productsBeforeRemove = tempOrder.products || [];
3809
4151
  const bookingsBeforeRemove = tempOrder.bookings || [];
3810
4152
  const preserveDisplayPosition = options?.preserveDisplayPosition;
@@ -3864,6 +4206,7 @@ class OrderModule extends _BaseModule.BaseModule {
3864
4206
  tempOrder.products = nextProductIndex >= 0 ? [...productsWithoutRemaining.slice(0, nextProductIndex), remainingProduct, ...productsWithoutRemaining.slice(nextProductIndex)] : [...productsWithoutRemaining, remainingProduct];
3865
4207
  }
3866
4208
  }
4209
+ this.removeSalesNotesForMissingProductLines(tempOrder);
3867
4210
  await this.finalizeAfterProductsMutation(tempOrder);
3868
4211
  this.logInfo('removeProductsFromOrder success', {
3869
4212
  identities
@@ -3876,13 +4219,15 @@ class OrderModule extends _BaseModule.BaseModule {
3876
4219
 
3877
4220
  /**
3878
4221
  * 仅清空购物车行(products / bookings),不重置整单。
3879
- * 用于「仅清空商品」;客户、备注、支付等协议字段保持不变。
4222
+ * 普通整单 note 保持不变;以商品行为主体的 Sales Notes 同步删除。
3880
4223
  */
3881
4224
  async clearOrderCartLines() {
3882
4225
  const tempOrder = this.ensureTempOrder();
4226
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
3883
4227
  tempOrder.products = [];
3884
4228
  tempOrder.bookings = [];
3885
4229
  (0, _virtualSettlement.releaseTempOrderSettlementState)(tempOrder);
4230
+ this.removeSalesNotesForMissingProductLines(tempOrder);
3886
4231
  this.syncTempOrderHolderFromBookings(tempOrder);
3887
4232
  if (tempOrder._extend && typeof tempOrder._extend === 'object') {
3888
4233
  tempOrder._extend = {
@@ -3890,13 +4235,14 @@ class OrderModule extends _BaseModule.BaseModule {
3890
4235
  productsByUid: {}
3891
4236
  };
3892
4237
  }
3893
- await this.applyPromotion();
4238
+ const promotionPayload = await this.evaluatePromotion();
3894
4239
  this.applyDiscount();
3895
4240
  this.sanitizeTempOrderProducts(tempOrder);
3896
4241
  await this.recalculateSummary({
3897
4242
  createIfMissing: true
3898
4243
  });
3899
4244
  this.persistTempOrder();
4245
+ this.emitPromotionApplied(promotionPayload);
3900
4246
  this.logInfo('clearOrderCartLines success', {});
3901
4247
  return tempOrder;
3902
4248
  }
@@ -3906,8 +4252,10 @@ class OrderModule extends _BaseModule.BaseModule {
3906
4252
  */
3907
4253
  async clearOrderForSettlementSwitch() {
3908
4254
  const tempOrder = this.ensureTempOrder();
4255
+ this.ensureSalesNotesLoadedForMutation(tempOrder);
3909
4256
  tempOrder.products = [];
3910
4257
  tempOrder.bookings = [];
4258
+ this.removeSalesNotesForMissingProductLines(tempOrder);
3911
4259
  tempOrder.discount_list = [];
3912
4260
  tempOrder.payments = [];
3913
4261
  tempOrder.refunds = [];
@@ -4050,6 +4398,7 @@ class OrderModule extends _BaseModule.BaseModule {
4050
4398
  })) || this.store.summary || (0, _utils.createEmptySummary)();
4051
4399
  const effectiveCacheId = params?.cacheId ?? this.cacheId;
4052
4400
  const hasPaymentStatusOverride = params?.paymentStatus !== undefined;
4401
+ const shouldOverridePaymentStatus = hasPaymentStatusOverride && !isOnlineStorePlatform && !shouldConfirmPendingVoucherPayments;
4053
4402
  const hasSmallTicketDataFlagOverride = params?.smallTicketDataFlag !== undefined;
4054
4403
  const enhancePayload = hasPaymentOverride || hasPaymentStatusOverride || hasSmallTicketDataFlagOverride || params?.enhancePayload ? (payload, ctx) => {
4055
4404
  const nextPayload = {
@@ -4057,7 +4406,7 @@ class OrderModule extends _BaseModule.BaseModule {
4057
4406
  ...(hasPaymentOverride ? {
4058
4407
  payments: preparedPaymentOverride || []
4059
4408
  } : {}),
4060
- ...(hasPaymentStatusOverride && !isOnlineStorePlatform && !shouldConfirmPendingVoucherPayments ? {
4409
+ ...(shouldOverridePaymentStatus ? {
4061
4410
  payment_status: params?.paymentStatus
4062
4411
  } : {}),
4063
4412
  ...(hasSmallTicketDataFlagOverride ? {
@@ -4124,6 +4473,12 @@ class OrderModule extends _BaseModule.BaseModule {
4124
4473
  this.logSubmitBackendRejected(result, payload);
4125
4474
  return result;
4126
4475
  }
4476
+ const isLocalPendingSubmit = this.isLocalPendingSubmitResult(result);
4477
+ // need_sync=1 仅表示已进入本地同步队列,不能视为远端已确认 Notes。
4478
+ if (Object.prototype.hasOwnProperty.call(payload, 'notes') && !isLocalPendingSubmit) {
4479
+ const salesNotesState = this.ensureSalesNotesRuntimeState(tempOrder);
4480
+ salesNotesState.dirty = false;
4481
+ }
4127
4482
  const submittedOrderId = this.extractOrderIdFromSubmitResult(result);
4128
4483
  this.logInfo('runSubmitTempOrder: 提交成功', {
4129
4484
  submittedOrderId,
@@ -4137,9 +4492,10 @@ class OrderModule extends _BaseModule.BaseModule {
4137
4492
  tempOrder.order_id = submittedOrderId;
4138
4493
  const resultRecord = result;
4139
4494
  await this.markLocalOrderSynced(submittedOrderId, resultRecord?.data || resultRecord || {});
4140
- } else if (this.isLocalPendingSubmitResult(result)) {
4495
+ }
4496
+ if (isLocalPendingSubmit) {
4141
4497
  this.store.syncState = 'local';
4142
- } else {
4498
+ } else if (submittedOrderId === null || submittedOrderId === undefined) {
4143
4499
  this.store.syncState = 'submitted';
4144
4500
  }
4145
4501
  return result;
@@ -4535,7 +4891,7 @@ class OrderModule extends _BaseModule.BaseModule {
4535
4891
  params.forceRemote = false;
4536
4892
  }
4537
4893
  const res = await this.request.get(`/order/sales/${encodeURIComponent(lookup)}`, {
4538
- with: ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info', 'order_meta_list', 'refunds'],
4894
+ with: ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info', 'order_meta_list', 'refunds', 'notes'],
4539
4895
  ...(params.forceRemote ? {
4540
4896
  forceRemote: true
4541
4897
  } : {})
@@ -4588,9 +4944,11 @@ class OrderModule extends _BaseModule.BaseModule {
4588
4944
  const isMergedSalesDetailHydrate = options?.source === 'mergedSalesDetail';
4589
4945
  const nextTempOrder = this.normalizeTempOrderForRuntime(raw, {
4590
4946
  // merge 前已只给远端商品补标记;这里不能把本地未提交商品一并变成 saved item。
4591
- makeEditMark: !isSessionStorageHydrate && !isMergedSalesDetailHydrate
4947
+ makeEditMark: !isSessionStorageHydrate && !isMergedSalesDetailHydrate,
4948
+ allowUnresolvedSalesNotes: isSessionStorageHydrate
4592
4949
  });
4593
4950
  this.store.tempOrder = nextTempOrder;
4951
+ this.clearCustomerWalletRuntimeState();
4594
4952
  if (isSessionStorageHydrate) {
4595
4953
  // 会话快照是未提交购物车,不应获得编辑订单的 saved/edit 标记与服务端基线。
4596
4954
  const restoredSessionDiscounts = Array.isArray(raw.discount_list) ? (0, _lodashEs.cloneDeep)(raw.discount_list) : [];
@@ -4810,6 +5168,17 @@ class OrderModule extends _BaseModule.BaseModule {
4810
5168
  nextTempOrder.payments = Array.isArray(raw.payments) ? raw.payments.map(p => ({
4811
5169
  ...(p || {})
4812
5170
  })) : [];
5171
+ const rawHasSalesNotes = Object.prototype.hasOwnProperty.call(raw, 'notes');
5172
+ const restoredSalesNotesState = raw._extend?.salesNotesState;
5173
+ const allowUnresolvedSalesNotes = options?.allowUnresolvedSalesNotes === true || restoredSalesNotesState?.dirty === true || Number(raw.is_draft_order || 0) === 1;
5174
+ const decodedSalesNotes = rawHasSalesNotes ? (0, _salesNotes.decodeSalesNotesSnapshot)(raw.notes, {
5175
+ allowUnresolvedOrderTarget: allowUnresolvedSalesNotes
5176
+ }) : {
5177
+ notes: [],
5178
+ complete: false
5179
+ };
5180
+ 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 === ''));
5181
+ nextTempOrder.notes = decodedSalesNotes.notes;
4813
5182
  nextTempOrder.surcharges = Array.isArray(raw.surcharges) ? raw.surcharges.map(s => ({
4814
5183
  ...(s || {})
4815
5184
  })) : [];
@@ -4830,6 +5199,10 @@ class OrderModule extends _BaseModule.BaseModule {
4830
5199
  productsByUid: {}
4831
5200
  };
4832
5201
  const tempOrderExtend = nextTempOrder._extend;
5202
+ tempOrderExtend.salesNotesState = {
5203
+ loaded: rawHasSalesNotes ? decodedSalesNotes.complete : restoredSalesNotesState?.loaded === true,
5204
+ dirty: decodedSalesNotes.complete && (restoredSalesNotesState?.dirty === true || allowUnresolvedSalesNotes && hasUnresolvedSalesNoteOrderTarget)
5205
+ };
4833
5206
  const productsByUid = tempOrderExtend.productsByUid || {};
4834
5207
  for (const [index, product] of nextTempOrder.products.entries()) {
4835
5208
  const uid = product.metadata?.unique_identification_number;
@@ -5021,7 +5394,7 @@ class OrderModule extends _BaseModule.BaseModule {
5021
5394
  }
5022
5395
  async getOrderInfo(order_id) {
5023
5396
  const res = await this.request.get(`/order/sales/${order_id}`, {
5024
- with: ['products', 'bookings']
5397
+ with: ['products', 'bookings', 'notes']
5025
5398
  }, {
5026
5399
  osServer: true
5027
5400
  });