@pisell/pisellos 2.2.315 → 2.2.316

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 (97) 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 +2 -2
  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 +2 -1
  21. package/dist/modules/Order/utils.js +36 -10
  22. package/dist/modules/Rules/index.js +62 -61
  23. package/dist/server/index.js +36 -28
  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.d.ts +28 -7
  28. package/dist/server/modules/products/index.js +1297 -770
  29. package/dist/server/modules/products/replication.d.ts +8 -0
  30. package/dist/server/modules/products/replication.js +69 -0
  31. package/dist/server/modules/products/types.d.ts +12 -1
  32. package/dist/server/modules/products/types.js +4 -0
  33. package/dist/shared/receipt/small-ticket.js +24 -12
  34. package/dist/shared/receipt/templateVisibility.d.ts +3 -0
  35. package/dist/shared/receipt/templateVisibility.js +6 -2
  36. package/dist/shared/sales/loadSalesOrderSnapshot.js +1 -1
  37. package/dist/solution/BaseSales/index.d.ts +4 -0
  38. package/dist/solution/BaseSales/index.js +1116 -935
  39. package/dist/solution/BaseSales/types.d.ts +2 -1
  40. package/dist/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  41. package/dist/solution/BaseSales/utils/parseSalesResponse.js +7 -3
  42. package/dist/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +14 -4
  43. package/dist/solution/BookingByStep/index.d.ts +1 -1
  44. package/dist/solution/BookingTicket/index.d.ts +15 -1
  45. package/dist/solution/BookingTicket/index.js +784 -501
  46. package/dist/solution/ScanOrder/index.js +65 -31
  47. package/dist/solution/ScanOrder/types.d.ts +3 -0
  48. package/dist/solution/UnifiedBookingSales/index.js +1 -1
  49. package/lib/model/strategy/adapter/promotion/evaluator.js +9 -14
  50. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.d.ts +3 -1
  51. package/lib/modules/BookingContext/utils/buildNormalProductCacheItemFromOrderLine.js +17 -1
  52. package/lib/modules/Holder/utils.d.ts +2 -0
  53. package/lib/modules/Holder/utils.js +27 -1
  54. package/lib/modules/OpenData/contactInfo.d.ts +20 -0
  55. package/lib/modules/OpenData/contactInfo.js +56 -0
  56. package/lib/modules/OpenData/index.d.ts +1 -0
  57. package/lib/modules/OpenData/index.js +12 -0
  58. package/lib/modules/OpenData/types.d.ts +9 -0
  59. package/lib/modules/Order/holderRelations.d.ts +14 -0
  60. package/lib/modules/Order/holderRelations.js +98 -0
  61. package/lib/modules/Order/index.d.ts +2 -2
  62. package/lib/modules/Order/index.js +44 -12
  63. package/lib/modules/Order/types.d.ts +19 -1
  64. package/lib/modules/Order/types.js +1 -0
  65. package/lib/modules/Order/utils/discountProductLineIdentity.d.ts +1 -1
  66. package/lib/modules/Order/utils/discountProductLineIdentity.js +51 -2
  67. package/lib/modules/Order/utils/orderCollectionIdentity.js +5 -0
  68. package/lib/modules/Order/utils.d.ts +2 -1
  69. package/lib/modules/Order/utils.js +28 -6
  70. package/lib/modules/Rules/index.js +2 -1
  71. package/lib/server/index.js +8 -2
  72. package/lib/server/modules/order/index.d.ts +4 -0
  73. package/lib/server/modules/order/index.js +46 -4
  74. package/lib/server/modules/order/types.d.ts +3 -0
  75. package/lib/server/modules/products/index.d.ts +28 -7
  76. package/lib/server/modules/products/index.js +226 -63
  77. package/lib/server/modules/products/replication.d.ts +8 -0
  78. package/lib/server/modules/products/replication.js +30 -0
  79. package/lib/server/modules/products/types.d.ts +12 -1
  80. package/lib/server/modules/products/types.js +3 -0
  81. package/lib/shared/receipt/small-ticket.js +22 -13
  82. package/lib/shared/receipt/templateVisibility.d.ts +3 -0
  83. package/lib/shared/receipt/templateVisibility.js +6 -2
  84. package/lib/shared/sales/loadSalesOrderSnapshot.js +1 -1
  85. package/lib/solution/BaseSales/index.d.ts +4 -0
  86. package/lib/solution/BaseSales/index.js +68 -2
  87. package/lib/solution/BaseSales/types.d.ts +2 -1
  88. package/lib/solution/BaseSales/utils/parseSalesResponse.d.ts +3 -1
  89. package/lib/solution/BaseSales/utils/parseSalesResponse.js +4 -0
  90. package/lib/solution/BaseSales/utils/transformBaseProductToOrderProduct.js +16 -8
  91. package/lib/solution/BookingByStep/index.d.ts +1 -1
  92. package/lib/solution/BookingTicket/index.d.ts +15 -1
  93. package/lib/solution/BookingTicket/index.js +132 -3
  94. package/lib/solution/ScanOrder/index.js +59 -20
  95. package/lib/solution/ScanOrder/types.d.ts +3 -0
  96. package/lib/solution/UnifiedBookingSales/index.js +1 -1
  97. package/package.json +1 -1
@@ -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',
@@ -341,7 +347,7 @@ function buildLocaleData(params) {
341
347
  financials
342
348
  }),
343
349
  customer: buildCustomer(order, locale, visibility.customer),
344
- appointment: buildAppointment(order, locale),
350
+ appointment: buildAppointment(order, locale, visibility.lineGroups.resourceTitle),
345
351
  ...(delivery.length ? {
346
352
  delivery
347
353
  } : {}),
@@ -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));
@@ -1322,7 +1330,7 @@ function formatPickupBookingStart(bookings, locale) {
1322
1330
  const dateText = locale === 'en' ? `${day}/${month}/${year}` : `${year}/${month}/${day}`;
1323
1331
  return `${time.slice(0, 5)} ${dateText}`;
1324
1332
  }
1325
- function buildAppointment(order, locale) {
1333
+ function buildAppointment(order, locale, showResourceTitle) {
1326
1334
  const parentScheduleId = order.metadata?.appointment_booking_parent_schedule_id;
1327
1335
  const bookings = sortBookings(normalizeArray(order.bookings).filter(booking => !(0, _pickupTiming.isFulfillmentTimingBooking)(booking) && isReceiptAppointmentBooking(booking, parentScheduleId)));
1328
1336
  if (bookings.length) {
@@ -1331,7 +1339,8 @@ function buildAppointment(order, locale) {
1331
1339
  booking,
1332
1340
  allResources,
1333
1341
  bookingCount: bookings.length,
1334
- locale
1342
+ locale,
1343
+ showResourceTitle
1335
1344
  }));
1336
1345
  }
1337
1346
  const tableName = order.table_name || order.metadata?.table_name || order.metadata?.table?.name || order.form_data?.table_name;
@@ -1370,7 +1379,7 @@ function buildAppointmentRows(params) {
1370
1379
  };
1371
1380
  const bookingId = stringify(firstPresentValue(booking.booking_id, booking.schedule_event_id, bookingUid)).trim();
1372
1381
  const bookingTime = formatBookingRange(booking, locale);
1373
- const resourceText = formatBookingResources(resolveBookingResourcesForReceipt(booking, allResources, bookingCount), locale);
1382
+ const resourceText = formatBookingResources(resolveBookingResourcesForReceipt(booking, allResources, bookingCount), locale, params.showResourceTitle);
1374
1383
  const holder = getLocalizedValue(booking.holder?.name ?? booking.metadata?.holder?.name, locale).trim();
1375
1384
  return [{
1376
1385
  ...receiptKey(booking, 'booking-id'),
@@ -1432,8 +1441,8 @@ function resolveBookingResourcesForReceipt(booking, allResources, bookingCount)
1432
1441
  if (bookingCount === 1 && allResources.length) return allResources;
1433
1442
  return [];
1434
1443
  }
1435
- function formatBookingResources(resources, locale) {
1436
- return resources.map(resource => [getLocalizedValue(resource.form_title || resource.metadata?.form_name || resource.relation_form_name || resource.metadata?.relation_form_name, locale), getLocalizedValue(resource.title || resource.name || resource.main_field || resource.metadata?.main_field || resource.metadata?.resource_name, locale)].filter(Boolean).join(' ')).filter(Boolean).join(', ');
1444
+ function formatBookingResources(resources, locale, showResourceTitle) {
1445
+ return resources.map(resource => [showResourceTitle ? getLocalizedValue(resource.form_title || resource.metadata?.form_name || resource.relation_form_name || resource.metadata?.relation_form_name, locale) : '', getLocalizedValue(resource.title || resource.name || resource.main_field || resource.metadata?.main_field || resource.metadata?.resource_name, locale)].filter(Boolean).join(' ')).filter(Boolean).join(', ');
1437
1446
  }
1438
1447
  function sortBookings(bookings) {
1439
1448
  return bookings.map((booking, index) => ({
@@ -1,5 +1,8 @@
1
1
  /** Template children are direct module members; legacy missing values stay visible. */
2
2
  export interface InvoiceDataVisibility {
3
+ lineGroups: {
4
+ resourceTitle: boolean;
5
+ };
3
6
  handover: {
4
7
  show: boolean;
5
8
  method: boolean;
@@ -34,6 +34,9 @@ function resolveInvoiceDataVisibility(settings) {
34
34
  const field = (config, key) => invoiceSettingIsVisible(config?.[key]?.is_show);
35
35
  const passTypes = Array.isArray(card?.pass_type) && card.pass_type.length > 0 ? new Set(card.pass_type.filter(tag => typeof tag === 'string' && tag.length > 0)) : null;
36
36
  return {
37
+ lineGroups: {
38
+ resourceTitle: field(module('line_groups'), 'resource_title')
39
+ },
37
40
  handover: {
38
41
  show: invoiceSettingIsVisible(handover?.is_show),
39
42
  method: field(handover, 'method'),
@@ -57,7 +60,8 @@ function requiresInvoiceDataFiltering(settings) {
57
60
  const {
58
61
  handover,
59
62
  customer,
60
- card
63
+ card,
64
+ lineGroups
61
65
  } = resolveInvoiceDataVisibility(settings);
62
- return !handover.show || !handover.method || !handover.fulfillmentTime || !customer.show || !customer.name || !customer.email || !customer.phone || !card.show || card.passTypes !== null;
66
+ return !lineGroups.resourceTitle || !handover.show || !handover.method || !handover.fulfillmentTime || !customer.show || !customer.name || !customer.email || !customer.phone || !card.show || card.passTypes !== null;
63
67
  }
@@ -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 草稿。 */
@@ -25,6 +25,8 @@ var _Order = require("../../modules/Order");
25
25
  var _pickupTiming = require("../../modules/Order/pickupTiming");
26
26
  var _pickupTiming2 = require("./pickupTiming");
27
27
  var _fulfillmentItemRules = require("./fulfillmentItemRules");
28
+ var _holderRelations = require("../../modules/Order/holderRelations");
29
+ var _contactInfo = require("../../modules/OpenData/contactInfo");
28
30
  var _decimal = _interopRequireDefault(require("decimal.js"));
29
31
  var _lodashEs = require("lodash-es");
30
32
  var _types2 = require("../BookingByStep/types");
@@ -349,6 +351,38 @@ function areBaseSalesNotesEquivalent(currentValue, loadedValue, options) {
349
351
  const loadedNotes = normalizeBaseSalesNotesForComparison(loadedValue);
350
352
  return currentNotes !== null && loadedNotes !== null && (0, _lodashEs.isEqual)(currentNotes, loadedNotes);
351
353
  }
354
+ function mergeBaseSalesHolderRelations(currentRecord, incomingRecord, strategy) {
355
+ if (!Array.isArray(incomingRecord.holder_relations)) {
356
+ return (0, _lodashEs.cloneDeep)(currentRecord.holder_relations);
357
+ }
358
+
359
+ // 仅保留商品集合合并实际留下的本地行关系;匹配的行沿用 incoming 快照。
360
+ // 未提供 products 的稀疏包不能据此判断哪些行已被删除或被本地保留。
361
+ const retainedProductUids = new Set();
362
+ if (Array.isArray(incomingRecord.products)) {
363
+ for (const product of currentRecord.products || []) {
364
+ const retained = !incomingRecord.products.some(incomingProduct => (0, _orderCollectionIdentity.isSameOrderCollectionItem)('product', product, incomingProduct)) && (strategy === 'preserve-local' || !(0, _orderCollectionIdentity.getOrderCollectionPersistentId)('product', product));
365
+ const uid = (0, _orderCollectionIdentity.getOrderCollectionReferenceUid)('product', product);
366
+ if (retained && uid) retainedProductUids.add(uid);
367
+ }
368
+ }
369
+ const belongsToRetainedProduct = relation => relation?.relation_model === 'order_detail' && retainedProductUids.has(String(relation.relation_uuid));
370
+ const selectedRelations = [...incomingRecord.holder_relations.filter(relation => !belongsToRetainedProduct(relation)), ...(Array.isArray(currentRecord.holder_relations) ? currentRecord.holder_relations.filter(belongsToRetainedProduct) : [])];
371
+ const result = [];
372
+ const indexByIdentity = new Map();
373
+ for (const relation of selectedRelations) {
374
+ const identityParts = [relation?.relation_model, relation?.relation_uuid, relation?.holder_type, relation?.holder_id];
375
+ const identity = identityParts.every(value => value !== undefined && value !== null) ? JSON.stringify(identityParts) : null;
376
+ const existingIndex = identity === null ? undefined : indexByIdentity.get(identity);
377
+ if (existingIndex !== undefined) {
378
+ result[existingIndex] = (0, _lodashEs.cloneDeep)(relation);
379
+ } else {
380
+ if (identity !== null) indexByIdentity.set(identity, result.length);
381
+ result.push((0, _lodashEs.cloneDeep)(relation));
382
+ }
383
+ }
384
+ return result;
385
+ }
352
386
  function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, strategy) {
353
387
  const loadedHasOrderMetaList = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'order_meta_list');
354
388
  const loadedHasSalesNotes = Object.prototype.hasOwnProperty.call(loadedRecord || {}, 'notes');
@@ -367,8 +401,12 @@ function mergeSalesDetailRecordWithTempOrder(currentTempOrder, loadedRecord, str
367
401
  const normalizedCurrentTempOrder = normalizeBaseSalesRecordCollections(currentTempOrder, {
368
402
  ensureUid: false
369
403
  });
404
+ const mergedHolderRelations = mergeBaseSalesHolderRelations(normalizedCurrentTempOrder, normalizedLoadedRecord, strategy);
370
405
  const uidRemaps = [];
371
- const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key) => {
406
+ const mergedRecord = (0, _lodashEs.mergeWith)(normalizedCurrentTempOrder, normalizedLoadedRecord, (currentValue, loadedValue, key, object) => {
407
+ if (key === 'holder_relations' && object === normalizedCurrentTempOrder) {
408
+ return (0, _lodashEs.cloneDeep)(mergedHolderRelations);
409
+ }
372
410
  // order_meta_list 是服务端/本地订单携带的完整业务快照。若 incoming
373
411
  // 明确提供它,必须整体替换;若省略,则保留当前值,避免 snapshot
374
412
  // 数组递归拼接后出现同 ID 的旧数据。
@@ -1862,6 +1900,29 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1862
1900
  if (!this.store.order) throw new Error('order 模块未初始化');
1863
1901
  return this.store.order.updateTempOrderContactsInfo(contactsInfo);
1864
1902
  }
1903
+ async loadContactInfoOpenData() {
1904
+ return this.otherParams?.openData ?? null;
1905
+ }
1906
+ async getContactInfoConfig() {
1907
+ const scope = this.getPickupTimingScope();
1908
+ const data = await this.loadContactInfoOpenData();
1909
+ if (scope !== this.getPickupTimingScope()) throw new Error('Store settings changed. Please try again.');
1910
+ return (0, _contactInfo.resolveContactInfoConfig)(data);
1911
+ }
1912
+ async validateContactInfo() {
1913
+ const order = this.getTempOrder();
1914
+ const customerId = order?.customer_id;
1915
+ // Missing customer IDs and the reserved Walk-In IDs do not require contact info.
1916
+ if (!Number.isFinite(Number(customerId)) || Number(customerId) <= 1) return;
1917
+ const identity = order?._extend?.localId || order?.external_sale_number || order?.order_id || order;
1918
+ const config = await this.getContactInfoConfig();
1919
+ const current = this.getTempOrder();
1920
+ if (identity !== (current?._extend?.localId || current?.external_sale_number || current?.order_id || current) || customerId !== current?.customer_id) {
1921
+ throw new Error('The order changed. Please try again.');
1922
+ }
1923
+ const missing = (0, _contactInfo.getMissingContactInfoFields)(config, current?.contacts_info);
1924
+ if (missing.length) throw new _contactInfo.ContactInfoRequiredError(missing);
1925
+ }
1865
1926
 
1866
1927
  /** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化。 */
1867
1928
  setEnableTempOrderPersist(enabled) {
@@ -2664,6 +2725,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2664
2725
  if (!this.store.order) {
2665
2726
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2666
2727
  }
2728
+ await this.validateContactInfo();
2667
2729
  if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
2668
2730
  freeze: true
2669
2731
  });
@@ -2824,6 +2886,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2824
2886
  for (const params of unfinished) await this.finalizeLocalEftposPayment(params);
2825
2887
  throw new Error('订单已更新,请确认商品和金额后重新付款');
2826
2888
  }
2889
+ await this.validateContactInfo();
2827
2890
  const lookup = temp?.order_id || temp?.external_sale_number;
2828
2891
  let localOrder = null;
2829
2892
  if (lookup && this.core?.server?.hasRoute?.('post', '/shop/local/orderPaymentReadiness')) {
@@ -2875,6 +2938,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2875
2938
  if (!this.store.order) {
2876
2939
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2877
2940
  }
2941
+ await this.validateContactInfo();
2878
2942
  if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
2879
2943
  freeze: true
2880
2944
  });
@@ -3293,7 +3357,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3293
3357
 
3294
3358
  const walletProducts = tempOrder?.products.map(product => {
3295
3359
  const metadata = product.metadata || {};
3296
- const rawHolderId = product.holder_id ?? metadata.holder_id;
3360
+ 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);
3297
3361
  const holderIdValue = Array.isArray(rawHolderId) ? rawHolderId[0] : rawHolderId;
3298
3362
  const holder_id = Number.isInteger(Number(holderIdValue)) ? Number(holderIdValue) : undefined;
3299
3363
  return {
@@ -3709,6 +3773,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3709
3773
  */
3710
3774
  async confirmWalletPayment() {
3711
3775
  if (!this.store.order) throw new Error('order 模块未初始化');
3776
+ await this.validateContactInfo();
3712
3777
  const beforePayments = (0, _lodashEs.cloneDeep)(this.store.order.getOrderPayments());
3713
3778
  const pendingWalletAmount = this.getPendingWalletPaymentAmount(beforePayments);
3714
3779
  const amountSnapshot = this.store.order.getOrderAmountSnapshot?.();
@@ -3780,6 +3845,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3780
3845
  */
3781
3846
  async payCash(params) {
3782
3847
  if (!this.store.order) throw new Error('order 模块未初始化');
3848
+ await this.validateContactInfo();
3783
3849
  const amount = Number(params.amount || 0);
3784
3850
  if (!Number.isFinite(amount) || amount <= 0) {
3785
3851
  throw new Error('现金支付金额必须大于 0');
@@ -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,7 +5,9 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.transformBaseProductToOrderProduct = transformBaseProductToOrderProduct;
7
7
  var _weighedProduct = require("../../../modules/Order/utils/weighedProduct");
8
- var _utils = require("../../../modules/Order/utils");
8
+ var _utils = require("../../../modules/Holder/utils");
9
+ var _serviceTimes = require("../../../modules/BookingContext/utils/serviceTimes");
10
+ var _utils2 = require("../../../modules/Order/utils");
9
11
  var _manualProductDiscount = require("../../../modules/Order/utils/manualProductDiscount");
10
12
  function toNumber(value, fallback = 0) {
11
13
  const parsedValue = Number(value);
@@ -108,7 +110,7 @@ function normalizeBundleItems(bundleItems, preferPriceField = false, preferBundl
108
110
  discount_list: preferPriceField ? [] : bundleItem.discount_list
109
111
  };
110
112
  // bundle_selling_price 落「正净额」:markdown = |price| − Σoptions;markup/原价维持原值。
111
- normalizedItem.bundle_selling_price = (0, _utils.getBundleSellingMagnitude)(magnitudeSource).toFixed(2);
113
+ normalizedItem.bundle_selling_price = (0, _utils2.getBundleSellingMagnitude)(magnitudeSource).toFixed(2);
112
114
  if ((normalizedItem.price_type ?? normalizedItem.custom_price_type) === 'markdown' && (normalizedItem.price_type_ext === '' || normalizedItem.price_type_ext === undefined || normalizedItem.price_type_ext === null)) {
113
115
  normalizedItem.custom_price = normalizedItem.custom_price ?? normalizedItem.price;
114
116
  normalizedItem.price = normalizedItem.bundle_selling_price;
@@ -158,14 +160,14 @@ function transformBaseProductToOrderProduct(params) {
158
160
  const explicitLineTotal = payload.line_total ?? payload.total ?? payload._extend?.total;
159
161
  const hasExplicitLineTotal = Number.isFinite(Number(explicitLineTotal));
160
162
  const lineCompositeTotal = toPriceString(explicitLineTotal, sourceProductPrice);
161
- const productOptionItem = (0, _utils.normalizeProductSkuOptions)(normalizeOptionItems(payload.product_sku?.option ?? payload.option ?? payload.options ?? payload.product_option_item, hasPriceOverride || weighedSourcePrice !== undefined));
163
+ const productOptionItem = (0, _utils2.normalizeProductSkuOptions)(normalizeOptionItems(payload.product_sku?.option ?? payload.option ?? payload.options ?? payload.product_option_item, hasPriceOverride || weighedSourcePrice !== undefined));
162
164
  const productBundle = normalizeBundleItems(payload.bundle ?? payload.product_bundle, hasPriceOverride, hasPriceOverride && weighedSourcePrice !== undefined);
163
165
  // `payload.unique` 是业务侧商品/票务标识,可能在连续加同一商品时重复;
164
166
  // 行级唯一值只接受显式协议字段,缺省交给 OrderModule 生成。
165
167
  const uniqueIdentificationNumber = payload.unique_identification_number;
166
168
  const resolvedQuantity = toSafePositiveInt(payload.quantity ?? payload.num, 1);
167
- const originTotal = weighedSourcePrice !== undefined ? Number((0, _utils.composeLinePrice)({
168
- mainPrice: Number(weighedSourcePrice) + (0, _utils.sumOptionUnitPrice)(productOptionItem).toNumber(),
169
+ const originTotal = weighedSourcePrice !== undefined ? Number((0, _utils2.composeLinePrice)({
170
+ mainPrice: Number(weighedSourcePrice) + (0, _utils2.sumOptionUnitPrice)(productOptionItem).toNumber(),
169
171
  bundle: hasPriceOverride ? (0, _weighedProduct.withOriginalBundleOptionPrices)(productBundle) : productBundle,
170
172
  useOriginalBundle: true
171
173
  })) : (0, _manualProductDiscount.resolveManualDiscountOriginTotal)({
@@ -197,14 +199,15 @@ function transformBaseProductToOrderProduct(params) {
197
199
  })) : payload.discount_list || [];
198
200
 
199
201
  // v2:source 不含 option;main_product_* 含 option(与 normalizeOrderProduct 一致)
200
- const optionSum = (0, _utils.sumOptionUnitPrice)(productOptionItem).toNumber();
202
+ const optionSum = (0, _utils2.sumOptionUnitPrice)(productOptionItem).toNumber();
201
203
  const mainProductOriginalPrice = toPriceString(Number(sourceProductPrice) + optionSum);
202
- const bundleOnlyComposite = (0, _utils.composeLinePrice)({
204
+ const bundleOnlyComposite = (0, _utils2.composeLinePrice)({
203
205
  mainPrice: 0,
204
206
  bundle: productBundle
205
207
  });
206
208
  const derivedMainSelling = hasExplicitLineTotal ? Number(lineCompositeTotal) - Number(bundleOnlyComposite) : Number(mainProductOriginalPrice);
207
209
  const mainProductSellingPrice = toPriceString(Number.isFinite(derivedMainSelling) && derivedMainSelling >= 0 ? derivedMainSelling : Number(mainProductOriginalPrice));
210
+ const holderRelations = [payload._extend, callbackOrigin?._extend, sourceProduct?._extend].find(source => Array.isArray(source?.holder_relations))?.holder_relations;
208
211
  const metadata = {
209
212
  unique: payload.unique,
210
213
  session: payload.session,
@@ -219,7 +222,7 @@ function transformBaseProductToOrderProduct(params) {
219
222
  main_product_selling_price: mainProductSellingPrice,
220
223
  main_product_original_price: mainProductOriginalPrice,
221
224
  price_schema_version: 2,
222
- holder_config: sourceProduct?.holder_config,
225
+ holder_config: (0, _utils.resolveProductHolderConfig)(sourceProduct) ?? undefined,
223
226
  ...(dataVariantIds !== undefined ? {
224
227
  data_variant_ids: dataVariantIds
225
228
  } : {}),
@@ -264,6 +267,11 @@ function transformBaseProductToOrderProduct(params) {
264
267
  tax_fee: toPriceString(payload.tax_fee ?? matchedVariant?.tax_fee ?? origin?.tax_fee ?? sourceProduct?.tax_fee, '0.00'),
265
268
  is_charge_tax: toNumber(payload.is_charge_tax ?? matchedVariant?.is_charge_tax ?? origin?.is_charge_tax ?? sourceProduct?.is_charge_tax, 0),
266
269
  metadata,
270
+ ...(holderRelations !== undefined && !(0, _serviceTimes.isBookingProduct)(origin) && !(0, _serviceTimes.isBookingProduct)(payload) ? {
271
+ _extend: {
272
+ holder_relations: holderRelations
273
+ }
274
+ } : {}),
267
275
  note: payload.note != null ? String(payload.note) : '',
268
276
  product_title: productTitle,
269
277
  product_cover: firstNonBlank(matchedVariant?.cover, sourceProduct?.cover),
@@ -328,7 +328,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
328
328
  date: string;
329
329
  status: string;
330
330
  week: string;
331
- weekNum: 0 | 5 | 3 | 1 | 2 | 4 | 6;
331
+ weekNum: 0 | 5 | 1 | 2 | 4 | 3 | 6;
332
332
  }[]>;
333
333
  submitTimeSlot(timeSlots: TimeSliceItem): void;
334
334
  private getScheduleDataByIds;
@@ -32,6 +32,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
32
32
  private orderCustomerDiscountRefreshCustomerId;
33
33
  private orderCustomerDiscountRefreshTarget;
34
34
  private orderCustomerPromotionRefreshInFlight;
35
+ private orderCustomerContactRequest;
35
36
  private readonly customerResolveRequests;
36
37
  protected loadOpenDataInFlight: Promise<OpenDataConfig> | null;
37
38
  protected loadOpenDataInFlightTarget: string | null;
@@ -62,6 +63,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
62
63
  private getIdGeneratorPlugin;
63
64
  protected loadOpenDataConfig(): Promise<OpenDataConfig | null>;
64
65
  getOpenData(): Promise<OpenDataConfig | null>;
66
+ protected loadContactInfoOpenData(): Promise<OpenDataConfig | null>;
65
67
  protected loadFulfillmentItemRuleOpenData(): Promise<OpenDataConfig | null>;
66
68
  getInvoiceLayoutConfig(): Promise<InvoiceLayoutConfig>;
67
69
  getInvoiceTemplateSettings(): Promise<InvoiceTemplateSettings>;
@@ -281,6 +283,16 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
281
283
  setOrderCustomer(customer: ICustomer | null, options?: {
282
284
  source?: OrderCustomerChangePayload['source'];
283
285
  }): void;
286
+ private resetOrderCustomerContactInfo;
287
+ /** Explicit edits, including clearing the contact, must win over a pending default. */
288
+ updateTempOrderContactsInfo(contact: Record<string, any> | null): Record<string, any> | null;
289
+ private getCustomerContactScope;
290
+ private getCustomerContactOrderKey;
291
+ replaceTempOrder(...args: Parameters<BaseSalesImpl['replaceTempOrder']>): Promise<import("../../modules/Order/types").OrderTempOrder>;
292
+ private applyDefaultOrderCustomerContactInfo;
293
+ private prefillOrderCustomerContactInfo;
294
+ protected waitForOrderCustomerContactInfo(): Promise<void>;
295
+ validateContactInfo(): Promise<void>;
284
296
  /**
285
297
  * 读取 tempOrder 上的下单客户协议字段;customer_id 缺失时返回 null。
286
298
  */
@@ -292,7 +304,9 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
292
304
  /**
293
305
  * 清空下单客户(协议字段 + snapshot)。
294
306
  */
295
- clearOrderCustomer(): void;
307
+ clearOrderCustomer(options?: {
308
+ preserveContactInfo?: boolean;
309
+ }): void;
296
310
  /**
297
311
  * 重置 tempOrder,并同步清空下单客户运行态。
298
312
  *