@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
@@ -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),
@@ -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
  *
@@ -39,6 +39,9 @@ var _orderLineDisplay = require("../../modules/BookingContext/utils/orderLineDis
39
39
  var _bookingStatus = require("./utils/bookingStatus");
40
40
  var _resolveBestAddTimePlan = require("./utils/resolveBestAddTimePlan");
41
41
  var _utils = require("../../modules/Order/utils");
42
+ var _contactInfo = require("../../modules/OpenData/contactInfo");
43
+ var _platform = require("../../utils/platform");
44
+ var _lodashEs = require("lodash-es");
42
45
  var _types2 = require("../../server/modules/form/types");
43
46
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
44
47
  const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'documents'];
@@ -163,6 +166,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
163
166
  orderCustomerDiscountRefreshCustomerId = null;
164
167
  orderCustomerDiscountRefreshTarget = null;
165
168
  orderCustomerPromotionRefreshInFlight = null;
169
+ orderCustomerContactRequest = null;
166
170
  customerResolveRequests = new Map();
167
171
  loadOpenDataInFlight = null;
168
172
  loadOpenDataInFlightTarget = null;
@@ -300,6 +304,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
300
304
  async getOpenData() {
301
305
  return this.loadOpenDataConfig();
302
306
  }
307
+ async loadContactInfoOpenData() {
308
+ return this.store.openData ? this.getOpenData() : super.loadContactInfoOpenData();
309
+ }
303
310
  async loadFulfillmentItemRuleOpenData() {
304
311
  return this.store.openData ? this.getOpenData() : super.loadFulfillmentItemRuleOpenData();
305
312
  }
@@ -1035,7 +1042,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1035
1042
  */
1036
1043
  setOrderCustomer(customer, options) {
1037
1044
  if (!customer) {
1038
- this.clearOrderCustomer();
1045
+ this.clearOrderCustomer({
1046
+ preserveContactInfo: options?.source === 'detailHydrate'
1047
+ });
1039
1048
  return;
1040
1049
  }
1041
1050
  const previousCustomerId = this.store.order.getOrderCustomer()?.customer_id;
@@ -1046,7 +1055,16 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1046
1055
  this.discountConfigCacheKey = null;
1047
1056
  this.hasManualDiscountSelection = false;
1048
1057
  }
1058
+ if (!customerUnchanged && options?.source !== 'detailHydrate') {
1059
+ this.resetOrderCustomerContactInfo();
1060
+ this.store.order.updateTempOrderContactsInfo((0, _lodashEs.cloneDeep)((0, _contactInfo.extractCustomerContactInfo)(customer)));
1061
+ }
1049
1062
  this.store.order.setOrderCustomer(patch, customer);
1063
+ if (options?.source === 'detailHydrate') {
1064
+ this.orderCustomerContactRequest = null;
1065
+ } else {
1066
+ this.prefillOrderCustomerContactInfo(customer, !customerUnchanged);
1067
+ }
1050
1068
  const snapshotAfterOrderSet = this.store.order.getOrderCustomerSnapshot();
1051
1069
  if (customerUnchanged) {
1052
1070
  // hydrate 后 customer_id 已存在但 _extend.customerSnapshot 仍为空时,
@@ -1079,6 +1097,105 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1079
1097
  this.orderCustomerPromotionRefreshInFlight = null;
1080
1098
  });
1081
1099
  }
1100
+ resetOrderCustomerContactInfo() {
1101
+ this.orderCustomerContactRequest = null;
1102
+ const order = this.getTempOrder();
1103
+ if (order?._extend) delete order._extend.contactInfoEdited;
1104
+ }
1105
+
1106
+ /** Explicit edits, including clearing the contact, must win over a pending default. */
1107
+ updateTempOrderContactsInfo(contact) {
1108
+ const order = this.store.order.ensureTempOrder();
1109
+ order._extend || (order._extend = {});
1110
+ order._extend.contactInfoEdited = true;
1111
+ this.orderCustomerContactRequest = null;
1112
+ return super.updateTempOrderContactsInfo(contact);
1113
+ }
1114
+ getCustomerContactScope() {
1115
+ return JSON.stringify([this.getPickupTimingScope(), this.otherParams?.channel, this.otherParams?.shop_id, this.otherParams?.tenantId]);
1116
+ }
1117
+ getCustomerContactOrderKey() {
1118
+ const order = this.getTempOrder();
1119
+ // Cart mutations can replace the object without changing the order.
1120
+ const identity = order?.external_sale_number || order?.request_unique_idempotency_token || order?._extend?.localId || order?.order_id;
1121
+ return identity ? JSON.stringify([identity, order?.order_id]) : order;
1122
+ }
1123
+ async replaceTempOrder(...args) {
1124
+ this.orderCustomerContactRequest = null;
1125
+ return super.replaceTempOrder(...args);
1126
+ }
1127
+ async applyDefaultOrderCustomerContactInfo(contact) {
1128
+ this.store.order.updateTempOrderContactsInfo((0, _lodashEs.cloneDeep)(contact));
1129
+ // Metadata may finish after promotion/discount refresh; publish the updated cart explicitly.
1130
+ await this.effectsEmit('onTempOrderReplaced', {
1131
+ tempOrder: this.getTempOrder()
1132
+ });
1133
+ }
1134
+ prefillOrderCustomerContactInfo(customer, customerChanged) {
1135
+ const order = this.getTempOrder();
1136
+ const customerId = Number(order?.customer_id);
1137
+ if (!order || !Number.isInteger(customerId) || customerId <= 1 || order.contacts_info != null || order._extend?.contactInfoEdited) return;
1138
+ // A restored new cart may already have its customer ID. Existing sales keep their snapshot,
1139
+ // even when it is empty; only an explicit customer switch replaces it.
1140
+ if (!customerChanged && order.order_id && !String(order.order_id).startsWith('local_order')) return;
1141
+ const contact = (0, _contactInfo.extractCustomerContactInfo)(customer);
1142
+ if (contact) {
1143
+ this.orderCustomerContactRequest = null;
1144
+ void this.applyDefaultOrderCustomerContactInfo(contact);
1145
+ return;
1146
+ }
1147
+ // A relation explicitly returned as null is authoritative. Only a missing relation needs fetching.
1148
+ if (customer.contacts_info !== undefined) return;
1149
+ const userPlatform = (0, _platform.isCustomerUserPlatform)(this.otherParams?.platform);
1150
+ const getAuthenticatedCustomerId = () => Number(this.core?.getPlugin?.('user')?.get?.()?.id);
1151
+ if (userPlatform && getAuthenticatedCustomerId() !== customerId) return;
1152
+ const scope = this.getCustomerContactScope();
1153
+ const orderKey = this.getCustomerContactOrderKey();
1154
+ const previous = this.orderCustomerContactRequest;
1155
+ if (previous?.orderKey === orderKey && previous.customerId === customerId && previous.scope === scope) return;
1156
+ const pending = {
1157
+ orderKey,
1158
+ customerId,
1159
+ scope,
1160
+ promise: Promise.resolve()
1161
+ };
1162
+ this.orderCustomerContactRequest = pending;
1163
+ const loadContact = async () => {
1164
+ if (userPlatform) {
1165
+ // Same endpoint as BookingByStep.getContactInfo; the PC/H5 host adds /h5 and login headers.
1166
+ const result = await this.request.get('/customer/metadata', {
1167
+ key: 'contacts_info'
1168
+ });
1169
+ if (result?.code !== 200 || result?.status === false) throw new Error('Customer contact lookup failed');
1170
+ return (0, _contactInfo.isContactRecord)(result.data) ? result.data : null;
1171
+ }
1172
+ // POS and physical Kiosk share the /shop request plugin and current-shop customer resolver.
1173
+ const resolved = await this.resolveCustomerById(customerId);
1174
+ return Number(resolved?.id) === customerId ? (0, _contactInfo.extractCustomerContactInfo)(resolved) : null;
1175
+ };
1176
+ pending.promise = loadContact().then(async value => {
1177
+ const current = this.getTempOrder();
1178
+ if (this.orderCustomerContactRequest !== pending || this.getCustomerContactOrderKey() !== orderKey || Number(current?.customer_id) !== customerId || scope !== this.getCustomerContactScope() || current?._extend?.contactInfoEdited || current?.contacts_info != null || userPlatform && getAuthenticatedCustomerId() !== customerId) return;
1179
+ if (value) await this.applyDefaultOrderCustomerContactInfo(value);
1180
+ }).catch(() => {
1181
+ // A failed default does not prevent manual entry; a subsequent sync can retry.
1182
+ if (this.orderCustomerContactRequest === pending) this.orderCustomerContactRequest = null;
1183
+ console.warn('[BookingTicket] Failed to load saved customer contact information');
1184
+ });
1185
+ }
1186
+ waitForOrderCustomerContactInfo() {
1187
+ return this.orderCustomerContactRequest?.promise ?? Promise.resolve();
1188
+ }
1189
+ async validateContactInfo() {
1190
+ const orderKey = this.getCustomerContactOrderKey();
1191
+ const customerId = this.getTempOrder()?.customer_id;
1192
+ const scope = this.getCustomerContactScope();
1193
+ await this.waitForOrderCustomerContactInfo();
1194
+ if (this.getCustomerContactOrderKey() !== orderKey || this.getTempOrder()?.customer_id !== customerId || this.getCustomerContactScope() !== scope) {
1195
+ throw new Error('The order changed. Please try again.');
1196
+ }
1197
+ await super.validateContactInfo();
1198
+ }
1082
1199
 
1083
1200
  /**
1084
1201
  * 读取 tempOrder 上的下单客户协议字段;customer_id 缺失时返回 null。
@@ -1097,7 +1214,10 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1097
1214
  /**
1098
1215
  * 清空下单客户(协议字段 + snapshot)。
1099
1216
  */
1100
- clearOrderCustomer() {
1217
+ clearOrderCustomer(options) {
1218
+ this.orderCustomerContactRequest = null;
1219
+ if (!options?.preserveContactInfo) this.resetOrderCustomerContactInfo();
1220
+ if (!options?.preserveContactInfo) this.store.order.updateTempOrderContactsInfo(null);
1101
1221
  this.store.order.clearOrderCustomer();
1102
1222
  this.discountConfigCacheKey = null;
1103
1223
  this.hasManualDiscountSelection = false;
@@ -1667,7 +1787,10 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1667
1787
  * 从已加车的普通商品 order line 反查 SkuDetailModal edit 所需的 cacheItem。
1668
1788
  */
1669
1789
  buildNormalProductCacheItemFromOrderLine(input) {
1670
- return (0, _buildNormalProductCacheItemFromOrderLine.buildNormalProductCacheItemFromOrderLine)(input);
1790
+ return (0, _buildNormalProductCacheItemFromOrderLine.buildNormalProductCacheItemFromOrderLine)({
1791
+ ...input,
1792
+ holder_relations: input.holder_relations ?? this.store.order?.getTempOrder()?.holder_relations
1793
+ });
1671
1794
  }
1672
1795
 
1673
1796
  /** 文档别名:与 OrderModule.updateOrderProduct 一致。 */
@@ -1900,6 +2023,11 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1900
2023
  async setOtherParams(params, {
1901
2024
  cover = false
1902
2025
  } = {}) {
2026
+ const nextParams = cover ? params : {
2027
+ ...this.otherParams,
2028
+ ...params
2029
+ };
2030
+ if (['businessCode', 'business_code', 'channel', 'platform', 'shop_id', 'tenantId', 'cacheId'].some(key => nextParams?.[key] !== this.otherParams?.[key])) this.orderCustomerContactRequest = null;
1903
2031
  await super.setOtherParams(params, {
1904
2032
  cover
1905
2033
  });
@@ -2015,6 +2143,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
2015
2143
  * 再清理 BookingTicket 自有资源(scan 监听 + scan 内存缓存)。
2016
2144
  */
2017
2145
  async destroy() {
2146
+ this.orderCustomerContactRequest = null;
2018
2147
  await super.destroy();
2019
2148
  try {
2020
2149
  this.scan?.removeAllListeners();
@@ -1877,7 +1877,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
1877
1877
  }
1878
1878
  this.store.customer?.setSelectedCustomer?.(authenticatedCustomer);
1879
1879
  this.setOrderCustomer(authenticatedCustomer);
1880
- await Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh()]);
1880
+ await Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh(), this.waitForOrderCustomerContactInfo()]);
1881
1881
  return true;
1882
1882
  }
1883
1883
  isAuthenticatedCustomerUserPlatform() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "2.3.164",
4
+ "version": "2.3.166",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",