@pisell/pisellos 2.3.164 → 2.3.166

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 (77) hide show
  1. package/dist/model/strategy/adapter/promotion/evaluator.js +89 -104
  2. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
  3. package/dist/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +28 -4
  4. package/dist/modules/Holder/utils.d.ts +2 -0
  5. package/dist/modules/Holder/utils.js +32 -2
  6. package/dist/modules/OpenData/contactInfo.d.ts +20 -0
  7. package/dist/modules/OpenData/contactInfo.js +70 -0
  8. package/dist/modules/OpenData/index.d.ts +1 -0
  9. package/dist/modules/OpenData/index.js +1 -0
  10. package/dist/modules/OpenData/types.d.ts +9 -0
  11. package/dist/modules/Order/holderRelations.d.ts +14 -0
  12. package/dist/modules/Order/holderRelations.js +154 -0
  13. package/dist/modules/Order/index.d.ts +1 -1
  14. package/dist/modules/Order/index.js +89 -44
  15. package/dist/modules/Order/types.d.ts +19 -1
  16. package/dist/modules/Order/types.js +2 -0
  17. package/dist/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
  18. package/dist/modules/Order/utils/discountProductLineIdentity.js +142 -7
  19. package/dist/modules/Order/utils/orderCollectionIdentity.js +24 -10
  20. package/dist/modules/Order/utils.d.ts +1 -1
  21. package/dist/modules/Order/utils.js +35 -9
  22. package/dist/modules/Rules/index.js +62 -61
  23. package/dist/server/index.js +18 -12
  24. package/dist/server/modules/order/index.d.ts +4 -0
  25. package/dist/server/modules/order/index.js +255 -190
  26. package/dist/server/modules/order/types.d.ts +3 -0
  27. package/dist/server/modules/products/index.js +3 -2
  28. package/dist/shared/receipt/small-ticket.js +17 -6
  29. package/dist/shared/sales/loadSalesOrderSnapshot.js +1 -1
  30. package/dist/solution/BaseSales/index.d.ts +4 -0
  31. package/dist/solution/BaseSales/index.js +1116 -935
  32. package/dist/solution/BaseSales/types.d.ts +2 -1
  33. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  34. package/dist/solution/BaseSales/utils/parseSalesResponse.js +7 -3
  35. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +14 -4
  36. package/dist/solution/BookingTicket/index.d.ts +15 -1
  37. package/dist/solution/BookingTicket/index.js +784 -501
  38. package/dist/solution/UnifiedBookingSales/index.js +1 -1
  39. package/lib/model/strategy/adapter/promotion/evaluator.js +9 -14
  40. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
  41. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +17 -1
  42. package/lib/modules/Holder/utils.d.ts +2 -0
  43. package/lib/modules/Holder/utils.js +27 -1
  44. package/lib/modules/OpenData/contactInfo.d.ts +20 -0
  45. package/lib/modules/OpenData/contactInfo.js +56 -0
  46. package/lib/modules/OpenData/index.d.ts +1 -0
  47. package/lib/modules/OpenData/index.js +12 -0
  48. package/lib/modules/OpenData/types.d.ts +9 -0
  49. package/lib/modules/Order/holderRelations.d.ts +14 -0
  50. package/lib/modules/Order/holderRelations.js +98 -0
  51. package/lib/modules/Order/index.d.ts +1 -1
  52. package/lib/modules/Order/index.js +44 -12
  53. package/lib/modules/Order/types.d.ts +19 -1
  54. package/lib/modules/Order/types.js +1 -0
  55. package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
  56. package/lib/modules/Order/utils/discountProductLineIdentity.js +51 -2
  57. package/lib/modules/Order/utils/orderCollectionIdentity.js +5 -0
  58. package/lib/modules/Order/utils.d.ts +1 -1
  59. package/lib/modules/Order/utils.js +27 -6
  60. package/lib/modules/Rules/index.js +2 -1
  61. package/lib/server/index.js +6 -0
  62. package/lib/server/modules/order/index.d.ts +4 -0
  63. package/lib/server/modules/order/index.js +46 -4
  64. package/lib/server/modules/order/types.d.ts +3 -0
  65. package/lib/server/modules/products/index.js +2 -1
  66. package/lib/shared/receipt/small-ticket.js +15 -7
  67. package/lib/shared/sales/loadSalesOrderSnapshot.js +1 -1
  68. package/lib/solution/BaseSales/index.d.ts +4 -0
  69. package/lib/solution/BaseSales/index.js +68 -2
  70. package/lib/solution/BaseSales/types.d.ts +2 -1
  71. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  72. package/lib/solution/BaseSales/utils/parseSalesResponse.js +4 -0
  73. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +16 -8
  74. package/lib/solution/BookingTicket/index.d.ts +15 -1
  75. package/lib/solution/BookingTicket/index.js +132 -3
  76. package/lib/solution/UnifiedBookingSales/index.js +1 -1
  77. package/package.json +1 -1
@@ -243,6 +243,11 @@ export interface OrderProduct extends OrderProductIdentity {
243
243
  product_cover?: string;
244
244
  metadata: Record<string, any>;
245
245
  note?: string;
246
+ /** 仅作按行更新输入;正式数据保存于订单顶层。 */
247
+ _extend?: {
248
+ holder_relations?: OrderHolderRelation[];
249
+ [key: string]: any;
250
+ };
246
251
  }
247
252
  export interface OrderSummary {
248
253
  product_quantity: number;
@@ -340,6 +345,17 @@ export interface SalesNotesMutationSnapshot {
340
345
  }
341
346
  export type SalesNoteProtocolRelation = SalesNoteRelation;
342
347
  export type SalesNoteProtocol = SalesNote;
348
+ /** 后端关系及 metadata 均为开放对象,未知字段必须往返保留。 */
349
+ export interface OrderHolderRelation {
350
+ /** 关系自身的 UUID;新选择/旧草稿可缺省,入单与提交时由 OS 补齐。 */
351
+ uuid?: string;
352
+ relation_model: string;
353
+ relation_uuid: string;
354
+ holder_type: 'form' | 'customer';
355
+ holder_id: number;
356
+ metadata?: Record<string, any>;
357
+ [key: string]: any;
358
+ }
343
359
  export interface OrderTempOrder {
344
360
  order_id: number | null;
345
361
  external_sale_number: string;
@@ -379,6 +395,7 @@ export interface OrderTempOrder {
379
395
  created_at: string | undefined;
380
396
  request_unique_idempotency_token?: string;
381
397
  products: OrderProduct[];
398
+ holder_relations?: OrderHolderRelation[];
382
399
  notes: SalesNote[];
383
400
  bookings: any[];
384
401
  payments: any[];
@@ -398,7 +415,7 @@ export interface OrderTempOrder {
398
415
  [key: string]: any;
399
416
  };
400
417
  }
401
- export interface OrderSubmitProduct extends Omit<OrderProduct, 'unique_identification_number' | 'num'> {
418
+ export interface OrderSubmitProduct extends Omit<OrderProduct, 'unique_identification_number' | 'num' | '_extend'> {
402
419
  product_quantity: number;
403
420
  payment_price: string;
404
421
  product_sku: OrderProductSku;
@@ -607,6 +624,7 @@ export interface SubmitSalesOrderParams {
607
624
  schedule_date: string;
608
625
  created_at?: string | undefined;
609
626
  products: OrderSubmitProduct[];
627
+ holder_relations?: OrderHolderRelation[];
610
628
  bookings: OrderSubmitBooking[];
611
629
  payments: any[];
612
630
  vouchers: any[];
@@ -63,6 +63,7 @@ let OrderHooks = exports.OrderHooks = /*#__PURE__*/function (OrderHooks) {
63
63
  * Sales Notes 乐观写入前的可恢复快照。
64
64
  * 该对象应视为 opaque token,只用于原样传回 restoreSalesNotesMutationSnapshot。
65
65
  */
66
+ /** 后端关系及 metadata 均为开放对象,未知字段必须往返保留。 */
66
67
  /**
67
68
  * 添加商品行时的顺序控制。
68
69
  * 默认追加到 products 尾部;预约数量同步可插入到既有预约组的最新行之后,
@@ -31,7 +31,7 @@ export declare const DISCOUNT_SPLIT_ORIGIN_UID_KEY = "discountSplitOriginUid";
31
31
  * 没有 UID 的裸商品不会被补写 UID。这是为了兼容 Rules/mock 的历史协议形状;
32
32
  * 正常 Order 写路径会在 normalize 阶段提前创建 UID。
33
33
  */
34
- export declare function ensureUniqueProductLineUids(tempOrder: OrderTempOrder): void;
34
+ export declare function ensureUniqueProductLineUids(tempOrder: OrderTempOrder, discounts?: Array<Record<string, any>>): void;
35
35
  /**
36
36
  * 把券移除后已经恢复原价的同源折扣拆分行重新合并。
37
37
  *
@@ -9,6 +9,7 @@ exports.ensureUniqueProductLineUids = ensureUniqueProductLineUids;
9
9
  var _lodashEs = require("lodash-es");
10
10
  var _utils = require("../../../solution/ScanOrder/utils");
11
11
  var _orderCollectionIdentity = require("./orderCollectionIdentity");
12
+ var _holderRelations = require("../holderRelations");
12
13
  /**
13
14
  * 商品券拆行的身份与回并工具。
14
15
  *
@@ -72,16 +73,20 @@ function ensureProductsByUid(tempOrder) {
72
73
  * 没有 UID 的裸商品不会被补写 UID。这是为了兼容 Rules/mock 的历史协议形状;
73
74
  * 正常 Order 写路径会在 normalize 阶段提前创建 UID。
74
75
  */
75
- function ensureUniqueProductLineUids(tempOrder) {
76
+ function ensureUniqueProductLineUids(tempOrder, discounts = []) {
76
77
  // usedUids 只描述本次 Rules 返回的商品集合,不能直接依赖 productsByUid:
77
78
  // productsByUid 可能还保留已删除行的历史 runtime,历史 UID 不应导致当前行被改号。
78
79
  const usedUids = new Set();
79
80
  const productsByUid = ensureProductsByUid(tempOrder);
81
+ const splitGroups = new Map();
80
82
  tempOrder.products = (tempOrder.products || []).map(product => {
81
83
  const sourceUid = getProductLineUid(product);
82
84
  // 单测 mock 或兼容旧 Rules 时可能返回没有 UID 的裸商品。
83
85
  // 这里不主动补 UID,避免一个“校验重复”的工具意外改变旧协议对象形状。
84
86
  if (!sourceUid) return product;
87
+ const group = splitGroups.get(sourceUid) || [];
88
+ group.push(product);
89
+ splitGroups.set(sourceUid, group);
85
90
 
86
91
  // 实际场景:可乐 ×5 首次进入 Rules 时只有一行。
87
92
  // 使用三张券后 Rules 可能返回「可乐 ×2 + 三条可乐 ×1」,但四条都复制原 UID。
@@ -122,6 +127,50 @@ function ensureUniqueProductLineUids(tempOrder) {
122
127
  usedUids.add(splitUid);
123
128
  return product;
124
129
  });
130
+
131
+ // 仅按本次券上的明确 Holder 分配迁移。数组顺序不能代表券对应的人。
132
+ // 未能明确分配时保留原 UID,不把整组关系复制给每一个拆分行。
133
+ for (const [sourceUid, products] of splitGroups) {
134
+ if (products.length < 2) continue;
135
+ const relations = (0, _holderRelations.getProductHolderRelations)(tempOrder, {
136
+ metadata: {
137
+ unique_identification_number: sourceUid
138
+ }
139
+ });
140
+ if (!relations.length || relations.some(relation => relation.holder_type !== 'form')) continue;
141
+ const assignments = new Map();
142
+ let remaining = [...relations];
143
+ let ambiguous = false;
144
+ for (const product of products) {
145
+ const holderIds = new Set();
146
+ for (const item of product.discount_list || []) {
147
+ const discount = discounts.find(candidate => String(candidate.id) === String(item.discount?.resource_id));
148
+ if (discount?.holder?.holder_type !== 'form') continue;
149
+ const value = discount.holder.holder_id;
150
+ (Array.isArray(value) ? value : [value]).forEach(id => {
151
+ const recordId = id && typeof id === 'object' ? id.form_record_id : id;
152
+ if (recordId !== undefined && recordId !== null && recordId !== '') holderIds.add(String(recordId));
153
+ });
154
+ }
155
+ if (!holderIds.size) continue;
156
+ const assigned = remaining.filter(relation => holderIds.has(String(relation.holder_id)));
157
+ if (assigned.length !== Number(product.num)) {
158
+ ambiguous = true;
159
+ break;
160
+ }
161
+ assignments.set(product, assigned);
162
+ remaining = remaining.filter(relation => !assigned.includes(relation));
163
+ }
164
+ const unassigned = products.filter(product => !assignments.has(product));
165
+ if (ambiguous || unassigned.length > 1) continue;
166
+ if (unassigned.length === 1) {
167
+ if (remaining.length !== Number(unassigned[0].num)) continue;
168
+ assignments.set(unassigned[0], remaining);
169
+ } else if (remaining.length) continue;
170
+ for (const [product, assigned] of assignments) {
171
+ for (const relation of assigned) relation.relation_uuid = String(getProductLineUid(product));
172
+ }
173
+ }
125
174
  }
126
175
 
127
176
  /**
@@ -154,7 +203,7 @@ function consolidateDiscountFreeProductLines(tempOrder) {
154
203
  // - 手动改价/备注:用户显式表达了不同价格或不同制作要求;
155
204
  // - 称重商品:每次称重的净重和金额属于独立交易事实;
156
205
  // - 预约/holder/历史订单行:分别关联预约、持有人或后端 order_detail。
157
- const hasMergeBoundary = hasDiscount || Boolean(product.metadata?._giftInfo || product._giftInfo) || Boolean(product.metadata?._promotion) || isManualProductDiscountLine(product) || hasProductLineNote(product) || Number(product.product_sku?.open_sold_weight || 0) === 1 || product.metadata?.is_edit_for_runtime === true || bookingId !== undefined && bookingId !== null && bookingId !== 0 && bookingId !== '0' || bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '' || holderId !== undefined && holderId !== null && String(holderId) !== '' || orderDetailId !== undefined && orderDetailId !== null && orderDetailId !== '';
206
+ const hasMergeBoundary = hasDiscount || Boolean(product.metadata?._giftInfo || product._giftInfo) || Boolean(product.metadata?._promotion) || isManualProductDiscountLine(product) || hasProductLineNote(product) || Number(product.product_sku?.open_sold_weight || 0) === 1 || product.metadata?.is_edit_for_runtime === true || bookingId !== undefined && bookingId !== null && bookingId !== 0 && bookingId !== '0' || bookingUid !== undefined && bookingUid !== null && String(bookingUid) !== '' || (0, _holderRelations.getProductHolderRelations)(tempOrder, product).length > 0 || holderId !== undefined && holderId !== null && String(holderId) !== '' || orderDetailId !== undefined && orderDetailId !== null && orderDetailId !== '';
158
207
  if (hasMergeBoundary) {
159
208
  consolidatedProducts.push(product);
160
209
  continue;
@@ -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
@@ -129,6 +129,7 @@ Object.defineProperty(exports, "syncManualProductDiscountProductNum", {
129
129
  return _manualProductDiscount.syncManualProductDiscountProductNum;
130
130
  }
131
131
  });
132
+ var _lodashEs = require("lodash-es");
132
133
  var _dayjs = _interopRequireDefault(require("dayjs"));
133
134
  var _customParseFormat = _interopRequireDefault(require("dayjs/plugin/customParseFormat"));
134
135
  var _decimal = _interopRequireDefault(require("decimal.js"));
@@ -139,6 +140,7 @@ var _utils2 = require("../../solution/ScanOrder/utils");
139
140
  var _orderCollectionIdentity = require("./utils/orderCollectionIdentity");
140
141
  var _salesNotes = require("./salesNotes");
141
142
  var _pickupTiming = require("./pickupTiming");
143
+ var _holderRelations = require("./holderRelations");
142
144
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
143
145
  _dayjs.default.extend(_customParseFormat.default);
144
146
  const POINT_CARD_SNAPSHOT_TAG = 'point_card';
@@ -833,7 +835,7 @@ function resolveEffectivePerUnitDiscount(product) {
833
835
  if (!Number.isFinite(originalPrice) || !Number.isFinite(finalPrice)) return fromList;
834
836
  return fromList + (originalPrice - finalPrice);
835
837
  }
836
- function createDefaultOrderRulesHooks(getBookings) {
838
+ function createDefaultOrderRulesHooks(getBookings, getHolderRelations) {
837
839
  const getProductStartDate = product => {
838
840
  const productUid = String(product.metadata?.unique_identification_number ?? product.unique_identification_number ?? '');
839
841
  const bookingUid = String(product.booking_uid ?? product.metadata?.booking_uid ?? '');
@@ -951,7 +953,9 @@ function createDefaultOrderRulesHooks(getBookings) {
951
953
  // 勿写死 false:否则 Rules 不会走 total≠origin_total 的手动价推断,
952
954
  // 无券时会把 bundle 价还原成 original_price(手动改价套餐会从 20 回到 30)。
953
955
  isManualDiscount: resolveRulesManualDiscountFlag(metadataAny),
954
- holder_id: product.metadata?.holder_id,
956
+ holder_id: (0, _holderRelations.hasProductBooking)(product) || !getHolderRelations ? product.metadata?.holder_id : (0, _holderRelations.getProductHolderRelations)({
957
+ holder_relations: getHolderRelations()
958
+ }, product).filter(relation => relation.holder_type === 'form').map(relation => relation.holder_id),
955
959
  startDate: getProductStartDate(product),
956
960
  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,
957
961
  inPromotion: metadataAny._promotion?.inPromotion === true,
@@ -1242,6 +1246,7 @@ function normalizeSubmitProduct(product, formatOptions = {}) {
1242
1246
  } = product;
1243
1247
  const {
1244
1248
  num,
1249
+ _extend: _productExtend,
1245
1250
  surcharge_fee: _productSurchargeFee,
1246
1251
  discount_amount: _deprecatedDiscountAmount,
1247
1252
  discount_type: _deprecatedDiscountType,
@@ -1428,6 +1433,7 @@ function createDefaultTempOrder(params) {
1428
1433
  request_unique_idempotency_token: '',
1429
1434
  vouchers: [],
1430
1435
  products: [],
1436
+ holder_relations: [],
1431
1437
  notes: [],
1432
1438
  bookings: [],
1433
1439
  payments: [],
@@ -1503,6 +1509,7 @@ function buildSubmitPayload(params) {
1503
1509
  summary,
1504
1510
  enhance
1505
1511
  } = params;
1512
+ (0, _holderRelations.ensureHolderRelationUuids)(tempOrder);
1506
1513
  const scheduleDate = tempOrder.schedule_date || tempOrder.created_at || formatDateTime(now);
1507
1514
  const bookingUuid = (0, _orderCollectionIdentity.createUuidV4)();
1508
1515
  const virtualSettlementMode = tempOrder.metadata?.currency_settlement_config?.settlement_type === 'virtual_currency';
@@ -1542,6 +1549,9 @@ function buildSubmitPayload(params) {
1542
1549
  });
1543
1550
  const payload = {
1544
1551
  ...tempOrderRest,
1552
+ ...(tempOrder.holder_relations !== undefined ? {
1553
+ holder_relations: (0, _lodashEs.cloneDeep)(tempOrder.holder_relations)
1554
+ } : {}),
1545
1555
  customer_id: tempOrder.customer_id ?? 1,
1546
1556
  customer_name: tempOrder.customer_name || 'Walk-In',
1547
1557
  country_calling_code: String(tempOrder.country_calling_code ?? ''),
@@ -1734,15 +1744,24 @@ function hasAssignedHolderId(value) {
1734
1744
  * clearTempOrderHolderAssignments(tempOrder);
1735
1745
  */
1736
1746
  function clearTempOrderHolderAssignments(tempOrder) {
1737
- let changed = false;
1747
+ const previousRelationCount = tempOrder.holder_relations?.length || 0;
1748
+ (0, _holderRelations.removeProductHolderRelations)(tempOrder, (tempOrder.products || []).filter(product => !(0, _holderRelations.hasProductBooking)(product)));
1749
+ let changed = previousRelationCount !== (tempOrder.holder_relations?.length || 0);
1738
1750
  const resetHolderField = (target, key = 'holder_id') => {
1739
1751
  if (!hasAssignedHolderId(target[key])) return;
1740
1752
  target[key] = null;
1741
1753
  changed = true;
1742
1754
  };
1755
+ const clearHolder = target => {
1756
+ resetHolderField(target);
1757
+ if (target.holder !== undefined && target.holder !== null) {
1758
+ target.holder = null;
1759
+ changed = true;
1760
+ }
1761
+ };
1743
1762
  for (const product of tempOrder.products || []) {
1744
1763
  if (product.metadata && typeof product.metadata === 'object') {
1745
- resetHolderField(product.metadata);
1764
+ clearHolder(product.metadata);
1746
1765
  }
1747
1766
  }
1748
1767
  for (const booking of tempOrder.bookings || []) {
@@ -1760,11 +1779,13 @@ function clearTempOrderHolderAssignments(tempOrder) {
1760
1779
  for (const entry of Object.values(productsByUid)) {
1761
1780
  const origin = entry?.origin;
1762
1781
  if (!origin || typeof origin !== 'object') continue;
1782
+ clearHolder(origin);
1763
1783
  if (origin.metadata && typeof origin.metadata === 'object') {
1764
- resetHolderField(origin.metadata);
1784
+ clearHolder(origin.metadata);
1765
1785
  }
1766
1786
  if (origin._extend && typeof origin._extend === 'object') {
1767
- resetHolderField(origin._extend);
1787
+ clearHolder(origin._extend);
1788
+ delete origin._extend.holder_relations;
1768
1789
  }
1769
1790
  }
1770
1791
  }
@@ -8,6 +8,7 @@ var _BaseModule = require("../BaseModule");
8
8
  var _types = require("./types");
9
9
  var _utils = require("../../solution/ShopDiscount/utils");
10
10
  var _utils2 = require("../Cart/utils");
11
+ var _utils3 = require("../Holder/utils");
11
12
  var _decimal = _interopRequireDefault(require("decimal.js"));
12
13
  var _lodashEs = require("lodash-es");
13
14
  var _dayjs = _interopRequireDefault(require("dayjs"));
@@ -59,7 +60,7 @@ class RulesModule extends _BaseModule.BaseModule {
59
60
  if (discount.holder?.holder_type !== 'form') return true;
60
61
 
61
62
  // getProduct 会裁剪商品字段,从原始商品读取配置,套餐子项沿用父商品。
62
- const holderConfig = holderSource?.holder_config ?? holderSource?.metadata?.holder_config ?? holderSource?.origin?.holder_config ?? holderSource?.metadata?.origin?.holder_config ?? holderSource?._origin?.holder_config ?? holderSource?._extend?.holder_config ?? holderSource?._extend?.product?.holder_config ?? holderSource?._extend?.product?.metadata?.holder_config;
63
+ const holderConfig = (0, _utils3.resolveProductHolderConfig)(holderSource);
63
64
  const holderStatus = holderConfig?.status;
64
65
  const holderEnabled = holderStatus === true || ['enable', '1'].includes(String(holderStatus).trim().toLowerCase());
65
66
  if (!holderEnabled || holderConfig?.resource_id === undefined || holderConfig?.resource_id === null || holderConfig?.resource_id === '') {
@@ -836,6 +836,9 @@ class Server {
836
836
  need_sync: 0,
837
837
  is_draft_order: 0
838
838
  };
839
+ if (!Array.isArray(fresh.holder_relations) && Array.isArray(checkoutData?.holder_relations)) {
840
+ syncedOrder.holder_relations = (0, _lodashEs.cloneDeep)(checkoutData.holder_relations);
841
+ }
839
842
  await this.order.upsertOrdersFromRemote([syncedOrder], 'checkoutSync');
840
843
  this.logInfo(`${title}: sync_sales 完整订单已同步到本地`, {
841
844
  order_id: fresh.order_id,
@@ -5502,6 +5505,9 @@ class Server {
5502
5505
  if (fresh.order_number !== undefined && fresh.order_number !== null) {
5503
5506
  patch.order_number = fresh.order_number;
5504
5507
  }
5508
+ if (Array.isArray(fresh.holder_relations)) {
5509
+ patch.holder_relations = (0, _lodashEs.cloneDeep)(fresh.holder_relations);
5510
+ }
5505
5511
  return patch;
5506
5512
  }
5507
5513
  buildSyncedOrderForPrint(params) {
@@ -326,6 +326,10 @@ export declare class OrderModule extends BaseModule implements Module {
326
326
  private getIdentifiedPendingPayments;
327
327
  private shouldProtectLocalOrderFromRemoteSnapshot;
328
328
  private getUnmatchedIdentifiedPendingPayments;
329
+ /** 关系数组是完整快照,不能拼接或按商品子项生成 UID;扩展字段整体保留。 */
330
+ private applyHolderRelationsSnapshot;
331
+ /** 全量拉取缺少关系字段时,仍保留同一订单已有的关系快照。 */
332
+ private preserveOmittedHolderRelations;
329
333
  private mergeOrderRecords;
330
334
  private summarizeDuplicateOrders;
331
335
  private logDuplicateOrders;
@@ -289,6 +289,7 @@ class OrderModule extends _BaseModule.BaseModule {
289
289
  const existingRecord = existing;
290
290
  const incomingRecord = incoming;
291
291
  const uidRemaps = [];
292
+ this.applyHolderRelationsSnapshot(reconciled, existing, incoming);
292
293
  for (const field of ORDER_AUTHORITATIVE_COLLECTION_FIELDS) {
293
294
  if (!Array.isArray(incomingRecord[field])) continue;
294
295
  reconciled[field] = this.mergeAuthoritativeOrderCollection(field, existingRecord[field], incomingRecord[field], uidRemaps);
@@ -365,7 +366,7 @@ class OrderModule extends _BaseModule.BaseModule {
365
366
  continue;
366
367
  }
367
368
 
368
- // 远端商品成员确实发生增删时必须落库,避免内存已更新但 SQLite 仍被回声窗口跳过。
369
+ // 远端商品成员或 Holder 关系确实变化时必须落库,避免被回声窗口跳过。
369
370
  if (forceWriteStorageKeys.has(storageKey)) {
370
371
  toWrite.push(order);
371
372
  continue;
@@ -1339,6 +1340,7 @@ class OrderModule extends _BaseModule.BaseModule {
1339
1340
  if (matchIndex >= 0) {
1340
1341
  const existingOrder = updatedList[matchIndex];
1341
1342
  const reconciledDraftOrder = this.preserveExistingBusinessCodeForDraft(existingOrder, draftOrder);
1343
+ this.applyHolderRelationsSnapshot(reconciledDraftOrder, existingOrder, draftOrder);
1342
1344
  const mergedOrder = this.mergeOrderRecords(existingOrder, reconciledDraftOrder);
1343
1345
  const nextOrder = {
1344
1346
  ...mergedOrder,
@@ -1617,7 +1619,10 @@ class OrderModule extends _BaseModule.BaseModule {
1617
1619
  const hasSingleId = routeInfo.hasSingleId;
1618
1620
  const normalizedBody = this.normalizeOrderSyncPayload(msg.body || msg.data);
1619
1621
  if (routeInfo.route === 'bodyUpsert' && normalizedBody) {
1620
- upsertOrders.set(this.getIdKey(normalizedBody.order_id), normalizedBody);
1622
+ const orderKey = this.getIdKey(normalizedBody.order_id);
1623
+ const previousBody = upsertOrders.get(orderKey);
1624
+ if (previousBody) this.applyHolderRelationsSnapshot(normalizedBody, previousBody, normalizedBody);
1625
+ upsertOrders.set(orderKey, normalizedBody);
1621
1626
  return;
1622
1627
  }
1623
1628
  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);
@@ -1810,6 +1815,9 @@ class OrderModule extends _BaseModule.BaseModule {
1810
1815
  const mergedStorageKey = this.getOrderStorageKey(mergedOrder);
1811
1816
  if (mergedStorageKey) forceWriteStorageKeys.add(mergedStorageKey);
1812
1817
  }
1818
+ if (nextStorageKey && !(0, _lodashEs.isEqual)(existingOrder.holder_relations, mergedOrder.holder_relations)) {
1819
+ forceWriteStorageKeys.add(nextStorageKey);
1820
+ }
1813
1821
  // 合并后身份字段可能新增(如挂单拿到 order_id),同步登记到索引,
1814
1822
  // 供同批后续 fresh 命中同一槽位,避免重复插入
1815
1823
  this.registerOrderIdentityKeys(identityKeyToIndex, mergedOrder, adjustedMatchIndex);
@@ -2110,6 +2118,38 @@ class OrderModule extends _BaseModule.BaseModule {
2110
2118
  const incomingIdentityKeys = new Set(incomingPayments.flatMap(payment => (0, _paymentUtils.getOrderPaymentIdentityKeys)(payment)));
2111
2119
  return existingPayments.filter(payment => (0, _paymentUtils.isIdentifiedPendingOrderPayment)(payment) && !(0, _paymentUtils.getOrderPaymentIdentityKeys)(payment).some(key => incomingIdentityKeys.has(key)));
2112
2120
  }
2121
+
2122
+ /** 关系数组是完整快照,不能拼接或按商品子项生成 UID;扩展字段整体保留。 */
2123
+ applyHolderRelationsSnapshot(target, existing, incoming) {
2124
+ if (Array.isArray(incoming.holder_relations)) {
2125
+ target.holder_relations = (0, _lodashEs.cloneDeep)(incoming.holder_relations);
2126
+ } else if (Array.isArray(existing.holder_relations)) {
2127
+ target.holder_relations = (0, _lodashEs.cloneDeep)(existing.holder_relations);
2128
+ } else {
2129
+ delete target.holder_relations;
2130
+ }
2131
+ }
2132
+
2133
+ /** 全量拉取缺少关系字段时,仍保留同一订单已有的关系快照。 */
2134
+ preserveOmittedHolderRelations(incomingOrders, existingOrders) {
2135
+ const existingByIdentity = new Map();
2136
+ for (const order of existingOrders) {
2137
+ if (!Array.isArray(order.holder_relations)) continue;
2138
+ for (const key of this.getOrderIdentityMatchKeys(order)) {
2139
+ existingByIdentity.set(key, order);
2140
+ }
2141
+ }
2142
+ return incomingOrders.map(order => {
2143
+ if (Array.isArray(order.holder_relations)) return order;
2144
+ const existingOrder = this.getOrderIdentityMatchKeys(order).map(key => existingByIdentity.get(key)).find(Boolean);
2145
+ if (!existingOrder) return order;
2146
+ const nextOrder = {
2147
+ ...order
2148
+ };
2149
+ this.applyHolderRelationsSnapshot(nextOrder, existingOrder, order);
2150
+ return nextOrder;
2151
+ });
2152
+ }
2113
2153
  mergeOrderRecords(existing, incoming) {
2114
2154
  const preferred = this.pickPreferredOrder(existing, incoming);
2115
2155
  const secondary = preferred === existing ? incoming : existing;
@@ -2152,6 +2192,7 @@ class OrderModule extends _BaseModule.BaseModule {
2152
2192
  if (Object.prototype.hasOwnProperty.call(incomingRecord, 'order_meta_list')) {
2153
2193
  mergedRecord.order_meta_list = (0, _lodashEs.cloneDeep)(incomingRecord.order_meta_list);
2154
2194
  }
2195
+ this.applyHolderRelationsSnapshot(merged, existing, incoming);
2155
2196
 
2156
2197
  // 子项数组遵循完整快照语义:incoming 自有且为数组(包括 [])时权威替换;
2157
2198
  // 字段缺席或不是数组时视为稀疏 patch,继续保留 existing。
@@ -2593,7 +2634,7 @@ class OrderModule extends _BaseModule.BaseModule {
2593
2634
  async replaceOrdersSnapshotInSQLite(orderList, source, protectedOrdersBeforeRemoteFetch = []) {
2594
2635
  if (!this.dbManager) {
2595
2636
  return {
2596
- orders: this.compactOrderListByIdentity(orderList.map(order => this.normalizeRemoteSyncedOrder(order)), `${source}.snapshotNoDb`).list,
2637
+ orders: this.compactOrderListByIdentity(this.preserveOmittedHolderRelations(orderList, this.store.list).map(order => this.normalizeRemoteSyncedOrder(order)), `${source}.snapshotNoDb`).list,
2597
2638
  persisted: false
2598
2639
  };
2599
2640
  }
@@ -2603,6 +2644,7 @@ class OrderModule extends _BaseModule.BaseModule {
2603
2644
  try {
2604
2645
  await this.runInOrderSQLiteSaveQueue(async () => {
2605
2646
  const existingOrders = typeof this.dbManager.getAll === 'function' ? await this.dbManager.getAll(INDEXDB_STORE_NAME) : [];
2647
+ const remoteSnapshotWithHolderRelations = this.preserveOmittedHolderRelations(remoteSnapshot, [...protectedOrdersBeforeRemoteFetch, ...(existingOrders || [])]);
2606
2648
  const protectedLocalOrders = protectedOrdersBeforeRemoteFetch.map(order => this.normalizeOrderCollectionsForIngress(order, `${source}.protectedBeforeRemoteFetch`)).filter(order => this.shouldProtectLocalOrderFromRemoteSnapshot(order));
2607
2649
  for (const rawCurrentOrder of existingOrders || []) {
2608
2650
  const currentOrder = this.normalizeOrderCollectionsForIngress(rawCurrentOrder, `${source}.currentSQLite`);
@@ -2618,7 +2660,7 @@ class OrderModule extends _BaseModule.BaseModule {
2618
2660
  }
2619
2661
  if (currentNeedsProtection) protectedLocalOrders.push(currentOrder);
2620
2662
  }
2621
- const orderListSnapshot = this.mergeRemoteSnapshotWithPendingOrders(remoteSnapshot, protectedLocalOrders);
2663
+ const orderListSnapshot = this.mergeRemoteSnapshotWithPendingOrders(remoteSnapshotWithHolderRelations, protectedLocalOrders);
2622
2664
  mergedSnapshot = this.compactOrderListByIdentity(orderListSnapshot, `${source}.sqliteSnapshot`).list;
2623
2665
  this.logInfo('replaceOrdersSnapshotInSQLite-开始', {
2624
2666
  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<{
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.ProductsModule = void 0;
7
7
  var _BaseModule = require("../../../modules/BaseModule");
8
+ var _utils = require("../../../modules/Holder/utils");
8
9
  var _Quotation = require("../../../modules/Quotation");
9
10
  var _types = require("./types");
10
11
  var _product = require("../../utils/product");
@@ -983,7 +984,7 @@ class ProductsModule extends _BaseModule.BaseModule {
983
984
  if (!product) continue;
984
985
  snapshots[String(id)] = {
985
986
  cover: product.cover,
986
- holder_config: product.holder_config
987
+ holder_config: (0, _utils.resolveProductHolderConfig)(product) ?? undefined
987
988
  };
988
989
  }
989
990
  return snapshots;
@@ -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'),
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);
@@ -1165,13 +1180,6 @@ function buildFees(params) {
1165
1180
  const isPriceIncludeTax = firstPresentValue(summary.is_price_include_tax, order.is_price_include_tax, shopInfo.is_price_include_tax);
1166
1181
  const roundingAmount = firstPresentValue(summary.rounding_amount, order.rounding_amount);
1167
1182
  const payProcessingFee = financials.payProcessingFee;
1168
- if (isNonZero(payProcessingFee)) {
1169
- fees.push({
1170
- ...receiptKey(order, 'payment-processing-fee'),
1171
- item: label(locale, 'payProcessingFee'),
1172
- value: formatMoney(payProcessingFee, currencySymbol)
1173
- });
1174
- }
1175
1183
  const productExpectAmount = firstPresentValue(summary.product_expect_amount, summary.product_amount, order.product_expect_amount);
1176
1184
  const productOriginalAmount = firstPresentValue(summary.product_original_amount, order.product_original_amount) ?? calculateProductOriginalAmount(order);
1177
1185
  const totalSavingsAmount = new _decimal.default(toMoneyNumber(productOriginalAmount)).minus(toMoneyNumber(productExpectAmount)).plus(toMoneyNumber(discountAmount));
@@ -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) {
@@ -5,6 +5,7 @@ import { BaseSalesOrderProduct, BaseSalesOrderProductIdentity, BaseSalesPaymentS
5
5
  import { OrderModule } from '../../modules/Order';
6
6
  import { type PickupTimingBookingInput } from '../../modules/Order/pickupTiming';
7
7
  import type { PickupTimingAvailability, PickupTimingSelection, PickupTimingValidationResult } from '../../modules/FulfillmentTiming';
8
+ import { type ContactInfoConfig } from '../../modules/OpenData/contactInfo';
8
9
  import type { AddProductBookingInput, AddProductToOrderOptions, LoadSalesDetailParams, OrderPaymentData, OrderPaymentUpdateOptions, OrderPaymentUpdateResult, OrderPaymentSource, SendCustomerPayLinkParams, SyncPaymentsToOrderParams, SyncPaymentsToOrderResult, UpdateTempOrderCustomerInput, UpdateOrderBookingParams, UpdateOrderProductParams, OrderTempOrder, OrderSummary, OrderPromotionEvaluator, OrderGiftSelectResolver, OrderUnfulfilledPromotion, OrderLastGiftActions, RemoveProductsFromOrderOptions, ReplaceTempOrderOptions, SalesNote, SalesNotesMutationSnapshot, SetSalesNoteInput, PickupReferenceMode, ShopServiceData, ShopServiceType } from '../../modules/Order/types';
9
10
  import type { SubmitPayloadEnhancer } from '../../modules/Order/utils';
10
11
  import type { Discount, VirtualCurrencyAsset } from '../../modules/Discount/types';
@@ -281,6 +282,9 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
281
282
  setPickupTimingBooking(input: PickupTimingBookingInput): import("../../modules/Order").PickupTimingBooking;
282
283
  clearPickupTimingBooking(): void;
283
284
  updateTempOrderContactsInfo(contactsInfo: Record<string, any> | null): Record<string, any> | null;
285
+ protected loadContactInfoOpenData(): Promise<OpenDataConfig | null>;
286
+ getContactInfoConfig(): Promise<ContactInfoConfig>;
287
+ validateContactInfo(): Promise<void>;
284
288
  /** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化。 */
285
289
  setEnableTempOrderPersist(enabled: boolean): void;
286
290
  /** 是否允许 saveDraft 写入 IndexedDB 草稿。 */