@pisell/pisellos 0.10.59 → 0.10.61

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 (72) hide show
  1. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
  2. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +25 -2
  3. package/dist/modules/Discount/index.js +1 -0
  4. package/dist/modules/Order/holderRelations.d.ts +14 -0
  5. package/dist/modules/Order/holderRelations.js +154 -0
  6. package/dist/modules/Order/index.d.ts +1 -1
  7. package/dist/modules/Order/index.js +92 -47
  8. package/dist/modules/Order/types.d.ts +19 -1
  9. package/dist/modules/Order/types.js +2 -0
  10. package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
  11. package/dist/modules/Order/utils/discountProductLineIdentity.js +142 -7
  12. package/dist/modules/Order/utils/orderCollectionIdentity.js +24 -10
  13. package/dist/modules/Order/utils.d.ts +1 -1
  14. package/dist/modules/Order/utils.js +35 -9
  15. package/dist/server/index.js +18 -12
  16. package/dist/server/modules/order/index.d.ts +4 -0
  17. package/dist/server/modules/order/index.js +255 -190
  18. package/dist/server/modules/order/types.d.ts +3 -0
  19. package/dist/shared/receipt/small-ticket.js +17 -0
  20. package/dist/shared/sales/loadSalesOrderSnapshot.js +1 -1
  21. package/dist/solution/BaseSales/index.js +97 -27
  22. package/dist/solution/BaseSales/types.d.ts +2 -1
  23. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  24. package/dist/solution/BaseSales/utils/parseSalesResponse.js +7 -3
  25. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +12 -3
  26. package/dist/solution/BookingTicket/index.js +7 -4
  27. package/dist/solution/BookingTicket/utils/addProductDecision.d.ts +1 -1
  28. package/dist/solution/BookingTicket/utils/addProductDecision.js +4 -3
  29. package/dist/solution/BookingTicket/utils/scan/applyGlobalScan.js +6 -2
  30. package/dist/solution/BookingTicket/utils/scan/cloudSearch.js +1 -0
  31. package/dist/solution/BookingTicket/utils/scan/handleScan.js +2 -1
  32. package/dist/solution/UnifiedBookingSales/index.d.ts +1 -1
  33. package/dist/solution/UnifiedBookingSales/index.js +4 -4
  34. package/dist/utils/platform.d.ts +2 -0
  35. package/dist/utils/platform.js +6 -0
  36. package/lib/model/strategy/adapter/promotion/index.js +46 -1
  37. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
  38. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +16 -1
  39. package/lib/modules/Discount/index.js +1 -0
  40. package/lib/modules/Order/holderRelations.d.ts +14 -0
  41. package/lib/modules/Order/holderRelations.js +98 -0
  42. package/lib/modules/Order/index.d.ts +1 -1
  43. package/lib/modules/Order/index.js +44 -12
  44. package/lib/modules/Order/types.d.ts +19 -1
  45. package/lib/modules/Order/types.js +1 -0
  46. package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
  47. package/lib/modules/Order/utils/discountProductLineIdentity.js +51 -2
  48. package/lib/modules/Order/utils/orderCollectionIdentity.js +5 -0
  49. package/lib/modules/Order/utils.d.ts +1 -1
  50. package/lib/modules/Order/utils.js +27 -6
  51. package/lib/server/index.js +6 -0
  52. package/lib/server/modules/order/index.d.ts +4 -0
  53. package/lib/server/modules/order/index.js +46 -4
  54. package/lib/server/modules/order/types.d.ts +3 -0
  55. package/lib/shared/receipt/small-ticket.js +15 -0
  56. package/lib/shared/sales/loadSalesOrderSnapshot.js +1 -1
  57. package/lib/solution/BaseSales/index.js +39 -2
  58. package/lib/solution/BaseSales/types.d.ts +2 -1
  59. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  60. package/lib/solution/BaseSales/utils/parseSalesResponse.js +4 -0
  61. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +7 -0
  62. package/lib/solution/BookingTicket/index.js +4 -1
  63. package/lib/solution/BookingTicket/utils/addProductDecision.d.ts +1 -1
  64. package/lib/solution/BookingTicket/utils/addProductDecision.js +4 -3
  65. package/lib/solution/BookingTicket/utils/scan/applyGlobalScan.js +7 -1
  66. package/lib/solution/BookingTicket/utils/scan/cloudSearch.js +1 -0
  67. package/lib/solution/BookingTicket/utils/scan/handleScan.js +2 -1
  68. package/lib/solution/UnifiedBookingSales/index.d.ts +1 -1
  69. package/lib/solution/UnifiedBookingSales/index.js +3 -3
  70. package/lib/utils/platform.d.ts +2 -0
  71. package/lib/utils/platform.js +7 -0
  72. package/package.json +1 -1
@@ -331,6 +331,11 @@ function applyOrderCollectionUidRemaps(sourceOrder, uidRemaps) {
331
331
  }
332
332
  // 冲突 UID 仍由第一条持有;赠品父引用缺少可用的持久化行定位时不能全局改写。
333
333
  if (remap.scope !== 'linked-owner') {
334
+ for (const relation of order.holder_relations || []) {
335
+ if (relation.relation_model === 'order_detail' && String(relation.relation_uuid) === remap.from) {
336
+ relation.relation_uuid = remap.to;
337
+ }
338
+ }
334
339
  for (const product of order.products || []) {
335
340
  if (String(product?.metadata?.parent_unique_identification_number || '') === remap.from) {
336
341
  product.metadata = {
@@ -173,7 +173,7 @@ export declare function resolveManualOverrideLineOriginalPrice(params: {
173
173
  * // => -4
174
174
  */
175
175
  export declare function resolveEffectivePerUnitDiscount(product: Record<string, any>): number;
176
- export declare function createDefaultOrderRulesHooks(getBookings?: () => OrderTempOrder['bookings']): RulesParamsHooks;
176
+ export declare function createDefaultOrderRulesHooks(getBookings?: () => OrderTempOrder['bookings'], getHolderRelations?: () => OrderTempOrder['holder_relations']): RulesParamsHooks;
177
177
  /**
178
178
  * 通过 session 类商品的开始时间结束时间生成商品的时长
179
179
  * @param {CartItem} cartItem
@@ -128,6 +128,7 @@ Object.defineProperty(exports, "syncManualProductDiscountProductNum", {
128
128
  return _manualProductDiscount.syncManualProductDiscountProductNum;
129
129
  }
130
130
  });
131
+ var _lodashEs = require("lodash-es");
131
132
  var _dayjs = _interopRequireDefault(require("dayjs"));
132
133
  var _customParseFormat = _interopRequireDefault(require("dayjs/plugin/customParseFormat"));
133
134
  var _decimal = _interopRequireDefault(require("decimal.js"));
@@ -138,6 +139,7 @@ var _utils2 = require("../../solution/ScanOrder/utils");
138
139
  var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
139
140
  var _salesNotes = require("./salesNotes");
140
141
  var _pickupTiming = require("./pickupTiming");
142
+ var _holderRelations = require("./holderRelations");
141
143
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
142
144
  _dayjs.default.extend(_customParseFormat.default);
143
145
  const POINT_CARD_SNAPSHOT_TAG = 'point_card';
@@ -824,7 +826,7 @@ function resolveEffectivePerUnitDiscount(product) {
824
826
  if (!Number.isFinite(originalPrice) || !Number.isFinite(finalPrice)) return fromList;
825
827
  return fromList + (originalPrice - finalPrice);
826
828
  }
827
- function createDefaultOrderRulesHooks(getBookings) {
829
+ function createDefaultOrderRulesHooks(getBookings, getHolderRelations) {
828
830
  const getProductStartDate = product => {
829
831
  const productUid = String(product.metadata?.unique_identification_number ?? product.unique_identification_number ?? '');
830
832
  const bookingUid = String(product.booking_uid ?? product.metadata?.booking_uid ?? '');
@@ -942,7 +944,9 @@ function createDefaultOrderRulesHooks(getBookings) {
942
944
  // 勿写死 false:否则 Rules 不会走 total≠origin_total 的手动价推断,
943
945
  // 无券时会把 bundle 价还原成 original_price(手动改价套餐会从 20 回到 30)。
944
946
  isManualDiscount: resolveRulesManualDiscountFlag(metadataAny),
945
- holder_id: product.metadata?.holder_id,
947
+ holder_id: (0, _holderRelations.hasProductBooking)(product) || !getHolderRelations ? product.metadata?.holder_id : (0, _holderRelations.getProductHolderRelations)({
948
+ holder_relations: getHolderRelations()
949
+ }, product).filter(relation => relation.holder_type === 'form').map(relation => relation.holder_id),
946
950
  startDate: getProductStartDate(product),
947
951
  main_product_selling_price: metadataAny.main_product_selling_price !== undefined ? new _decimal.default(Number(metadataAny.main_product_selling_price) || 0).minus(optionSellingSum).toDecimalPlaces(2).toString() : undefined,
948
952
  inPromotion: metadataAny._promotion?.inPromotion === true,
@@ -1233,6 +1237,7 @@ function normalizeSubmitProduct(product, formatOptions = {}) {
1233
1237
  } = product;
1234
1238
  const {
1235
1239
  num,
1240
+ _extend: _productExtend,
1236
1241
  surcharge_fee: _productSurchargeFee,
1237
1242
  discount_amount: _deprecatedDiscountAmount,
1238
1243
  discount_type: _deprecatedDiscountType,
@@ -1419,6 +1424,7 @@ function createDefaultTempOrder(params) {
1419
1424
  request_unique_idempotency_token: '',
1420
1425
  vouchers: [],
1421
1426
  products: [],
1427
+ holder_relations: [],
1422
1428
  notes: [],
1423
1429
  bookings: [],
1424
1430
  payments: [],
@@ -1494,6 +1500,7 @@ function buildSubmitPayload(params) {
1494
1500
  summary,
1495
1501
  enhance
1496
1502
  } = params;
1503
+ (0, _holderRelations.ensureHolderRelationUuids)(tempOrder);
1497
1504
  const scheduleDate = tempOrder.schedule_date || tempOrder.created_at || formatDateTime(now);
1498
1505
  const bookingUuid = (0, _orderCollectionIdentity.createUuidV4)();
1499
1506
  const virtualSettlementMode = tempOrder.metadata?.currency_settlement_config?.settlement_type === 'virtual_currency';
@@ -1533,6 +1540,9 @@ function buildSubmitPayload(params) {
1533
1540
  });
1534
1541
  const payload = {
1535
1542
  ...tempOrderRest,
1543
+ ...(tempOrder.holder_relations !== undefined ? {
1544
+ holder_relations: (0, _lodashEs.cloneDeep)(tempOrder.holder_relations)
1545
+ } : {}),
1536
1546
  customer_id: tempOrder.customer_id ?? 1,
1537
1547
  customer_name: tempOrder.customer_name || 'Walk-In',
1538
1548
  country_calling_code: String(tempOrder.country_calling_code ?? ''),
@@ -1725,15 +1735,24 @@ function hasAssignedHolderId(value) {
1725
1735
  * clearTempOrderHolderAssignments(tempOrder);
1726
1736
  */
1727
1737
  function clearTempOrderHolderAssignments(tempOrder) {
1728
- let changed = false;
1738
+ const previousRelationCount = tempOrder.holder_relations?.length || 0;
1739
+ (0, _holderRelations.removeProductHolderRelations)(tempOrder, (tempOrder.products || []).filter(product => !(0, _holderRelations.hasProductBooking)(product)));
1740
+ let changed = previousRelationCount !== (tempOrder.holder_relations?.length || 0);
1729
1741
  const resetHolderField = (target, key = 'holder_id') => {
1730
1742
  if (!hasAssignedHolderId(target[key])) return;
1731
1743
  target[key] = null;
1732
1744
  changed = true;
1733
1745
  };
1746
+ const clearHolder = target => {
1747
+ resetHolderField(target);
1748
+ if (target.holder !== undefined && target.holder !== null) {
1749
+ target.holder = null;
1750
+ changed = true;
1751
+ }
1752
+ };
1734
1753
  for (const product of tempOrder.products || []) {
1735
1754
  if (product.metadata && typeof product.metadata === 'object') {
1736
- resetHolderField(product.metadata);
1755
+ clearHolder(product.metadata);
1737
1756
  }
1738
1757
  }
1739
1758
  for (const booking of tempOrder.bookings || []) {
@@ -1751,11 +1770,13 @@ function clearTempOrderHolderAssignments(tempOrder) {
1751
1770
  for (const entry of Object.values(productsByUid)) {
1752
1771
  const origin = entry?.origin;
1753
1772
  if (!origin || typeof origin !== 'object') continue;
1773
+ clearHolder(origin);
1754
1774
  if (origin.metadata && typeof origin.metadata === 'object') {
1755
- resetHolderField(origin.metadata);
1775
+ clearHolder(origin.metadata);
1756
1776
  }
1757
1777
  if (origin._extend && typeof origin._extend === 'object') {
1758
- resetHolderField(origin._extend);
1778
+ clearHolder(origin._extend);
1779
+ delete origin._extend.holder_relations;
1759
1780
  }
1760
1781
  }
1761
1782
  }
@@ -824,6 +824,9 @@ class Server {
824
824
  need_sync: 0,
825
825
  is_draft_order: 0
826
826
  };
827
+ if (!Array.isArray(fresh.holder_relations) && Array.isArray(checkoutData?.holder_relations)) {
828
+ syncedOrder.holder_relations = (0, _lodashEs.cloneDeep)(checkoutData.holder_relations);
829
+ }
827
830
  await this.order.upsertOrdersFromRemote([syncedOrder], 'checkoutSync');
828
831
  this.logInfo(`${title}: sync_sales 完整订单已同步到本地`, {
829
832
  order_id: fresh.order_id,
@@ -5423,6 +5426,9 @@ class Server {
5423
5426
  if (fresh.order_number !== undefined && fresh.order_number !== null) {
5424
5427
  patch.order_number = fresh.order_number;
5425
5428
  }
5429
+ if (Array.isArray(fresh.holder_relations)) {
5430
+ patch.holder_relations = (0, _lodashEs.cloneDeep)(fresh.holder_relations);
5431
+ }
5426
5432
  return patch;
5427
5433
  }
5428
5434
  buildSyncedOrderForPrint(params) {
@@ -311,6 +311,10 @@ export declare class OrderModule extends BaseModule implements Module {
311
311
  private getIdentifiedPendingPayments;
312
312
  private shouldProtectLocalOrderFromRemoteSnapshot;
313
313
  private getUnmatchedIdentifiedPendingPayments;
314
+ /** 关系数组是完整快照,不能拼接或按商品子项生成 UID;扩展字段整体保留。 */
315
+ private applyHolderRelationsSnapshot;
316
+ /** 全量拉取缺少关系字段时,仍保留同一订单已有的关系快照。 */
317
+ private preserveOmittedHolderRelations;
314
318
  private mergeOrderRecords;
315
319
  private summarizeDuplicateOrders;
316
320
  private logDuplicateOrders;
@@ -288,6 +288,7 @@ class OrderModule extends _BaseModule.BaseModule {
288
288
  const existingRecord = existing;
289
289
  const incomingRecord = incoming;
290
290
  const uidRemaps = [];
291
+ this.applyHolderRelationsSnapshot(reconciled, existing, incoming);
291
292
  for (const field of ORDER_AUTHORITATIVE_COLLECTION_FIELDS) {
292
293
  if (!Array.isArray(incomingRecord[field])) continue;
293
294
  reconciled[field] = this.mergeAuthoritativeOrderCollection(field, existingRecord[field], incomingRecord[field], uidRemaps);
@@ -364,7 +365,7 @@ class OrderModule extends _BaseModule.BaseModule {
364
365
  continue;
365
366
  }
366
367
 
367
- // 远端商品成员确实发生增删时必须落库,避免内存已更新但 SQLite 仍被回声窗口跳过。
368
+ // 远端商品成员或 Holder 关系确实变化时必须落库,避免被回声窗口跳过。
368
369
  if (forceWriteStorageKeys.has(storageKey)) {
369
370
  toWrite.push(order);
370
371
  continue;
@@ -1234,6 +1235,7 @@ class OrderModule extends _BaseModule.BaseModule {
1234
1235
  if (matchIndex >= 0) {
1235
1236
  const existingOrder = updatedList[matchIndex];
1236
1237
  const reconciledDraftOrder = this.preserveExistingBusinessCodeForDraft(existingOrder, draftOrder);
1238
+ this.applyHolderRelationsSnapshot(reconciledDraftOrder, existingOrder, draftOrder);
1237
1239
  const mergedOrder = this.mergeOrderRecords(existingOrder, reconciledDraftOrder);
1238
1240
  const nextOrder = {
1239
1241
  ...mergedOrder,
@@ -1512,7 +1514,10 @@ class OrderModule extends _BaseModule.BaseModule {
1512
1514
  const hasSingleId = routeInfo.hasSingleId;
1513
1515
  const normalizedBody = this.normalizeOrderSyncPayload(msg.body || msg.data);
1514
1516
  if (routeInfo.route === 'bodyUpsert' && normalizedBody) {
1515
- upsertOrders.set(this.getIdKey(normalizedBody.order_id), normalizedBody);
1517
+ const orderKey = this.getIdKey(normalizedBody.order_id);
1518
+ const previousBody = upsertOrders.get(orderKey);
1519
+ if (previousBody) this.applyHolderRelationsSnapshot(normalizedBody, previousBody, normalizedBody);
1520
+ upsertOrders.set(orderKey, normalizedBody);
1516
1521
  return;
1517
1522
  }
1518
1523
  if (hasBatchIds) refreshIds.push(...routeInfo.batchIds);else if (routeInfo.route === 'httpRefresh.singleId' && singleId !== null) refreshIds.push(singleId);else if (routeInfo.route === 'httpRefresh.relationIds') refreshIds.push(...routeInfo.batchIds);
@@ -1705,6 +1710,9 @@ class OrderModule extends _BaseModule.BaseModule {
1705
1710
  const mergedStorageKey = this.getOrderStorageKey(mergedOrder);
1706
1711
  if (mergedStorageKey) forceWriteStorageKeys.add(mergedStorageKey);
1707
1712
  }
1713
+ if (nextStorageKey && !(0, _lodashEs.isEqual)(existingOrder.holder_relations, mergedOrder.holder_relations)) {
1714
+ forceWriteStorageKeys.add(nextStorageKey);
1715
+ }
1708
1716
  // 合并后身份字段可能新增(如挂单拿到 order_id),同步登记到索引,
1709
1717
  // 供同批后续 fresh 命中同一槽位,避免重复插入
1710
1718
  this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, adjustedMatchIndex);
@@ -2005,6 +2013,38 @@ class OrderModule extends _BaseModule.BaseModule {
2005
2013
  const incomingIdentityKeys = new Set(incomingPayments.flatMap(payment => (0, _paymentUtils.getOrderPaymentIdentityKeys)(payment)));
2006
2014
  return existingPayments.filter(payment => (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment) && !(0, _paymentUtils.getOrderPaymentIdentityKeys)(payment).some(key => incomingIdentityKeys.has(key)));
2007
2015
  }
2016
+
2017
+ /** 关系数组是完整快照,不能拼接或按商品子项生成 UID;扩展字段整体保留。 */
2018
+ applyHolderRelationsSnapshot(target, existing, incoming) {
2019
+ if (Array.isArray(incoming.holder_relations)) {
2020
+ target.holder_relations = (0, _lodashEs.cloneDeep)(incoming.holder_relations);
2021
+ } else if (Array.isArray(existing.holder_relations)) {
2022
+ target.holder_relations = (0, _lodashEs.cloneDeep)(existing.holder_relations);
2023
+ } else {
2024
+ delete target.holder_relations;
2025
+ }
2026
+ }
2027
+
2028
+ /** 全量拉取缺少关系字段时,仍保留同一订单已有的关系快照。 */
2029
+ preserveOmittedHolderRelations(incomingOrders, existingOrders) {
2030
+ const existingByIdentity = new Map();
2031
+ for (const order of existingOrders) {
2032
+ if (!Array.isArray(order.holder_relations)) continue;
2033
+ for (const key of this.getOrderIdentityMatchKeys(order)) {
2034
+ existingByIdentity.set(key, order);
2035
+ }
2036
+ }
2037
+ return incomingOrders.map(order => {
2038
+ if (Array.isArray(order.holder_relations)) return order;
2039
+ const existingOrder = this.getOrderIdentityMatchKeys(order).map(key => existingByIdentity.get(key)).find(Boolean);
2040
+ if (!existingOrder) return order;
2041
+ const nextOrder = {
2042
+ ...order
2043
+ };
2044
+ this.applyHolderRelationsSnapshot(nextOrder, existingOrder, order);
2045
+ return nextOrder;
2046
+ });
2047
+ }
2008
2048
  mergeOrderRecords(existing, incoming) {
2009
2049
  const preferred = this.pickPreferredOrder(existing, incoming);
2010
2050
  const secondary = preferred === existing ? incoming : existing;
@@ -2041,6 +2081,7 @@ class OrderModule extends _BaseModule.BaseModule {
2041
2081
  if (Object.prototype.hasOwnProperty.call(incomingRecord, 'order_meta_list')) {
2042
2082
  mergedRecord.order_meta_list = (0, _lodashEs.cloneDeep)(incomingRecord.order_meta_list);
2043
2083
  }
2084
+ this.applyHolderRelationsSnapshot(merged, existing, incoming);
2044
2085
 
2045
2086
  // 子项数组遵循完整快照语义:incoming 自有且为数组(包括 [])时权威替换;
2046
2087
  // 字段缺席或不是数组时视为稀疏 patch,继续保留 existing。
@@ -2482,7 +2523,7 @@ class OrderModule extends _BaseModule.BaseModule {
2482
2523
  async replaceOrdersSnapshotInSQLite(orderList, source, protectedOrdersBeforeRemoteFetch = []) {
2483
2524
  if (!this.dbManager) {
2484
2525
  return {
2485
- orders: this.compactOrderListByIdentity(orderList.map(order => this.normalizeRemoteSyncedOrder(order)), `${source}.snapshotNoDb`).list,
2526
+ orders: this.compactOrderListByIdentity(this.preserveOmittedHolderRelations(orderList, this.store.list).map(order => this.normalizeRemoteSyncedOrder(order)), `${source}.snapshotNoDb`).list,
2486
2527
  persisted: false
2487
2528
  };
2488
2529
  }
@@ -2492,6 +2533,7 @@ class OrderModule extends _BaseModule.BaseModule {
2492
2533
  try {
2493
2534
  await this.runInOrderSQLiteSaveQueue(async () => {
2494
2535
  const existingOrders = typeof this.dbManager.getAll === 'function' ? await this.dbManager.getAll(INDEXDB_STORE_NAME) : [];
2536
+ const remoteSnapshotWithHolderRelations = this.preserveOmittedHolderRelations(remoteSnapshot, [...protectedOrdersBeforeRemoteFetch, ...(existingOrders || [])]);
2495
2537
  const protectedLocalOrders = protectedOrdersBeforeRemoteFetch.map(order => this.normalizeOrderCollectionsForIngress(order, `${source}.protectedBeforeRemoteFetch`)).filter(order => this.shouldProtectLocalOrderFromRemoteSnapshot(order));
2496
2538
  for (const rawCurrentOrder of existingOrders || []) {
2497
2539
  const currentOrder = this.normalizeOrderCollectionsForIngress(rawCurrentOrder, `${source}.currentSQLite`);
@@ -2507,7 +2549,7 @@ class OrderModule extends _BaseModule.BaseModule {
2507
2549
  }
2508
2550
  if (currentNeedsProtection) protectedLocalOrders.push(currentOrder);
2509
2551
  }
2510
- const orderListSnapshot = this.mergeRemoteSnapshotWithPendingOrders(remoteSnapshot, protectedLocalOrders);
2552
+ const orderListSnapshot = this.mergeRemoteSnapshotWithPendingOrders(remoteSnapshotWithHolderRelations, protectedLocalOrders);
2511
2553
  mergedSnapshot = this.compactOrderListByIdentity(orderListSnapshot, `${source}.sqliteSnapshot`).list;
2512
2554
  this.logInfo('replaceOrdersSnapshotInSQLite-开始', {
2513
2555
  source,
@@ -1,4 +1,5 @@
1
1
  import type { ScheduleEventResource } from '../resource/types';
2
+ import type { OrderHolderRelation } from '../../../modules/Order/types';
2
3
  /**
3
4
  * Order 模块主键类型(对齐标准 Sales 协议的 order_id)
4
5
  */
@@ -448,6 +449,8 @@ export interface OrderData {
448
449
  contacts_info?: unknown[];
449
450
  /** 订单级主体参数 */
450
451
  holder?: Record<string, unknown> | null;
452
+ /** 非预约商品 Holder 关系完整快照;缺席表示本次回包未提供 */
453
+ holder_relations?: OrderHolderRelation[];
451
454
  /** 订单前端扩展快照 */
452
455
  order_meta_list?: {
453
456
  point_cards_snapshot?: Array<{
@@ -7,6 +7,7 @@ exports.buildSmallTicketData = buildSmallTicketData;
7
7
  exports.hasSmallTicketData = hasSmallTicketData;
8
8
  var _dayjs = _interopRequireDefault(require("dayjs"));
9
9
  var _decimal = _interopRequireDefault(require("decimal.js"));
10
+ var _holderRelations = require("../../modules/Order/holderRelations");
10
11
  var _pickupTiming = require("../../modules/Order/pickupTiming");
11
12
  var _templateVisibility = require("./templateVisibility");
12
13
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -97,6 +98,7 @@ const LABELS = {
97
98
  bookingDateTime: 'Date & time',
98
99
  bookingResource: 'Resource',
99
100
  bookingHolder: 'Holder',
101
+ productHolder: 'Holder',
100
102
  orderDiscount: 'Order discount',
101
103
  manualDiscount: 'Manual discount',
102
104
  manualMarkup: 'Manual markup',
@@ -142,6 +144,7 @@ const LABELS = {
142
144
  bookingDateTime: '日期和时间',
143
145
  bookingResource: '资源',
144
146
  bookingHolder: '预约人',
147
+ productHolder: '持有人',
145
148
  orderDiscount: '整单折扣',
146
149
  manualDiscount: '手动折扣',
147
150
  manualMarkup: '手动加价',
@@ -187,6 +190,7 @@ const LABELS = {
187
190
  bookingDateTime: '日期和時間',
188
191
  bookingResource: '資源',
189
192
  bookingHolder: '預約人',
193
+ productHolder: '持有人',
190
194
  orderDiscount: '整單折扣',
191
195
  manualDiscount: '手動折扣',
192
196
  manualMarkup: '手動加價',
@@ -230,6 +234,7 @@ const LABELS = {
230
234
  bookingDateTime: '日時',
231
235
  bookingResource: 'リソース',
232
236
  bookingHolder: '予約者',
237
+ productHolder: '利用者',
233
238
  orderDiscount: '注文割引',
234
239
  manualDiscount: '手動割引',
235
240
  manualMarkup: '手動値上げ',
@@ -274,6 +279,7 @@ const LABELS = {
274
279
  bookingDateTime: 'Data e hora',
275
280
  bookingResource: 'Recurso',
276
281
  bookingHolder: 'Titular',
282
+ productHolder: 'Titular',
277
283
  orderDiscount: 'Desconto do pedido',
278
284
  manualDiscount: 'Desconto manual',
279
285
  manualMarkup: 'Acréscimo manual',
@@ -445,6 +451,15 @@ function buildProducts(order, locale, productMap) {
445
451
  quantity,
446
452
  originalPrice
447
453
  });
454
+ const holderName = (0, _holderRelations.hasProductBooking)(product) ? '' : (0, _holderRelations.getProductHolderRelations)(order, product).map(relation => getLocalizedValue(relation.metadata?.name, locale).trim()).filter(Boolean).join(', ');
455
+ if (holderName) {
456
+ // 复用 Web Invoice / 原生均支持的文本扩展行,不混入金额或商品备注。
457
+ extensionList.push({
458
+ ...receiptKey(rawProduct, 'product-holder', 'text'),
459
+ item: `${label(locale, 'productHolder')}: ${holderName}`,
460
+ value: ''
461
+ });
462
+ }
448
463
  const productNote = resolveProductNote(product, locale);
449
464
  const allergy = isPresentId(productUid) ? allergyByProductUid.get(String(productUid).trim()) || '' : '';
450
465
  const basePrice = resolveProductBasePrice(product);
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.SALES_ORDER_RELATIONS = void 0;
7
7
  exports.loadSalesOrderSnapshot = loadSalesOrderSnapshot;
8
- const SALES_ORDER_RELATIONS = exports.SALES_ORDER_RELATIONS = ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info', 'order_meta_list', 'refunds', 'notes'];
8
+ const SALES_ORDER_RELATIONS = exports.SALES_ORDER_RELATIONS = ['products', 'bookings', 'payments', 'customer', 'summary', 'contacts_info', 'order_meta_list', 'refunds', 'notes', 'holder_relations'];
9
9
 
10
10
  /** Shared transport contract; remote callers use their host's /h5 prefix. */
11
11
  function loadSalesOrderSnapshot(request, params) {
@@ -24,6 +24,7 @@ Object.keys(_types).forEach(function (key) {
24
24
  var _Order = require("../../modules/Order");
25
25
  var _pickupTiming = require("../../modules/Order/pickupTiming");
26
26
  var _pickupTiming2 = require("./pickupTiming");
27
+ var _holderRelations = require("../../modules/Order/holderRelations");
27
28
  var _decimal = _interopRequireDefault(require("decimal.js"));
28
29
  var _lodashEs = require("lodash-es");
29
30
  var _types2 = require("../BookingByStep/types");
@@ -348,6 +349,38 @@ function areBaseSalesNotesEquivalent(currentValue, loadedValue, options) {
348
349
  const loadedNotes = normalizeBaseSalesNotesForComparison(loadedValue);
349
350
  return currentNotes !== null && loadedNotes !== null && (0, _lodashEs.isEqual)(currentNotes, loadedNotes);
350
351
  }
352
+ function mergeBaseSalesHolderRelations(currentRecord, incomingRecord, strategy) {
353
+ if (!Array.isArray(incomingRecord.holder_relations)) {
354
+ return (0, _lodashEs.cloneDeep)(currentRecord.holder_relations);
355
+ }
356
+
357
+ // 仅保留商品集合合并实际留下的本地行关系;匹配的行沿用 incoming 快照。
358
+ // 未提供 products 的稀疏包不能据此判断哪些行已被删除或被本地保留。
359
+ const retainedProductUids = new Set();
360
+ if (Array.isArray(incomingRecord.products)) {
361
+ for (const product of currentRecord.products || []) {
362
+ const retained = !incomingRecord.products.some(incomingProduct => (0, _orderCollectionIdentity.isSameOrderCollectionItem)('product', product, incomingProduct)) && (strategy === 'preserve-local' || !(0, _orderCollectionIdentity.getOrderCollectionPersistentId)('product', product));
363
+ const uid = (0, _orderCollectionIdentity.getOrderCollectionReferenceUid)('product', product);
364
+ if (retained && uid) retainedProductUids.add(uid);
365
+ }
366
+ }
367
+ const belongsToRetainedProduct = relation => relation?.relation_model === 'order_detail' && retainedProductUids.has(String(relation.relation_uuid));
368
+ const selectedRelations = [...incomingRecord.holder_relations.filter(relation => !belongsToRetainedProduct(relation)), ...(Array.isArray(currentRecord.holder_relations) ? currentRecord.holder_relations.filter(belongsToRetainedProduct) : [])];
369
+ const result = [];
370
+ const indexByIdentity = new Map();
371
+ for (const relation of selectedRelations) {
372
+ const identityParts = [relation?.relation_model, relation?.relation_uuid, relation?.holder_type, relation?.holder_id];
373
+ const identity = identityParts.every(value => value !== undefined && value !== null) ? JSON.stringify(identityParts) : null;
374
+ const existingIndex = identity === null ? undefined : indexByIdentity.get(identity);
375
+ if (existingIndex !== undefined) {
376
+ result[existingIndex] = (0, _lodashEs.cloneDeep)(relation);
377
+ } else {
378
+ if (identity !== null) indexByIdentity.set(identity, result.length);
379
+ result.push((0, _lodashEs.cloneDeep)(relation));
380
+ }
381
+ }
382
+ return result;
383
+ }
351
384
  function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, strategy) {
352
385
  const loadedHasOrderMetaList = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'order_meta_list');
353
386
  const loadedHasSalesNotes = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'notes');
@@ -366,8 +399,12 @@ function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, str
366
399
  const normalizedCurrentTempOrder = normalizeBaseSalesRecordCollections(currentTempOrder, {
367
400
  ensureUid: false
368
401
  });
402
+ const mergedHolderRelations = mergeBaseSalesHolderRelations(normalizedCurrentTempOrder, normalizedLoadedRecord, strategy);
369
403
  const uidRemaps = [];
370
- const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key) => {
404
+ const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key, object) => {
405
+ if (key === 'holder_relations' && object === normalizedCurrentTempOrder) {
406
+ return (0, _lodashEs.cloneDeep)(mergedHolderRelations);
407
+ }
371
408
  // order_meta_list 是服务端/本地订单携带的完整业务快照。若 incoming
372
409
  // 明确提供它,必须整体替换;若省略,则保留当前值,避免 snapshot
373
410
  // 数组递归拼接后出现同 ID 的旧数据。
@@ -3184,7 +3221,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3184
3221
 
3185
3222
  const walletProducts = tempOrder?.products.map(product => {
3186
3223
  const metadata = product.metadata || {};
3187
- const rawHolderId = product.holder_id ?? metadata.holder_id;
3224
+ const rawHolderId = (0, _holderRelations.hasProductBooking)(product) ? product.holder_id ?? metadata.holder_id : (0, _holderRelations.getProductHolderRelations)(tempOrder, product).filter(relation => relation.holder_type === 'form').map(relation => relation.holder_id);
3188
3225
  const holderIdValue = Array.isArray(rawHolderId) ? rawHolderId[0] : rawHolderId;
3189
3226
  const holder_id = Number.isInteger(Number(holderIdValue)) ? Number(holderIdValue) : undefined;
3190
3227
  return {
@@ -4,7 +4,7 @@ import type { ScanOrderLogInput as BaseSalesLogInput, ScanOrderLoggerProviderCon
4
4
  import type { QuantityCheckResult, QuantityLimitResult } from '../../model/strategy/adapter/itemRule';
5
5
  import type { QuotationCustomerScopeInfo } from '../../modules/Quotation/types';
6
6
  import type { PaymentMethod } from '../../modules/Payment/types';
7
- import type { OrderPaymentSource } from '../../modules/Order/types';
7
+ import type { OrderHolderRelation, OrderPaymentSource } from '../../modules/Order/types';
8
8
  import type { SubmitPayloadEnhancer } from '../../modules/Order/utils';
9
9
  /**
10
10
  * BaseSales 流程 hook 后缀。
@@ -277,6 +277,7 @@ export interface BaseSalesTempOrder {
277
277
  contacts: any[];
278
278
  contacts_info: Record<string, any> | null;
279
279
  holder: Record<string, any> | null;
280
+ holder_relations?: OrderHolderRelation[];
280
281
  metadata: Record<string, any>;
281
282
  _extend?: {
282
283
  productsByUid?: Record<string, Record<string, any>>;
@@ -14,7 +14,7 @@
14
14
  * - 不在此处发明等价关系;UI 侧(ticketBooking)若需要别的字段,由 view-model 适配层做转换
15
15
  */
16
16
  import type { BaseSalesOrderProduct } from '../types';
17
- import type { OrderMetaList } from '../../../modules/Order/types';
17
+ import type { OrderHolderRelation, OrderMetaList } from '../../../modules/Order/types';
18
18
  export interface ISalesDetailHeader {
19
19
  order_id: number | string | null;
20
20
  order_number?: string;
@@ -121,6 +121,8 @@ export interface ISalesDetail {
121
121
  header: ISalesDetailHeader;
122
122
  /** 商品行快照(保留 tempOrder / 后端详情原字段) */
123
123
  products: BaseSalesOrderProduct[];
124
+ /** 非预约商品 Holder 关系,保留关系及 metadata 的扩展字段。 */
125
+ holder_relations?: OrderHolderRelation[];
124
126
  /** 全部 bookings,按服务端原始顺序输出(含 root + child) */
125
127
  bookings: ISalesBooking[];
126
128
  /** root booking(parent_id === 0),多个时取首个,无则为 null */
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.parseSalesResponse = parseSalesResponse;
7
7
  var _orderCollectionIdentity = require("../../../modules/Order/utils/orderCollectionIdentity");
8
8
  var _pickupTiming = require("../../../modules/Order/pickupTiming");
9
+ var _lodashEs = require("lodash-es");
9
10
  /**
10
11
  * 把 `/order/sales/{id}` 响应(OrderData)解析为 ISalesDetail。
11
12
  *
@@ -130,6 +131,9 @@ function parseSalesResponse(input) {
130
131
  return {
131
132
  header: pickHeader(raw),
132
133
  products,
134
+ ...(Array.isArray(raw?.holder_relations) ? {
135
+ holder_relations: (0, _lodashEs.cloneDeep)(raw.holder_relations)
136
+ } : {}),
133
137
  bookings,
134
138
  rootBooking,
135
139
  bookingsByProductUid,
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.transformBaseProductToOrderProduct = transformBaseProductToOrderProduct;
7
7
  var _weighedProduct = require("../../../modules/Order/utils/weighedProduct");
8
+ var _serviceTimes = require("../../../modules/BookingContext/utils/serviceTimes");
8
9
  var _utils = require("../../../modules/Order/utils");
9
10
  var _manualProductDiscount = require("../../../modules/Order/utils/manualProductDiscount");
10
11
  function toNumber(value, fallback = 0) {
@@ -205,6 +206,7 @@ function transformBaseProductToOrderProduct(params) {
205
206
  });
206
207
  const derivedMainSelling = hasExplicitLineTotal ? Number(lineCompositeTotal) - Number(bundleOnlyComposite) : Number(mainProductOriginalPrice);
207
208
  const mainProductSellingPrice = toPriceString(Number.isFinite(derivedMainSelling) && derivedMainSelling >= 0 ? derivedMainSelling : Number(mainProductOriginalPrice));
209
+ const holderRelations = [payload._extend, callbackOrigin?._extend, sourceProduct?._extend].find(source => Array.isArray(source?.holder_relations))?.holder_relations;
208
210
  const metadata = {
209
211
  unique: payload.unique,
210
212
  session: payload.session,
@@ -264,6 +266,11 @@ function transformBaseProductToOrderProduct(params) {
264
266
  tax_fee: toPriceString(payload.tax_fee ?? matchedVariant?.tax_fee ?? origin?.tax_fee ?? sourceProduct?.tax_fee, '0.00'),
265
267
  is_charge_tax: toNumber(payload.is_charge_tax ?? matchedVariant?.is_charge_tax ?? origin?.is_charge_tax ?? sourceProduct?.is_charge_tax, 0),
266
268
  metadata,
269
+ ...(holderRelations !== undefined && !(0, _serviceTimes.isBookingProduct)(origin) && !(0, _serviceTimes.isBookingProduct)(payload) ? {
270
+ _extend: {
271
+ holder_relations: holderRelations
272
+ }
273
+ } : {}),
267
274
  note: payload.note != null ? String(payload.note) : '',
268
275
  product_title: productTitle,
269
276
  product_cover: firstNonBlank(matchedVariant?.cover, sourceProduct?.cover),
@@ -1641,7 +1641,10 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1641
1641
  * 从已加车的普通商品 order line 反查 SkuDetailModal edit 所需的 cacheItem。
1642
1642
  */
1643
1643
  buildNormalProductCacheItemFromOrderLine(input) {
1644
- return (0, _buildNormalProductCacheItemFromOrderLine.buildNormalProductCacheItemFromOrderLine)(input);
1644
+ return (0, _buildNormalProductCacheItemFromOrderLine.buildNormalProductCacheItemFromOrderLine)({
1645
+ ...input,
1646
+ holder_relations: input.holder_relations ?? this.store.order?.getTempOrder()?.holder_relations
1647
+ });
1645
1648
  }
1646
1649
 
1647
1650
  /** 文档别名:与 OrderModule.updateOrderProduct 一致。 */
@@ -5,7 +5,7 @@ import type { OrderProduct, OrderProductIdentity, AddProductBookingInput } from
5
5
  * cart.addProduct 两阶段决策(与 ticketBooking `handleSelectProduct` 对齐):
6
6
  *
7
7
  * 1. `requiresDetail`:规格 / SKU / 套餐 / Session / 称重弹窗(门禁:`isOpenDetailModal` / `cartDetailValue` / `open_sold_weight`)
8
- * 2. `requiresBookingEdit`:资源 / 时间编辑抽屉(kiosk 强制进入;其他渠道由 `getIsAutoClose` 决定)
8
+ * 2. `requiresBookingEdit`:资源 / 时间编辑抽屉(自助终端强制进入;其他渠道由 `getIsAutoClose` 决定)
9
9
  * 3. `add`:两关均通过,直接 transform + 加车
10
10
  */
11
11
  export type AddProductDecision = {
@@ -17,12 +17,13 @@ var _dayjs = _interopRequireDefault(require("dayjs"));
17
17
  var _BookingContext = require("../../../modules/BookingContext");
18
18
  var _transformBaseProductToOrderProduct = require("../../BaseSales/utils/transformBaseProductToOrderProduct");
19
19
  var _weighingProductSku = require("./weighingProductSku");
20
+ var _platform = require("../../../utils/platform");
20
21
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
21
22
  /**
22
23
  * cart.addProduct 两阶段决策(与 ticketBooking `handleSelectProduct` 对齐):
23
24
  *
24
25
  * 1. `requiresDetail`:规格 / SKU / 套餐 / Session / 称重弹窗(门禁:`isOpenDetailModal` / `cartDetailValue` / `open_sold_weight`)
25
- * 2. `requiresBookingEdit`:资源 / 时间编辑抽屉(kiosk 强制进入;其他渠道由 `getIsAutoClose` 决定)
26
+ * 2. `requiresBookingEdit`:资源 / 时间编辑抽屉(自助终端强制进入;其他渠道由 `getIsAutoClose` 决定)
26
27
  * 3. `add`:两关均通过,直接 transform + 加车
27
28
  */
28
29
 
@@ -39,11 +40,11 @@ function needsSpecDetailModal(item) {
39
40
  }
40
41
 
41
42
  /**
42
- * 第二段:预约商品是否仍需打开资源 / 时间编辑(kiosk 强制打开,其他渠道与 info2 `cacheItem.autoClose` 等价)。
43
+ * 第二段:预约商品是否仍需打开资源 / 时间编辑(自助终端强制打开,其他渠道与 info2 `cacheItem.autoClose` 等价)。
43
44
  */
44
45
  function needsBookingEdit(cacheItem, ctx) {
45
46
  if (isNormalBookingProduct(cacheItem)) return false;
46
- if (ctx?.channel === 'kiosk') return true;
47
+ if ((0, _platform.isSelfServicePlatform)(ctx?.channel)) return true;
47
48
  const isAutoAllocationOn = ctx?.isAutoAllocationOn || (() => true);
48
49
  return !(0, _BookingContext.getIsAutoClose)(cacheItem, isAutoAllocationOn);
49
50
  }
@@ -32,9 +32,15 @@ async function processScannedProductItem(host, item, code, bridge) {
32
32
  return runRequiresDetailFlow(host, item, ctx, bridge);
33
33
  }
34
34
  const values = (0, _scanProductValues.genScanProductValues)(item, code);
35
- const itemWithSelection = {
35
+ // 条码已唯一命中某个规格时,商品目录的 price 可能是“最低规格价”。
36
+ // 仅在扫码预选规格的这条路径改用命中规格价,避免影响商品卡片和手工选规格。
37
+ const itemWithSelection = values.price === undefined ? {
36
38
  ...item,
37
39
  cartDetailValue: values
40
+ } : {
41
+ ...item,
42
+ price: values.price,
43
+ cartDetailValue: values
38
44
  };
39
45
  const decision = host.decideAddProduct(itemWithSelection, ctx);
40
46
  return runAddProductDecision(host, item, decision, ctx, bridge);
@@ -10,6 +10,7 @@ exports.searchWalletPass = exports.searchWallet = exports.searchProduct = export
10
10
  const searchWalletPass = async (request, code) => {
11
11
  const params = {
12
12
  code,
13
+ resolve_ticket_order: 1,
13
14
  translate_flag: 1,
14
15
  // 翻译识别码名称 0:不翻译 1:翻译
15
16
  relation_product: 1,
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.handleUniversalScan = exports.handleGlobalScan = exports.handleCustomerScan = void 0;
7
7
  var _cloudSearch = require("./cloudSearch");
8
8
  var _scanCache = _interopRequireDefault(require("./scanCache"));
9
+ var _platform = require("../../../../utils/platform");
9
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
11
  /**
11
12
  * Promise.any 的替代实现
@@ -107,7 +108,7 @@ const handleGlobalScan = (request, localSearch, channel) => {
107
108
  if (value.startsWith('WL')) {
108
109
  requestList = [(0, _cloudSearch.searchWallet)(request, value)];
109
110
  } else {
110
- requestList = [...(channel === 'kiosk' ? [] : [(0, _cloudSearch.searchOrder)(request, value)]), (0, _cloudSearch.searchWalletPass)(request, value), (0, _cloudSearch.searchWallet)(request, value), (0, _cloudSearch.searchProduct)(request, value)];
111
+ requestList = [...((0, _platform.isSelfServicePlatform)(channel) ? [] : [(0, _cloudSearch.searchOrder)(request, value)]), (0, _cloudSearch.searchWalletPass)(request, value), (0, _cloudSearch.searchWallet)(request, value), (0, _cloudSearch.searchProduct)(request, value)];
111
112
  }
112
113
  return requestList;
113
114
  };
@@ -145,7 +145,7 @@ export declare class UnifiedBookingSalesImpl extends BookingTicket {
145
145
  */
146
146
  clearSessionStorageOrderSnapshot(): void;
147
147
  /**
148
- * PC/H5/Kiosk 结账默认直接等待云端 checkout;POS/WebPOS 仍保持本地待同步。
148
+ * PC/H5 及自助终端结账默认直接等待云端 checkout;POS/WebPOS 仍保持本地待同步。
149
149
  * 显式传 checkoutSyncTaskEnabled: true 时仍可回退到本地同步模式。
150
150
  */
151
151
  protected shouldUseCheckoutSyncTask(): boolean;