@pisell/pisellos 2.3.75 → 2.3.77

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.
@@ -562,7 +562,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
562
562
  */
563
563
  hydrateTempOrderFromRecord(record: Record<string, any>, options?: {
564
564
  recalcOnHydrate?: boolean;
565
- source?: 'salesDetail' | 'sessionStorage';
565
+ source?: 'salesDetail' | 'mergedSalesDetail' | 'sessionStorage';
566
566
  }): Promise<OrderTempOrder>;
567
567
  /**
568
568
  * 兼容后端详情将 holder 放在 metadata.holder 的历史形态,统一补到运行时展示路径。
@@ -37,7 +37,7 @@ import { isNormalProduct } from "../Product/utils";
37
37
  import dayjs from 'dayjs';
38
38
  import { ensureOrderPaymentNumber, mergeOrderPaymentRecord, resolveOrderPaymentIdentity } from "./payment-utils";
39
39
  import { resolvePaymentNumberDevicePrefix, resolvePaymentNumberDevicePrefixFromDeviceId } from "../../utils/payment-number";
40
- import { processCartPromotion, appendPromotionTags as _appendPromotionTags, getOrderProductUid as getPromotionUid } from "../../solution/BaseSales/utils/cartPromotion";
40
+ import { processCartPromotion, appendPromotionTags as _appendPromotionTags, getOrderProductUid as getPromotionUid, PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY } from "../../solution/BaseSales/utils/cartPromotion";
41
41
  import { buildProductLineFingerprint, getProductIdentityIndex, getSafeProductNum, isIdentityMatch, normalizeOrderProduct } from "../../solution/ScanOrder/utils";
42
42
  import { hasManualProductDiscountFlag, syncManualProductDiscountProductNum } from "./utils/manualProductDiscount";
43
43
  import { expandHydratedProductsForDiscountCollection, restoreHydratedBundleDiscounts } from "./utils/bundleDiscountHydration";
@@ -2305,6 +2305,8 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
2305
2305
  }, {
2306
2306
  key: "restoreOriginalSnapshotIfProductsUnchanged",
2307
2307
  value: function restoreOriginalSnapshotIfProductsUnchanged(tempOrder, snapshot) {
2308
+ // merge hydrate 的商品集合包含本地未提交行,但 summary 仍来自远端旧订单,不能复用。
2309
+ if (snapshot.requiresFullSummaryRecalculation === true) return null;
2308
2310
  var currentFingerprint = this.buildOrderProductsFingerprint(tempOrder.products);
2309
2311
  var manualDepositOverride = this.getManualDepositOverride(tempOrder);
2310
2312
  if (currentFingerprint !== snapshot.productFingerprint) {
@@ -4018,8 +4020,12 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
4018
4020
  booking: booking
4019
4021
  }) : null;
4020
4022
  var productToAdd = (linked === null || linked === void 0 ? void 0 : linked.product) || product;
4021
- var incomingFingerprint = buildProductLineFingerprint(getProductSkuOptions(productToAdd), productToAdd.product_bundle);
4023
+ var disableMerge = (options === null || options === void 0 ? void 0 : options.disableMerge) === true;
4024
+ var incomingFingerprint = disableMerge ? '' : buildProductLineFingerprint(getProductSkuOptions(productToAdd), productToAdd.product_bundle);
4022
4025
  var normalizedIncoming = mergeOrderProductDisplayFields(productToAdd, normalizeOrderProduct(productToAdd));
4026
+ if (disableMerge) {
4027
+ normalizedIncoming.metadata = _objectSpread(_objectSpread({}, normalizedIncoming.metadata || {}), {}, _defineProperty({}, PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY, true));
4028
+ }
4023
4029
  var isWeighingProduct = Number(((_normalizedIncoming$p = normalizedIncoming.product_sku) === null || _normalizedIncoming$p === void 0 ? void 0 : _normalizedIncoming$p.open_sold_weight) || 0) === 1;
4024
4030
  var netWeight = Number((_normalizedIncoming$p2 = normalizedIncoming.product_sku) === null || _normalizedIncoming$p2 === void 0 ? void 0 : _normalizedIncoming$p2.net_weight);
4025
4031
  if (isWeighingProduct && (!Number.isFinite(netWeight) || netWeight <= 0)) {
@@ -4038,7 +4044,7 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
4038
4044
  var hasIncomingManualProductDiscount = isManualProductDiscountProduct(normalizedIncoming);
4039
4045
  var hasIncomingBookingUid = this.hasLinkedBookingUid(productToAdd) || this.hasLinkedBookingUid(normalizedIncoming);
4040
4046
  var isIncomingCustomItem = ((_productToAdd$metadat = productToAdd.metadata) === null || _productToAdd$metadat === void 0 || (_productToAdd$metadat = _productToAdd$metadat.origin) === null || _productToAdd$metadat === void 0 ? void 0 : _productToAdd$metadat.isCustomItem) === true || ((_origin = productToAdd._origin) === null || _origin === void 0 ? void 0 : _origin.isCustomItem) === true;
4041
- var shouldForceNewLine = !!booking || hasIncomingProductNote || hasIncomingManualProductDiscount || hasIncomingBookingUid || isIncomingCustomItem || isWeighingProduct;
4047
+ var shouldForceNewLine = disableMerge || !!booking || hasIncomingProductNote || hasIncomingManualProductDiscount || hasIncomingBookingUid || isIncomingCustomItem || isWeighingProduct;
4042
4048
  var matchedIndex = hasIncomingGoodPass || shouldForceNewLine ? -1 : tempOrder.products.findIndex(function (item) {
4043
4049
  var _item$metadata3;
4044
4050
  if (_this27.hasGoodPassDiscount(item)) return false;
@@ -4168,7 +4174,8 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
4168
4174
  singleLine.num = 1;
4169
4175
  delete singleLine.product_quantity;
4170
4176
  _appendResult = this.appendProductLineToTempOrder(tempOrder, singleLine, cloneDeep(booking), {
4171
- insertAtIndex: insertAtIndex
4177
+ insertAtIndex: insertAtIndex,
4178
+ disableMerge: options === null || options === void 0 ? void 0 : options.disableMerge
4172
4179
  });
4173
4180
  if (!_appendResult) {
4174
4181
  _context21.next = 17;
@@ -4189,7 +4196,8 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
4189
4196
  return _context21.abrupt("return", tempOrder.products);
4190
4197
  case 24:
4191
4198
  appendResult = this.appendProductLineToTempOrder(tempOrder, product, booking, {
4192
- insertAtIndex: insertAtIndex
4199
+ insertAtIndex: insertAtIndex,
4200
+ disableMerge: options === null || options === void 0 ? void 0 : options.disableMerge
4193
4201
  });
4194
4202
  if (!appendResult) {
4195
4203
  _context21.next = 28;
@@ -6013,7 +6021,7 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
6013
6021
  value: (function () {
6014
6022
  var _hydrateTempOrderFromRecord = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee42(record, options) {
6015
6023
  var _sourceRaw$_extend, _this$store$discount22;
6016
- var sourceRaw, duplicateProductIdentityRepair, duplicateIdentityReadySource, identityReadySource, normalizedCollections, raw, hasIdentityChanges, identityLogMetadata, hasIdentityWarnings, isSessionStorageHydrate, nextTempOrder, _this$store$discount20, _this$store$discount21, restoredSessionDiscounts, nextExtend, sourceLastOrderInfo, isDraftOrder, syntheticIdentityOptions, rawSummary, summarySurcharges, hadSyntheticDraftOrderId, hydratedEditDiscounts, currentDiscounts, currentScanDiscounts, hydratedDiscountIds, retainedScanDiscounts, nextDiscounts, shouldSkipEmptyDiscountSync, _this$store$discount23, _this$store$discount24;
6024
+ var sourceRaw, duplicateProductIdentityRepair, duplicateIdentityReadySource, identityReadySource, normalizedCollections, raw, hasIdentityChanges, identityLogMetadata, hasIdentityWarnings, isSessionStorageHydrate, isMergedSalesDetailHydrate, nextTempOrder, _this$store$discount20, _this$store$discount21, restoredSessionDiscounts, nextExtend, sourceLastOrderInfo, isDraftOrder, syntheticIdentityOptions, rawSummary, summarySurcharges, hadSyntheticDraftOrderId, hydratedEditDiscounts, currentDiscounts, currentScanDiscounts, hydratedDiscountIds, retainedScanDiscounts, nextDiscounts, shouldSkipEmptyDiscountSync, _this$store$discount23, _this$store$discount24;
6017
6025
  return _regeneratorRuntime().wrap(function _callee42$(_context45) {
6018
6026
  while (1) switch (_context45.prev = _context45.next) {
6019
6027
  case 0:
@@ -6048,12 +6056,14 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
6048
6056
  }
6049
6057
  }
6050
6058
  isSessionStorageHydrate = (options === null || options === void 0 ? void 0 : options.source) === 'sessionStorage';
6059
+ isMergedSalesDetailHydrate = (options === null || options === void 0 ? void 0 : options.source) === 'mergedSalesDetail';
6051
6060
  nextTempOrder = this.normalizeTempOrderForRuntime(raw, {
6052
- makeEditMark: !isSessionStorageHydrate
6061
+ // merge 前已只给远端商品补标记;这里不能把本地未提交商品一并变成 saved item。
6062
+ makeEditMark: !isSessionStorageHydrate && !isMergedSalesDetailHydrate
6053
6063
  });
6054
6064
  this.store.tempOrder = nextTempOrder;
6055
6065
  if (!isSessionStorageHydrate) {
6056
- _context45.next = 29;
6066
+ _context45.next = 30;
6057
6067
  break;
6058
6068
  }
6059
6069
  // 会话快照是未提交购物车,不应获得编辑订单的 saved/edit 标记与服务端基线。
@@ -6069,24 +6079,24 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
6069
6079
  nextTempOrder.discount_list = restoredSessionDiscounts;
6070
6080
  this.store.summary = createEmptySummary();
6071
6081
  this.store.lastOrderInfo = undefined;
6072
- _context45.next = 22;
6082
+ _context45.next = 23;
6073
6083
  return (_this$store$discount20 = this.store.discount) === null || _this$store$discount20 === void 0 ? void 0 : _this$store$discount20.setOriginalDiscountList(cloneDeep(restoredSessionDiscounts));
6074
- case 22:
6075
- _context45.next = 24;
6084
+ case 23:
6085
+ _context45.next = 25;
6076
6086
  return (_this$store$discount21 = this.store.discount) === null || _this$store$discount21 === void 0 ? void 0 : _this$store$discount21.setDiscountList(cloneDeep(restoredSessionDiscounts));
6077
- case 24:
6087
+ case 25:
6078
6088
  if (!(options !== null && options !== void 0 && options.recalcOnHydrate)) {
6079
- _context45.next = 27;
6089
+ _context45.next = 28;
6080
6090
  break;
6081
6091
  }
6082
- _context45.next = 27;
6092
+ _context45.next = 28;
6083
6093
  return this.recalculateSummary({
6084
6094
  createIfMissing: false
6085
6095
  });
6086
- case 27:
6096
+ case 28:
6087
6097
  this.persistTempOrder();
6088
6098
  return _context45.abrupt("return", nextTempOrder);
6089
- case 29:
6099
+ case 30:
6090
6100
  // Server list compatibility may synthesize order_id from external_sale_number; tempOrder must not.
6091
6101
  sourceLastOrderInfo = sourceRaw.lastOrderInfo || sourceRaw;
6092
6102
  isDraftOrder = Number(nextTempOrder.is_draft_order || 0) === 1;
@@ -6102,7 +6112,7 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
6102
6112
  surcharges: summarySurcharges
6103
6113
  });
6104
6114
  nextTempOrder._extend = _objectSpread(_objectSpread({}, nextTempOrder._extend || {}), {}, {
6105
- originalSalesSnapshot: {
6115
+ originalSalesSnapshot: _objectSpread({
6106
6116
  summary: cloneDeep(this.store.summary),
6107
6117
  products: cloneDeep(nextTempOrder.products || []),
6108
6118
  bookings: cloneDeep(nextTempOrder.bookings || []),
@@ -6110,7 +6120,9 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
6110
6120
  surcharges: cloneDeep(nextTempOrder.surcharges || []),
6111
6121
  shop_discount: nextTempOrder.shop_discount || '0.00',
6112
6122
  productFingerprint: this.buildOrderProductsFingerprint(nextTempOrder.products || [])
6113
- }
6123
+ }, isMergedSalesDetailHydrate ? {
6124
+ requiresFullSummaryRecalculation: true
6125
+ } : {})
6114
6126
  });
6115
6127
  this.store.lastOrderInfo = this.withoutSyntheticDraftOrderIdForRuntime(sourceLastOrderInfo, syntheticIdentityOptions);
6116
6128
  hadSyntheticDraftOrderId = this.isSyntheticDraftOrderIdForRuntime(raw, syntheticIdentityOptions) || this.isSyntheticDraftOrderIdForRuntime(sourceLastOrderInfo, syntheticIdentityOptions);
@@ -6131,28 +6143,28 @@ export var OrderModule = /*#__PURE__*/function (_BaseModule) {
6131
6143
  nextDiscounts = [].concat(_toConsumableArray(hydratedEditDiscounts), _toConsumableArray(retainedScanDiscounts));
6132
6144
  shouldSkipEmptyDiscountSync = nextDiscounts.length === 0 && currentDiscounts.length === 0;
6133
6145
  if (shouldSkipEmptyDiscountSync) {
6134
- _context45.next = 52;
6146
+ _context45.next = 53;
6135
6147
  break;
6136
6148
  }
6137
6149
  OrderModule.populateSavedAmounts(nextTempOrder.products, nextDiscounts);
6138
- _context45.next = 50;
6150
+ _context45.next = 51;
6139
6151
  return (_this$store$discount23 = this.store.discount) === null || _this$store$discount23 === void 0 ? void 0 : _this$store$discount23.setOriginalDiscountList(nextDiscounts);
6140
- case 50:
6141
- _context45.next = 52;
6152
+ case 51:
6153
+ _context45.next = 53;
6142
6154
  return (_this$store$discount24 = this.store.discount) === null || _this$store$discount24 === void 0 ? void 0 : _this$store$discount24.setDiscountList(nextDiscounts);
6143
- case 52:
6155
+ case 53:
6144
6156
  if (!(options !== null && options !== void 0 && options.recalcOnHydrate)) {
6145
- _context45.next = 55;
6157
+ _context45.next = 56;
6146
6158
  break;
6147
6159
  }
6148
- _context45.next = 55;
6160
+ _context45.next = 56;
6149
6161
  return this.recalculateSummary({
6150
6162
  createIfMissing: false
6151
6163
  });
6152
- case 55:
6164
+ case 56:
6153
6165
  this.persistTempOrder();
6154
6166
  return _context45.abrupt("return", nextTempOrder);
6155
- case 57:
6167
+ case 58:
6156
6168
  case "end":
6157
6169
  return _context45.stop();
6158
6170
  }
@@ -330,6 +330,8 @@ export type AddProductBookingInput = Record<string, any>;
330
330
  */
331
331
  export interface AddProductToOrderOptions {
332
332
  insertAfterProductUid?: string;
333
+ /** 跳过同商品/规格匹配,始终新增购物车行。 */
334
+ disableMerge?: boolean;
333
335
  }
334
336
  /**
335
337
  * 删除商品后的可选位置保持策略。
@@ -821,7 +823,7 @@ export interface OrderModuleAPI {
821
823
  saveDraft: () => Promise<void>;
822
824
  hydrateTempOrderFromRecord: (record: Record<string, any>, options?: {
823
825
  recalcOnHydrate?: boolean;
824
- source?: 'salesDetail' | 'sessionStorage';
826
+ source?: 'salesDetail' | 'mergedSalesDetail' | 'sessionStorage';
825
827
  }) => Promise<OrderTempOrder>;
826
828
  syncPaymentsToOrder: <T = any>(params: SyncPaymentsToOrderParams) => Promise<SyncPaymentsToOrderResult<T>>;
827
829
  getSalesOrderByLookup: (params: SalesOrderLookupParams) => Promise<any>;
@@ -370,6 +370,24 @@ function filterPendingPaymentsFromLoadedSalesDetail(sourceRecord) {
370
370
  }
371
371
  return record;
372
372
  }
373
+
374
+ /**
375
+ * merge hydrate 不会统一给所有商品补 saved 标记,因此先只标记远端已保存商品。
376
+ * 当前 tempOrder 中尚未提交的本地商品会在 merge 后继续保持 current item 语义。
377
+ */
378
+ function markLoadedSalesDetailProductsAsSaved(sourceRecord) {
379
+ if (!sourceRecord || _typeof(sourceRecord) !== 'object') return sourceRecord;
380
+ if (!Array.isArray(sourceRecord.products)) return sourceRecord;
381
+ return _objectSpread(_objectSpread({}, sourceRecord), {}, {
382
+ products: sourceRecord.products.map(function (product) {
383
+ return _objectSpread(_objectSpread({}, product || {}), {}, {
384
+ metadata: _objectSpread(_objectSpread({}, product !== null && product !== void 0 && product.metadata && _typeof(product.metadata) === 'object' ? product.metadata : {}), {}, {
385
+ is_edit_for_runtime: true
386
+ })
387
+ });
388
+ })
389
+ });
390
+ }
373
391
  export var BaseSalesImpl = /*#__PURE__*/function (_BaseModule) {
374
392
  _inherits(BaseSalesImpl, _BaseModule);
375
393
  var _super = _createSuper(BaseSalesImpl);
@@ -669,7 +687,7 @@ export var BaseSalesImpl = /*#__PURE__*/function (_BaseModule) {
669
687
  }, {
670
688
  key: "hydrateLatestLoadedSalesDetail",
671
689
  value: function () {
672
- var _hydrateLatestLoadedSalesDetail = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(record, loadSequence, hydrateSource) {
690
+ var _hydrateLatestLoadedSalesDetail = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(record, loadSequence, options) {
673
691
  var _this4 = this;
674
692
  var hydratedSales, skippedBeforeHydrate, hydrateTask, queuedTask;
675
693
  return _regeneratorRuntime().wrap(function _callee5$(_context5) {
@@ -679,7 +697,7 @@ export var BaseSalesImpl = /*#__PURE__*/function (_BaseModule) {
679
697
  skippedBeforeHydrate = false;
680
698
  hydrateTask = /*#__PURE__*/function () {
681
699
  var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4() {
682
- var sales;
700
+ var hydrateOptions, sales;
683
701
  return _regeneratorRuntime().wrap(function _callee4$(_context4) {
684
702
  while (1) switch (_context4.prev = _context4.next) {
685
703
  case 0:
@@ -696,30 +714,39 @@ export var BaseSalesImpl = /*#__PURE__*/function (_BaseModule) {
696
714
  }
697
715
  throw new Error('order 模块未初始化');
698
716
  case 5:
699
- _context4.next = 7;
700
- return _this4.store.order.hydrateTempOrderFromRecord(record, hydrateSource === 'sessionStorage' ? {
701
- recalcOnHydrate: true,
702
- source: 'sessionStorage'
703
- } : {
717
+ hydrateOptions = {
704
718
  recalcOnHydrate: false
705
- });
706
- case 7:
719
+ };
720
+ if ((options === null || options === void 0 ? void 0 : options.hydrateSource) === 'sessionStorage') {
721
+ hydrateOptions = {
722
+ recalcOnHydrate: true,
723
+ source: 'sessionStorage'
724
+ };
725
+ } else if (options !== null && options !== void 0 && options.merge) {
726
+ hydrateOptions = {
727
+ recalcOnHydrate: true,
728
+ source: 'mergedSalesDetail'
729
+ };
730
+ }
731
+ _context4.next = 9;
732
+ return _this4.store.order.hydrateTempOrderFromRecord(record, hydrateOptions);
733
+ case 9:
707
734
  sales = _context4.sent;
708
735
  hydratedSales = sales;
709
736
 
710
737
  // hydrate 期间可能又发起了更新请求;后续任务已串行排队,将负责最终状态。
711
738
  if (!(loadSequence !== _this4.salesDetailLoadSequence)) {
712
- _context4.next = 11;
739
+ _context4.next = 13;
713
740
  break;
714
741
  }
715
742
  return _context4.abrupt("return");
716
- case 11:
743
+ case 13:
717
744
  _this4.currentSalesDetail = sales;
718
- _context4.next = 14;
745
+ _context4.next = 16;
719
746
  return _this4.core.effects.emit("".concat(_this4.name, ":onSalesOrderLoaded"), {
720
747
  sales: sales
721
748
  });
722
- case 14:
749
+ case 16:
723
750
  case "end":
724
751
  return _context4.stop();
725
752
  }
@@ -1534,7 +1561,7 @@ export var BaseSalesImpl = /*#__PURE__*/function (_BaseModule) {
1534
1561
  key: "loadSalesDetail",
1535
1562
  value: (function () {
1536
1563
  var _loadSalesDetail = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee14(orderIdOrParams) {
1537
- var loadSequence, _ref27, _ref28, _ref29, _ref30, _params$orderId, params, externalSaleNumber, preloadedSalesDetail, lookup, loadedRecord, message, loadedSalesDetail, shouldFilterPendingPayments, remoteRecord, currentTempOrder, mergedRecord, record;
1564
+ var loadSequence, _ref27, _ref28, _ref29, _ref30, _params$orderId, params, externalSaleNumber, preloadedSalesDetail, lookup, loadedRecord, message, loadedSalesDetail, shouldFilterPendingPayments, remoteRecord, mergeSourceRecord, currentTempOrder, mergedRecord, record;
1538
1565
  return _regeneratorRuntime().wrap(function _callee14$(_context14) {
1539
1566
  while (1) switch (_context14.prev = _context14.next) {
1540
1567
  case 0:
@@ -1586,39 +1613,43 @@ export var BaseSalesImpl = /*#__PURE__*/function (_BaseModule) {
1586
1613
  loadedSalesDetail = preloadedSalesDetail !== null && preloadedSalesDetail !== void 0 ? preloadedSalesDetail : loadedRecord.data; // payment_pending 只存在于本地;远端详情需过滤,但会话快照必须完整恢复。
1587
1614
  shouldFilterPendingPayments = params.hydrateSource !== 'sessionStorage';
1588
1615
  remoteRecord = shouldFilterPendingPayments ? filterPendingPaymentsFromLoadedSalesDetail(loadedSalesDetail) : loadedSalesDetail;
1616
+ mergeSourceRecord = params.merge ? markLoadedSalesDetailProductsAsSaved(remoteRecord) : remoteRecord;
1589
1617
  currentTempOrder = params.merge ? this.store.order.getTempOrder() : null;
1590
- mergedRecord = params.merge ? mergeSalesDetailRecordWithTempOrder(currentTempOrder, remoteRecord, 'preserve-local') : remoteRecord;
1618
+ mergedRecord = params.merge ? mergeSalesDetailRecordWithTempOrder(currentTempOrder, mergeSourceRecord, 'preserve-local') : mergeSourceRecord;
1591
1619
  record = shouldFilterPendingPayments ? filterPendingPaymentsFromLoadedSalesDetail(mergedRecord) : mergedRecord;
1592
- _context14.next = 30;
1593
- return this.hydrateLatestLoadedSalesDetail(record, loadSequence, params.hydrateSource);
1594
- case 30:
1595
- return _context14.abrupt("return", _context14.sent);
1620
+ _context14.next = 31;
1621
+ return this.hydrateLatestLoadedSalesDetail(record, loadSequence, {
1622
+ hydrateSource: params.hydrateSource,
1623
+ merge: params.merge
1624
+ });
1596
1625
  case 31:
1597
- _context14.prev = 31;
1626
+ return _context14.abrupt("return", _context14.sent);
1627
+ case 32:
1628
+ _context14.prev = 32;
1598
1629
  this.salesDetailLoadInFlightCount = Math.max(0, this.salesDetailLoadInFlightCount - 1);
1599
1630
  if (!(this.salesDetailLoadInFlightCount === 0)) {
1600
- _context14.next = 42;
1631
+ _context14.next = 43;
1601
1632
  break;
1602
1633
  }
1603
- _context14.prev = 34;
1604
- _context14.next = 37;
1634
+ _context14.prev = 35;
1635
+ _context14.next = 38;
1605
1636
  return this.drainQueuedServerOrderChanges();
1606
- case 37:
1607
- _context14.next = 42;
1637
+ case 38:
1638
+ _context14.next = 43;
1608
1639
  break;
1609
- case 39:
1610
- _context14.prev = 39;
1611
- _context14.t1 = _context14["catch"](34);
1640
+ case 40:
1641
+ _context14.prev = 40;
1642
+ _context14.t1 = _context14["catch"](35);
1612
1643
  this.logError('drain queued server order changes failed', {
1613
1644
  error: _context14.t1 instanceof Error ? _context14.t1.message : String(_context14.t1)
1614
1645
  });
1615
- case 42:
1616
- return _context14.finish(31);
1617
1646
  case 43:
1647
+ return _context14.finish(32);
1648
+ case 44:
1618
1649
  case "end":
1619
1650
  return _context14.stop();
1620
1651
  }
1621
- }, _callee14, this, [[4,, 31, 43], [34, 39]]);
1652
+ }, _callee14, this, [[4,, 32, 44], [35, 40]]);
1622
1653
  }));
1623
1654
  function loadSalesDetail(_x13) {
1624
1655
  return _loadSalesDetail.apply(this, arguments);
@@ -1,4 +1,9 @@
1
1
  import type { OrderProduct } from '../../../modules/Order/types';
2
+ /**
3
+ * 运行态商品行标记:该行由“不合并商品”加车策略创建,促销重算时必须保持独立。
4
+ * `normalizeSubmitProduct` 会按白名单裁剪 metadata,不会把此字段提交到后端。
5
+ */
6
+ export declare const PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY = "_productLineMergeDisabled";
2
7
  /**
3
8
  * Cart promotion 工具集(OS 内部纯函数)
4
9
  *
@@ -23,6 +23,12 @@ import { cloneDeep } from 'lodash-es';
23
23
  import { composeLinePrice, createUuidV4, getProductSkuOptions, sumOptionUnitPrice } from "../../../modules/Order/utils";
24
24
  import { buildProductLineFingerprint, getSafeProductNum } from "../../ScanOrder/utils";
25
25
 
26
+ /**
27
+ * 运行态商品行标记:该行由“不合并商品”加车策略创建,促销重算时必须保持独立。
28
+ * `normalizeSubmitProduct` 会按白名单裁剪 metadata,不会把此字段提交到后端。
29
+ */
30
+ export var PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY = '_productLineMergeDisabled';
31
+
26
32
  /**
27
33
  * Cart promotion 工具集(OS 内部纯函数)
28
34
  *
@@ -483,10 +489,13 @@ function hasGoodPassDiscount(product) {
483
489
  }
484
490
 
485
491
  /**
486
- * 商品券、自定义商品、称重行以及编辑订单历史行都有各自的促销合并边界。
492
+ * 不合并商品、商品券、自定义商品、称重行以及编辑订单历史行都有各自的促销合并边界。
487
493
  */
488
494
  function getPromotionLineMergeBoundary(product) {
489
- var _product$product_sku, _product$metadata3;
495
+ var _product$metadata3, _product$product_sku, _product$metadata4;
496
+ if ((product === null || product === void 0 || (_product$metadata3 = product.metadata) === null || _product$metadata3 === void 0 ? void 0 : _product$metadata3[PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY]) === true) {
497
+ return "separate:".concat(getOrderProductUid(product));
498
+ }
490
499
  if ((product === null || product === void 0 ? void 0 : product.product_id) === undefined || (product === null || product === void 0 ? void 0 : product.product_id) === null) {
491
500
  return "custom:".concat(getOrderProductUid(product));
492
501
  }
@@ -496,7 +505,7 @@ function getPromotionLineMergeBoundary(product) {
496
505
  if (Number((product === null || product === void 0 || (_product$product_sku = product.product_sku) === null || _product$product_sku === void 0 ? void 0 : _product$product_sku.open_sold_weight) || 0) === 1) {
497
506
  return "weighing:".concat(getOrderProductUid(product));
498
507
  }
499
- if ((product === null || product === void 0 || (_product$metadata3 = product.metadata) === null || _product$metadata3 === void 0 ? void 0 : _product$metadata3.is_edit_for_runtime) !== true) return '';
508
+ if ((product === null || product === void 0 || (_product$metadata4 = product.metadata) === null || _product$metadata4 === void 0 ? void 0 : _product$metadata4.is_edit_for_runtime) !== true) return '';
500
509
  return "edit:".concat(getOrderProductUid(product));
501
510
  }
502
511
  function isPersistedGeneratedVoucher(product) {
@@ -921,13 +930,13 @@ export function resolveStrategyEligibleProducts(matched) {
921
930
  export function calculateUnfulfilledPromotions(processedProducts, applicableStrategiesResult, mainProducts) {
922
931
  var unfulfilledMap = new Map();
923
932
  var _loop2 = function _loop2() {
924
- var _product$metadata4, _ref13, _product$_originalPro, _product$metadata5, _product$num2;
933
+ var _product$metadata5, _ref13, _product$_originalPro, _product$metadata6, _product$num2;
925
934
  var product = processedProducts[i];
926
- var promotion = product === null || product === void 0 || (_product$metadata4 = product.metadata) === null || _product$metadata4 === void 0 ? void 0 : _product$metadata4._promotion;
935
+ var promotion = product === null || product === void 0 || (_product$metadata5 = product.metadata) === null || _product$metadata5 === void 0 ? void 0 : _product$metadata5._promotion;
927
936
  if (!promotion || promotion.inPromotion !== false) return 0; // continue
928
937
  var strategyId = promotion.strategyId;
929
938
  if (!strategyId) return 0; // continue
930
- var lookupUid = String((_ref13 = (_product$_originalPro = product === null || product === void 0 ? void 0 : product._originalProductUid) !== null && _product$_originalPro !== void 0 ? _product$_originalPro : product === null || product === void 0 || (_product$metadata5 = product.metadata) === null || _product$metadata5 === void 0 ? void 0 : _product$metadata5.unique_identification_number) !== null && _ref13 !== void 0 ? _ref13 : '');
939
+ var lookupUid = String((_ref13 = (_product$_originalPro = product === null || product === void 0 ? void 0 : product._originalProductUid) !== null && _product$_originalPro !== void 0 ? _product$_originalPro : product === null || product === void 0 || (_product$metadata6 = product.metadata) === null || _product$metadata6 === void 0 ? void 0 : _product$metadata6.unique_identification_number) !== null && _ref13 !== void 0 ? _ref13 : '');
931
940
  var originalIndex = mainProducts.findIndex(function (item) {
932
941
  return getOrderProductUid(item) === lookupUid;
933
942
  });
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 2 | 1 | 3 | 4 | 5 | 6;
329
+ weekNum: 0 | 1 | 2 | 5 | 4 | 3 | 6;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -345,7 +345,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
345
345
  count: number;
346
346
  left: number;
347
347
  summaryCount: number;
348
- status: "lots_of_space" | "filling_up_fast" | "sold_out";
348
+ status: "sold_out" | "lots_of_space" | "filling_up_fast";
349
349
  }[];
350
350
  /**
351
351
  * 找到多个资源的公共可用时间段
@@ -334,7 +334,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
334
334
  * 获取当前的客户搜索条件
335
335
  * @returns 当前搜索条件
336
336
  */
337
- getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "num" | "skip">;
337
+ getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "skip" | "num">;
338
338
  /**
339
339
  * 获取客户列表状态(包含滚动加载相关状态)
340
340
  * @returns 客户状态
@@ -453,19 +453,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
453
453
  * 加车两阶段主决策(规格弹窗 / 资源编辑 / 直接加车)。
454
454
  * 与 ticketBooking `handleSelectProduct` + `handleBooking4Service` 等价。
455
455
  */
456
- decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): {
457
- action: "reloadCatalog";
458
- } | {
459
- action: "add";
460
- cacheItem: any;
461
- } | {
462
- action: "requiresDetail";
463
- payload: AddProductRequiresDetailPayload;
464
- } | {
465
- action: "requiresBookingEdit";
466
- cacheItem: any;
467
- payload: import("./utils/addProductDecision").AddProductRequiresBookingEditPayload;
468
- };
456
+ decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
469
457
  /** 规格弹窗 callback 后的第二段决策。 */
470
458
  decideAfterDetail(cacheItem: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
471
459
  /**
@@ -987,7 +987,9 @@ function buildBookingFromAvailabilitySelection(params) {
987
987
  product = params.product,
988
988
  _params$resources = params.resources,
989
989
  resources = _params$resources === void 0 ? [] : _params$resources,
990
- bookingUid = params.bookingUid;
990
+ bookingUid = params.bookingUid,
991
+ holder = params.holder,
992
+ holderIds = params.holderIds;
991
993
  var bookingStartAt = candidate.bookingStartAt || candidate.startAt;
992
994
  var bookingEndAt = candidate.bookingEndAt || candidate.endAt;
993
995
  var startScalar = localDateTimeToScalar(parseLocalDateTime(bookingStartAt));
@@ -1005,14 +1007,19 @@ function buildBookingFromAvailabilitySelection(params) {
1005
1007
  scheduleRefs: candidate.scheduleRefs
1006
1008
  });
1007
1009
  }
1008
- return {
1010
+ var normalizedHolder = holder && _typeof(holder) === 'object' ? cloneDeep(holder) : undefined;
1011
+ var normalizedHolderIds = Array.isArray(holderIds) ? cloneDeep(holderIds) : holderIds;
1012
+ return _objectSpread(_objectSpread({
1009
1013
  start_date: bookingStartAt.slice(0, 10),
1010
1014
  start_time: bookingStartAt.slice(11, 16),
1011
1015
  end_date: bookingEndAt.slice(0, 10),
1012
1016
  end_time: bookingEndAt.slice(11, 16),
1013
1017
  duration: Math.max(1, Math.trunc((endScalar - startScalar) / (60 * 1000))),
1014
1018
  like_status: 'common',
1015
- schedule_id: scheduleId !== null && scheduleId !== void 0 ? scheduleId : 0,
1019
+ schedule_id: scheduleId !== null && scheduleId !== void 0 ? scheduleId : 0
1020
+ }, normalizedHolder ? {
1021
+ holder: normalizedHolder
1022
+ } : {}), {}, {
1016
1023
  resources: assignments.map(function (assignment) {
1017
1024
  // requirementSelections 的提交协议只携带资源 ID;购物车展示则需要名称。
1018
1025
  // commit 此时仍持有本次不可变 projection,因此直接按 ID 取回名称并
@@ -1035,7 +1042,7 @@ function buildBookingFromAvailabilitySelection(params) {
1035
1042
  }
1036
1043
  } : {});
1037
1044
  }),
1038
- metadata: _objectSpread(_objectSpread(_objectSpread({}, bookingUid ? {
1045
+ metadata: _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, bookingUid ? {
1039
1046
  unique_identification_number: bookingUid
1040
1047
  } : {}), {}, {
1041
1048
  unified_booking_sales: true,
@@ -1048,8 +1055,12 @@ function buildBookingFromAvailabilitySelection(params) {
1048
1055
  time_slot_id: timeSlotId
1049
1056
  }), {}, {
1050
1057
  product_kind: product.kind
1051
- })
1052
- };
1058
+ }, normalizedHolderIds === undefined ? {} : {
1059
+ holder_id: normalizedHolderIds
1060
+ }), normalizedHolder ? {
1061
+ holder: cloneDeep(normalizedHolder)
1062
+ } : {})
1063
+ });
1053
1064
  }
1054
1065
  export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
1055
1066
  _inherits(UnifiedBookingSalesImpl, _BookingTicket);
@@ -4137,7 +4148,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4137
4148
  key: "commitAvailabilitySelection",
4138
4149
  value: (function () {
4139
4150
  var _commitAvailabilitySelection = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee36(contextId, params) {
4140
- var normalizedContextId, _params$bookingCount, _ref45, _params$orderProduct$, _params$orderProduct, _params$orderProduct2, _params$orderProduct3, _params$orderProduct$2, _params$orderProduct4, _params$orderProduct5, _editingLine$product_, _baseProjection$meta$, _refreshedProjection$, _baseProjection$meta$2, bookingCount, orderProductQuantity, partySize, _yield$this$ensureAva3, context, projectionEntry, refreshed, boundEditingUids, orderProductId, editingLine, _editingLine$product_2, baseProjection, commitWriteGuard, prepareParams, fallbackResources, commitSnapshot, refreshedProjection, _baseProjection$meta$3, latestContext, refreshedRanges, refreshedCandidateBase, validation, capacityConflict, revalidationEntry, revalidatedContext, refreshedCandidate, product, existingBooking, existingBookingUid, booking, transformed, productLineUid, bookingUid, productLineUids, bookingUids, _transformed$product_, _transformed$product_2, _transformed$product_3, _ref46, _transformed$product_4, _this$getTempOrder4, _this$getTempOrder5, _this$getTempOrder6, beforeProductLineUids, beforeBookingUids, products, addedLines, addedBookings, nextContext, result;
4151
+ var normalizedContextId, _params$bookingCount, _ref45, _params$orderProduct$, _params$orderProduct, _params$orderProduct2, _params$orderProduct3, _params$orderProduct$2, _params$orderProduct4, _params$orderProduct5, _editingLine$product_, _baseProjection$meta$, _refreshedProjection$, _baseProjection$meta$2, _ref46, _params$orderProduct$3, _params$orderProduct6, _existingBooking$meta, _ref47, _ref48, _params$orderProduct$4, _params$orderProduct7, _params$orderProduct8, _existingBooking$meta2, _existingBooking$hold, bookingCount, orderProductQuantity, partySize, _yield$this$ensureAva3, context, projectionEntry, refreshed, boundEditingUids, orderProductId, editingLine, _editingLine$product_2, baseProjection, commitWriteGuard, prepareParams, fallbackResources, commitSnapshot, refreshedProjection, _baseProjection$meta$3, latestContext, refreshedRanges, refreshedCandidateBase, validation, capacityConflict, revalidationEntry, revalidatedContext, refreshedCandidate, product, existingBooking, existingBookingUid, booking, transformed, productLineUid, bookingUid, productLineUids, bookingUids, _transformed$product_, _transformed$product_2, _transformed$product_3, _ref49, _transformed$product_4, _this$getTempOrder4, _this$getTempOrder5, _this$getTempOrder6, beforeProductLineUids, beforeBookingUids, products, addedLines, addedBookings, nextContext, result;
4141
4152
  return _regeneratorRuntime().wrap(function _callee36$(_context36) {
4142
4153
  while (1) switch (_context36.prev = _context36.next) {
4143
4154
  case 0:
@@ -4464,7 +4475,9 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4464
4475
  candidate: refreshedCandidate,
4465
4476
  product: product,
4466
4477
  resources: refreshedProjection.resources,
4467
- bookingUid: existingBookingUid || undefined
4478
+ bookingUid: existingBookingUid || undefined,
4479
+ holder: (_ref46 = (_params$orderProduct$3 = (_params$orderProduct6 = params.orderProduct) === null || _params$orderProduct6 === void 0 || (_params$orderProduct6 = _params$orderProduct6._extend) === null || _params$orderProduct6 === void 0 ? void 0 : _params$orderProduct6.holder) !== null && _params$orderProduct$3 !== void 0 ? _params$orderProduct$3 : existingBooking === null || existingBooking === void 0 ? void 0 : existingBooking.holder) !== null && _ref46 !== void 0 ? _ref46 : existingBooking === null || existingBooking === void 0 || (_existingBooking$meta = existingBooking.metadata) === null || _existingBooking$meta === void 0 ? void 0 : _existingBooking$meta.holder,
4480
+ holderIds: (_ref47 = (_ref48 = (_params$orderProduct$4 = (_params$orderProduct7 = params.orderProduct) === null || _params$orderProduct7 === void 0 || (_params$orderProduct7 = _params$orderProduct7._extend) === null || _params$orderProduct7 === void 0 ? void 0 : _params$orderProduct7.holder_id) !== null && _params$orderProduct$4 !== void 0 ? _params$orderProduct$4 : (_params$orderProduct8 = params.orderProduct) === null || _params$orderProduct8 === void 0 || (_params$orderProduct8 = _params$orderProduct8._extend) === null || _params$orderProduct8 === void 0 || (_params$orderProduct8 = _params$orderProduct8.holder) === null || _params$orderProduct8 === void 0 ? void 0 : _params$orderProduct8.form_record) !== null && _ref48 !== void 0 ? _ref48 : existingBooking === null || existingBooking === void 0 || (_existingBooking$meta2 = existingBooking.metadata) === null || _existingBooking$meta2 === void 0 ? void 0 : _existingBooking$meta2.holder_id) !== null && _ref47 !== void 0 ? _ref47 : existingBooking === null || existingBooking === void 0 || (_existingBooking$hold = existingBooking.holder) === null || _existingBooking$hold === void 0 ? void 0 : _existingBooking$hold.form_record
4468
4481
  });
4469
4482
  transformed = this.buildUnifiedOrderProduct(_objectSpread(_objectSpread({}, params.orderProduct), {}, {
4470
4483
  num: bookingCount,
@@ -4503,7 +4516,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4503
4516
  _context36.next = 107;
4504
4517
  return this.updateOrderProduct({
4505
4518
  product_id: (_transformed$product_3 = transformed.product_id) !== null && _transformed$product_3 !== void 0 ? _transformed$product_3 : editingLine.product_id,
4506
- product_variant_id: (_ref46 = (_transformed$product_4 = transformed.product_variant_id) !== null && _transformed$product_4 !== void 0 ? _transformed$product_4 : editingLine.product_variant_id) !== null && _ref46 !== void 0 ? _ref46 : 0,
4519
+ product_variant_id: (_ref49 = (_transformed$product_4 = transformed.product_variant_id) !== null && _transformed$product_4 !== void 0 ? _transformed$product_4 : editingLine.product_variant_id) !== null && _ref49 !== void 0 ? _ref49 : 0,
4507
4520
  unique_identification_number: params.productLineUid,
4508
4521
  updates: transformed,
4509
4522
  booking: booking
@@ -4542,8 +4555,8 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
4542
4555
  }).filter(Boolean);
4543
4556
  if (bookingUids.length === 0) {
4544
4557
  bookingUids = addedLines.map(function (line) {
4545
- var _ref47, _line$booking_uid, _line$metadata2;
4546
- return String((_ref47 = (_line$booking_uid = line.booking_uid) !== null && _line$booking_uid !== void 0 ? _line$booking_uid : (_line$metadata2 = line.metadata) === null || _line$metadata2 === void 0 ? void 0 : _line$metadata2.booking_uid) !== null && _ref47 !== void 0 ? _ref47 : '');
4558
+ var _ref50, _line$booking_uid, _line$metadata2;
4559
+ return String((_ref50 = (_line$booking_uid = line.booking_uid) !== null && _line$booking_uid !== void 0 ? _line$booking_uid : (_line$metadata2 = line.metadata) === null || _line$metadata2 === void 0 ? void 0 : _line$metadata2.booking_uid) !== null && _ref50 !== void 0 ? _ref50 : '');
4547
4560
  }).filter(Boolean);
4548
4561
  }
4549
4562
  productLineUid = productLineUids[productLineUids.length - 1] || '';
@@ -562,7 +562,7 @@ export declare class OrderModule extends BaseModule implements Module, OrderModu
562
562
  */
563
563
  hydrateTempOrderFromRecord(record: Record<string, any>, options?: {
564
564
  recalcOnHydrate?: boolean;
565
- source?: 'salesDetail' | 'sessionStorage';
565
+ source?: 'salesDetail' | 'mergedSalesDetail' | 'sessionStorage';
566
566
  }): Promise<OrderTempOrder>;
567
567
  /**
568
568
  * 兼容后端详情将 holder 放在 metadata.holder 的历史形态,统一补到运行时展示路径。
@@ -1589,6 +1589,8 @@ class OrderModule extends _BaseModule.BaseModule {
1589
1589
  return !new _decimal.default(tempOrder.shop_discount || 0).equals(snapshotShopDiscount || 0);
1590
1590
  }
1591
1591
  restoreOriginalSnapshotIfProductsUnchanged(tempOrder, snapshot) {
1592
+ // merge hydrate 的商品集合包含本地未提交行,但 summary 仍来自远端旧订单,不能复用。
1593
+ if (snapshot.requiresFullSummaryRecalculation === true) return null;
1592
1594
  const currentFingerprint = this.buildOrderProductsFingerprint(tempOrder.products);
1593
1595
  const manualDepositOverride = this.getManualDepositOverride(tempOrder);
1594
1596
  if (currentFingerprint !== snapshot.productFingerprint) {
@@ -2805,8 +2807,15 @@ class OrderModule extends _BaseModule.BaseModule {
2805
2807
  booking
2806
2808
  }) : null;
2807
2809
  const productToAdd = linked?.product || product;
2808
- const incomingFingerprint = (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(productToAdd), productToAdd.product_bundle);
2810
+ const disableMerge = options?.disableMerge === true;
2811
+ const incomingFingerprint = disableMerge ? '' : (0, _utils3.buildProductLineFingerprint)((0, _utils.getProductSkuOptions)(productToAdd), productToAdd.product_bundle);
2809
2812
  const normalizedIncoming = (0, _utils.mergeOrderProductDisplayFields)(productToAdd, (0, _utils3.normalizeOrderProduct)(productToAdd));
2813
+ if (disableMerge) {
2814
+ normalizedIncoming.metadata = {
2815
+ ...(normalizedIncoming.metadata || {}),
2816
+ [_cartPromotion.PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY]: true
2817
+ };
2818
+ }
2810
2819
  const isWeighingProduct = Number(normalizedIncoming.product_sku?.open_sold_weight || 0) === 1;
2811
2820
  const netWeight = Number(normalizedIncoming.product_sku?.net_weight);
2812
2821
  if (isWeighingProduct && (!Number.isFinite(netWeight) || netWeight <= 0)) {
@@ -2826,7 +2835,7 @@ class OrderModule extends _BaseModule.BaseModule {
2826
2835
  const hasIncomingManualProductDiscount = isManualProductDiscountProduct(normalizedIncoming);
2827
2836
  const hasIncomingBookingUid = this.hasLinkedBookingUid(productToAdd) || this.hasLinkedBookingUid(normalizedIncoming);
2828
2837
  const isIncomingCustomItem = productToAdd.metadata?.origin?.isCustomItem === true || productToAdd._origin?.isCustomItem === true;
2829
- const shouldForceNewLine = !!booking || hasIncomingProductNote || hasIncomingManualProductDiscount || hasIncomingBookingUid || isIncomingCustomItem || isWeighingProduct;
2838
+ const shouldForceNewLine = disableMerge || !!booking || hasIncomingProductNote || hasIncomingManualProductDiscount || hasIncomingBookingUid || isIncomingCustomItem || isWeighingProduct;
2830
2839
  const matchedIndex = hasIncomingGoodPass || shouldForceNewLine ? -1 : tempOrder.products.findIndex(item => {
2831
2840
  if (this.hasGoodPassDiscount(item)) return false;
2832
2841
  if (this.hasOrderProductLineNote(item)) return false;
@@ -2944,7 +2953,8 @@ class OrderModule extends _BaseModule.BaseModule {
2944
2953
  singleLine.num = 1;
2945
2954
  delete singleLine.product_quantity;
2946
2955
  const appendResult = this.appendProductLineToTempOrder(tempOrder, singleLine, (0, _lodashEs.cloneDeep)(booking), {
2947
- insertAtIndex
2956
+ insertAtIndex,
2957
+ disableMerge: options?.disableMerge
2948
2958
  });
2949
2959
  if (appendResult) await appendResult;
2950
2960
  if (insertAtIndex !== undefined) insertAtIndex += 1;
@@ -2954,7 +2964,8 @@ class OrderModule extends _BaseModule.BaseModule {
2954
2964
  }
2955
2965
  }
2956
2966
  const appendResult = this.appendProductLineToTempOrder(tempOrder, product, booking, {
2957
- insertAtIndex
2967
+ insertAtIndex,
2968
+ disableMerge: options?.disableMerge
2958
2969
  });
2959
2970
  if (appendResult) await appendResult;
2960
2971
  await this.finalizeProductOrderMutation(tempOrder);
@@ -4134,8 +4145,10 @@ class OrderModule extends _BaseModule.BaseModule {
4134
4145
  }
4135
4146
  }
4136
4147
  const isSessionStorageHydrate = options?.source === 'sessionStorage';
4148
+ const isMergedSalesDetailHydrate = options?.source === 'mergedSalesDetail';
4137
4149
  const nextTempOrder = this.normalizeTempOrderForRuntime(raw, {
4138
- makeEditMark: !isSessionStorageHydrate
4150
+ // merge 前已只给远端商品补标记;这里不能把本地未提交商品一并变成 saved item。
4151
+ makeEditMark: !isSessionStorageHydrate && !isMergedSalesDetailHydrate
4139
4152
  });
4140
4153
  this.store.tempOrder = nextTempOrder;
4141
4154
  if (isSessionStorageHydrate) {
@@ -4190,7 +4203,11 @@ class OrderModule extends _BaseModule.BaseModule {
4190
4203
  payments: (0, _lodashEs.cloneDeep)(nextTempOrder.payments || []),
4191
4204
  surcharges: (0, _lodashEs.cloneDeep)(nextTempOrder.surcharges || []),
4192
4205
  shop_discount: nextTempOrder.shop_discount || '0.00',
4193
- productFingerprint: this.buildOrderProductsFingerprint(nextTempOrder.products || [])
4206
+ productFingerprint: this.buildOrderProductsFingerprint(nextTempOrder.products || []),
4207
+ // merged record 的 summary 仍是远端旧值,保留快照仅用于完整重算的价格清理基线。
4208
+ ...(isMergedSalesDetailHydrate ? {
4209
+ requiresFullSummaryRecalculation: true
4210
+ } : {})
4194
4211
  }
4195
4212
  };
4196
4213
  this.store.lastOrderInfo = this.withoutSyntheticDraftOrderIdForRuntime(sourceLastOrderInfo, syntheticIdentityOptions);
@@ -330,6 +330,8 @@ export type AddProductBookingInput = Record<string, any>;
330
330
  */
331
331
  export interface AddProductToOrderOptions {
332
332
  insertAfterProductUid?: string;
333
+ /** 跳过同商品/规格匹配,始终新增购物车行。 */
334
+ disableMerge?: boolean;
333
335
  }
334
336
  /**
335
337
  * 删除商品后的可选位置保持策略。
@@ -821,7 +823,7 @@ export interface OrderModuleAPI {
821
823
  saveDraft: () => Promise<void>;
822
824
  hydrateTempOrderFromRecord: (record: Record<string, any>, options?: {
823
825
  recalcOnHydrate?: boolean;
824
- source?: 'salesDetail' | 'sessionStorage';
826
+ source?: 'salesDetail' | 'mergedSalesDetail' | 'sessionStorage';
825
827
  }) => Promise<OrderTempOrder>;
826
828
  syncPaymentsToOrder: <T = any>(params: SyncPaymentsToOrderParams) => Promise<SyncPaymentsToOrderResult<T>>;
827
829
  getSalesOrderByLookup: (params: SalesOrderLookupParams) => Promise<any>;
@@ -331,6 +331,25 @@ function filterPendingPaymentsFromLoadedSalesDetail(sourceRecord) {
331
331
  }
332
332
  return record;
333
333
  }
334
+
335
+ /**
336
+ * merge hydrate 不会统一给所有商品补 saved 标记,因此先只标记远端已保存商品。
337
+ * 当前 tempOrder 中尚未提交的本地商品会在 merge 后继续保持 current item 语义。
338
+ */
339
+ function markLoadedSalesDetailProductsAsSaved(sourceRecord) {
340
+ if (!sourceRecord || typeof sourceRecord !== 'object') return sourceRecord;
341
+ if (!Array.isArray(sourceRecord.products)) return sourceRecord;
342
+ return {
343
+ ...sourceRecord,
344
+ products: sourceRecord.products.map(product => ({
345
+ ...(product || {}),
346
+ metadata: {
347
+ ...(product?.metadata && typeof product.metadata === 'object' ? product.metadata : {}),
348
+ is_edit_for_runtime: true
349
+ }
350
+ }))
351
+ };
352
+ }
334
353
  class BaseSalesImpl extends _BaseModule.BaseModule {
335
354
  defaultName = 'baseSales';
336
355
  defaultVersion = '1.0.0';
@@ -512,7 +531,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
512
531
  });
513
532
  });
514
533
  }
515
- async hydrateLatestLoadedSalesDetail(record, loadSequence, hydrateSource) {
534
+ async hydrateLatestLoadedSalesDetail(record, loadSequence, options) {
516
535
  let hydratedSales = null;
517
536
  let skippedBeforeHydrate = false;
518
537
  const hydrateTask = async () => {
@@ -522,12 +541,21 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
522
541
  return;
523
542
  }
524
543
  if (!this.store.order) throw new Error('order 模块未初始化');
525
- const sales = await this.store.order.hydrateTempOrderFromRecord(record, hydrateSource === 'sessionStorage' ? {
526
- recalcOnHydrate: true,
527
- source: 'sessionStorage'
528
- } : {
544
+ let hydrateOptions = {
529
545
  recalcOnHydrate: false
530
- });
546
+ };
547
+ if (options?.hydrateSource === 'sessionStorage') {
548
+ hydrateOptions = {
549
+ recalcOnHydrate: true,
550
+ source: 'sessionStorage'
551
+ };
552
+ } else if (options?.merge) {
553
+ hydrateOptions = {
554
+ recalcOnHydrate: true,
555
+ source: 'mergedSalesDetail'
556
+ };
557
+ }
558
+ const sales = await this.store.order.hydrateTempOrderFromRecord(record, hydrateOptions);
531
559
  hydratedSales = sales;
532
560
 
533
561
  // hydrate 期间可能又发起了更新请求;后续任务已串行排队,将负责最终状态。
@@ -1076,10 +1104,14 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1076
1104
  // payment_pending 只存在于本地;远端详情需过滤,但会话快照必须完整恢复。
1077
1105
  const shouldFilterPendingPayments = params.hydrateSource !== 'sessionStorage';
1078
1106
  const remoteRecord = shouldFilterPendingPayments ? filterPendingPaymentsFromLoadedSalesDetail(loadedSalesDetail) : loadedSalesDetail;
1107
+ const mergeSourceRecord = params.merge ? markLoadedSalesDetailProductsAsSaved(remoteRecord) : remoteRecord;
1079
1108
  const currentTempOrder = params.merge ? this.store.order.getTempOrder() : null;
1080
- const mergedRecord = params.merge ? mergeSalesDetailRecordWithTempOrder(currentTempOrder, remoteRecord, 'preserve-local') : remoteRecord;
1109
+ const mergedRecord = params.merge ? mergeSalesDetailRecordWithTempOrder(currentTempOrder, mergeSourceRecord, 'preserve-local') : mergeSourceRecord;
1081
1110
  const record = shouldFilterPendingPayments ? filterPendingPaymentsFromLoadedSalesDetail(mergedRecord) : mergedRecord;
1082
- return await this.hydrateLatestLoadedSalesDetail(record, loadSequence, params.hydrateSource);
1111
+ return await this.hydrateLatestLoadedSalesDetail(record, loadSequence, {
1112
+ hydrateSource: params.hydrateSource,
1113
+ merge: params.merge
1114
+ });
1083
1115
  } finally {
1084
1116
  this.salesDetailLoadInFlightCount = Math.max(0, this.salesDetailLoadInFlightCount - 1);
1085
1117
  if (this.salesDetailLoadInFlightCount === 0) {
@@ -1,4 +1,9 @@
1
1
  import type { OrderProduct } from '../../../modules/Order/types';
2
+ /**
3
+ * 运行态商品行标记:该行由“不合并商品”加车策略创建,促销重算时必须保持独立。
4
+ * `normalizeSubmitProduct` 会按白名单裁剪 metadata,不会把此字段提交到后端。
5
+ */
6
+ export declare const PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY = "_productLineMergeDisabled";
2
7
  /**
3
8
  * Cart promotion 工具集(OS 内部纯函数)
4
9
  *
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY = void 0;
6
7
  exports.appendPromotionTags = appendPromotionTags;
7
8
  exports.buildConsolidateKeyForPromotion = buildConsolidateKeyForPromotion;
8
9
  exports.calculateUnfulfilledPromotions = calculateUnfulfilledPromotions;
@@ -19,6 +20,12 @@ var _lodashEs = require("lodash-es");
19
20
  var _utils = require("../../../modules/Order/utils");
20
21
  var _utils2 = require("../../ScanOrder/utils");
21
22
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
+ /**
24
+ * 运行态商品行标记:该行由“不合并商品”加车策略创建,促销重算时必须保持独立。
25
+ * `normalizeSubmitProduct` 会按白名单裁剪 metadata,不会把此字段提交到后端。
26
+ */
27
+ const PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY = exports.PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY = '_productLineMergeDisabled';
28
+
22
29
  /**
23
30
  * Cart promotion 工具集(OS 内部纯函数)
24
31
  *
@@ -396,9 +403,12 @@ function hasGoodPassDiscount(product) {
396
403
  }
397
404
 
398
405
  /**
399
- * 商品券、自定义商品、称重行以及编辑订单历史行都有各自的促销合并边界。
406
+ * 不合并商品、商品券、自定义商品、称重行以及编辑订单历史行都有各自的促销合并边界。
400
407
  */
401
408
  function getPromotionLineMergeBoundary(product) {
409
+ if (product?.metadata?.[PRODUCT_LINE_MERGE_DISABLED_METADATA_KEY] === true) {
410
+ return `separate:${getOrderProductUid(product)}`;
411
+ }
402
412
  if (product?.product_id === undefined || product?.product_id === null) {
403
413
  return `custom:${getOrderProductUid(product)}`;
404
414
  }
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 2 | 1 | 3 | 4 | 5 | 6;
329
+ weekNum: 0 | 1 | 2 | 5 | 4 | 3 | 6;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -345,7 +345,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
345
345
  count: number;
346
346
  left: number;
347
347
  summaryCount: number;
348
- status: "lots_of_space" | "filling_up_fast" | "sold_out";
348
+ status: "sold_out" | "lots_of_space" | "filling_up_fast";
349
349
  }[];
350
350
  /**
351
351
  * 找到多个资源的公共可用时间段
@@ -334,7 +334,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
334
334
  * 获取当前的客户搜索条件
335
335
  * @returns 当前搜索条件
336
336
  */
337
- getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "num" | "skip">;
337
+ getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "skip" | "num">;
338
338
  /**
339
339
  * 获取客户列表状态(包含滚动加载相关状态)
340
340
  * @returns 客户状态
@@ -453,19 +453,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
453
453
  * 加车两阶段主决策(规格弹窗 / 资源编辑 / 直接加车)。
454
454
  * 与 ticketBooking `handleSelectProduct` + `handleBooking4Service` 等价。
455
455
  */
456
- decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): {
457
- action: "reloadCatalog";
458
- } | {
459
- action: "add";
460
- cacheItem: any;
461
- } | {
462
- action: "requiresDetail";
463
- payload: AddProductRequiresDetailPayload;
464
- } | {
465
- action: "requiresBookingEdit";
466
- cacheItem: any;
467
- payload: import("./utils/addProductDecision").AddProductRequiresBookingEditPayload;
468
- };
456
+ decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
469
457
  /** 规格弹窗 callback 后的第二段决策。 */
470
458
  decideAfterDetail(cacheItem: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
471
459
  /**
@@ -906,7 +906,9 @@ function buildBookingFromAvailabilitySelection(params) {
906
906
  candidate,
907
907
  product,
908
908
  resources = [],
909
- bookingUid
909
+ bookingUid,
910
+ holder,
911
+ holderIds
910
912
  } = params;
911
913
  const bookingStartAt = candidate.bookingStartAt || candidate.startAt;
912
914
  const bookingEndAt = candidate.bookingEndAt || candidate.endAt;
@@ -921,6 +923,8 @@ function buildBookingFromAvailabilitySelection(params) {
921
923
  scheduleRefs: candidate.scheduleRefs
922
924
  });
923
925
  }
926
+ const normalizedHolder = holder && typeof holder === 'object' ? (0, _lodashEs.cloneDeep)(holder) : undefined;
927
+ const normalizedHolderIds = Array.isArray(holderIds) ? (0, _lodashEs.cloneDeep)(holderIds) : holderIds;
924
928
  return {
925
929
  start_date: bookingStartAt.slice(0, 10),
926
930
  start_time: bookingStartAt.slice(11, 16),
@@ -929,6 +933,9 @@ function buildBookingFromAvailabilitySelection(params) {
929
933
  duration: Math.max(1, Math.trunc((endScalar - startScalar) / (60 * 1000))),
930
934
  like_status: 'common',
931
935
  schedule_id: scheduleId ?? 0,
936
+ ...(normalizedHolder ? {
937
+ holder: normalizedHolder
938
+ } : {}),
932
939
  resources: assignments.map(assignment => {
933
940
  // requirementSelections 的提交协议只携带资源 ID;购物车展示则需要名称。
934
941
  // commit 此时仍持有本次不可变 projection,因此直接按 ID 取回名称并
@@ -963,7 +970,13 @@ function buildBookingFromAvailabilitySelection(params) {
963
970
  ...(timeSlotId == null ? {} : {
964
971
  time_slot_id: timeSlotId
965
972
  }),
966
- product_kind: product.kind
973
+ product_kind: product.kind,
974
+ ...(normalizedHolderIds === undefined ? {} : {
975
+ holder_id: normalizedHolderIds
976
+ }),
977
+ ...(normalizedHolder ? {
978
+ holder: (0, _lodashEs.cloneDeep)(normalizedHolder)
979
+ } : {})
967
980
  }
968
981
  };
969
982
  }
@@ -2939,7 +2952,9 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
2939
2952
  candidate: refreshedCandidate,
2940
2953
  product,
2941
2954
  resources: refreshedProjection.resources,
2942
- bookingUid: existingBookingUid || undefined
2955
+ bookingUid: existingBookingUid || undefined,
2956
+ holder: params.orderProduct?._extend?.holder ?? existingBooking?.holder ?? existingBooking?.metadata?.holder,
2957
+ holderIds: params.orderProduct?._extend?.holder_id ?? params.orderProduct?._extend?.holder?.form_record ?? existingBooking?.metadata?.holder_id ?? existingBooking?.holder?.form_record
2943
2958
  });
2944
2959
  const transformed = this.buildUnifiedOrderProduct({
2945
2960
  ...params.orderProduct,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.3.75",
4
+ "version": "2.3.77",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",