@pisell/pisellos 0.10.65 → 0.10.67

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.
@@ -2152,7 +2152,7 @@ export var UnifiedBookingSalesImpl = /*#__PURE__*/function (_BookingTicket) {
2152
2152
  (_this$store$customer = this.store.customer) === null || _this$store$customer === void 0 || (_this$store$customer$ = _this$store$customer.setSelectedCustomer) === null || _this$store$customer$ === void 0 || _this$store$customer$.call(_this$store$customer, authenticatedCustomer);
2153
2153
  this.setOrderCustomer(authenticatedCustomer);
2154
2154
  _context8.next = 15;
2155
- return Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh()]);
2155
+ return Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh(), this.waitForOrderCustomerContactInfo()]);
2156
2156
  case 15:
2157
2157
  return _context8.abrupt("return", true);
2158
2158
  case 16:
@@ -1 +1,46 @@
1
- "use strict";
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "BUY_X_GET_Y_FREE_STRATEGY", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _examples.BUY_X_GET_Y_FREE_STRATEGY;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "ITEM_REWARD_STRATEGY", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _examples.ITEM_REWARD_STRATEGY;
16
+ }
17
+ });
18
+ Object.defineProperty(exports, "PromotionAdapter", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _adapter.PromotionAdapter;
22
+ }
23
+ });
24
+ Object.defineProperty(exports, "PromotionEvaluator", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _evaluator.PromotionEvaluator;
28
+ }
29
+ });
30
+ Object.defineProperty(exports, "X_ITEMS_FOR_Y_PRICE_STRATEGY", {
31
+ enumerable: true,
32
+ get: function () {
33
+ return _examples.X_ITEMS_FOR_Y_PRICE_STRATEGY;
34
+ }
35
+ });
36
+ Object.defineProperty(exports, "default", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _adapter.default;
40
+ }
41
+ });
42
+ var _evaluator = require("./evaluator");
43
+ var _adapter = _interopRequireWildcard(require("./adapter"));
44
+ var _examples = require("./examples");
45
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
46
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
@@ -0,0 +1,20 @@
1
+ import type { OpenDataConfig } from './types';
2
+ export declare const CONTACT_INFO_FIELDS: readonly ["first_name", "last_name", "phone", "email"];
3
+ export type ContactInfoField = typeof CONTACT_INFO_FIELDS[number];
4
+ export interface ContactInfoConfig {
5
+ enabled: boolean;
6
+ fields: Record<ContactInfoField, {
7
+ visible: boolean;
8
+ required: boolean;
9
+ }>;
10
+ }
11
+ export declare function resolveContactInfoConfig(data?: OpenDataConfig | null): ContactInfoConfig;
12
+ export declare function getMissingContactInfoFields(config: ContactInfoConfig, value: unknown): ContactInfoField[];
13
+ export declare function isContactRecord(value: unknown): value is Record<string, any>;
14
+ /** Customer relations wrap the contact in value; sales orders store the object itself. */
15
+ export declare function extractCustomerContactInfo(customer: unknown): Record<string, any> | null;
16
+ export declare class ContactInfoRequiredError extends Error {
17
+ readonly fields: ContactInfoField[];
18
+ readonly code = "CONTACT_INFO_REQUIRED";
19
+ constructor(fields: ContactInfoField[]);
20
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ContactInfoRequiredError = exports.CONTACT_INFO_FIELDS = void 0;
7
+ exports.extractCustomerContactInfo = extractCustomerContactInfo;
8
+ exports.getMissingContactInfoFields = getMissingContactInfoFields;
9
+ exports.isContactRecord = isContactRecord;
10
+ exports.resolveContactInfoConfig = resolveContactInfoConfig;
11
+ const CONTACT_INFO_FIELDS = exports.CONTACT_INFO_FIELDS = ['first_name', 'last_name', 'phone', 'email'];
12
+ function resolveContactInfoConfig(data) {
13
+ const enabled = data?.['sale.enable_contact_info'] === true;
14
+ const fields = CONTACT_INFO_FIELDS.reduce((result, field) => {
15
+ const visible = data?.[`sale.${field}.visible`] !== false;
16
+ result[field] = {
17
+ visible,
18
+ required: enabled && visible && data?.[`sale.${field}.required`] === true
19
+ };
20
+ return result;
21
+ }, {});
22
+ return {
23
+ enabled: enabled && CONTACT_INFO_FIELDS.some(field => fields[field].visible),
24
+ fields
25
+ };
26
+ }
27
+ function getMissingContactInfoFields(config, value) {
28
+ if (!config.enabled) return [];
29
+ const contact = isContactRecord(value) ? value : {};
30
+ return CONTACT_INFO_FIELDS.filter(field => {
31
+ if (!config.fields[field].visible || !config.fields[field].required) return false;
32
+ const fieldValue = field === 'phone' ? isContactRecord(contact.phone) ? contact.phone.phone : undefined : contact[field];
33
+ return typeof fieldValue !== 'string' || !fieldValue.trim();
34
+ });
35
+ }
36
+ function isContactRecord(value) {
37
+ return !!value && typeof value === 'object' && !Array.isArray(value);
38
+ }
39
+
40
+ /** Customer relations wrap the contact in value; sales orders store the object itself. */
41
+ function extractCustomerContactInfo(customer) {
42
+ if (!isContactRecord(customer)) return null;
43
+ const contact = customer.contacts_info;
44
+ if (!isContactRecord(contact)) return null;
45
+ const value = Object.prototype.hasOwnProperty.call(contact, 'value') ? contact.value : contact;
46
+ return isContactRecord(value) ? value : null;
47
+ }
48
+ class ContactInfoRequiredError extends Error {
49
+ code = 'CONTACT_INFO_REQUIRED';
50
+ constructor(fields) {
51
+ super(`Contact information is required: ${fields.join(', ')}`);
52
+ this.fields = fields;
53
+ this.name = 'ContactInfoRequiredError';
54
+ }
55
+ }
56
+ exports.ContactInfoRequiredError = ContactInfoRequiredError;
@@ -3,6 +3,7 @@ import { BaseModule } from '../BaseModule';
3
3
  import type { ScheduleModule } from '../Schedule';
4
4
  import type { OpenDataConfig, OpenDataFetchParams, OpenDataAvailabilityResult, InvoiceLayoutConfig, InvoiceTemplateSettings } from './types';
5
5
  export * from './types';
6
+ export * from './contactInfo';
6
7
  export { computeAvailability, getDefaultInvoiceLayoutConfig, getDefaultInvoiceTemplateSettings, normalizeInvoiceLayoutConfig, normalizeInvoiceLayoutFromTemplateSettings, normalizeInvoiceTemplateSettings, normalizeRawInvoiceTemplateSettings, resolveInvoiceReceiptLocales, } from './utils';
7
8
  export declare class OpenDataModule extends BaseModule implements Module {
8
9
  protected defaultName: string;
@@ -77,6 +77,18 @@ Object.keys(_types).forEach(function (key) {
77
77
  }
78
78
  });
79
79
  });
80
+ var _contactInfo = require("./contactInfo");
81
+ Object.keys(_contactInfo).forEach(function (key) {
82
+ if (key === "default" || key === "__esModule") return;
83
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
84
+ if (key in exports && exports[key] === _contactInfo[key]) return;
85
+ Object.defineProperty(exports, key, {
86
+ enumerable: true,
87
+ get: function () {
88
+ return _contactInfo[key];
89
+ }
90
+ });
91
+ });
80
92
  function isRecord(value) {
81
93
  return !!value && typeof value === 'object' && !Array.isArray(value);
82
94
  }
@@ -69,6 +69,15 @@ export interface OpenDataConfig {
69
69
  'reservation.time_slice'?: number | string;
70
70
  'sale.allow_item_notes'?: boolean;
71
71
  'sale.allow_order_notes'?: boolean;
72
+ 'sale.enable_contact_info'?: boolean;
73
+ 'sale.first_name.visible'?: boolean;
74
+ 'sale.first_name.required'?: boolean;
75
+ 'sale.last_name.visible'?: boolean;
76
+ 'sale.last_name.required'?: boolean;
77
+ 'sale.phone.visible'?: boolean;
78
+ 'sale.phone.required'?: boolean;
79
+ 'sale.email.visible'?: boolean;
80
+ 'sale.email.required'?: boolean;
72
81
  /** False submits an order without starting or confirming payment. */
73
82
  'sale.require_payment_on_submit'?: boolean | null;
74
83
  'sale.enable_item_rules'?: boolean;
@@ -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';
@@ -19,6 +20,7 @@ import { QuotationModule } from '../../modules/Quotation';
19
20
  import type { ISalesDetail, ISalesDetailHeader, ISalesBooking, ISalesPayment, ISalesSurcharge } from './utils/parseSalesResponse';
20
21
  import { AppPlugin } from '../../plugins/app';
21
22
  import { type TransformBaseProductToOrderProductParams } from './utils/transformBaseProductToOrderProduct';
23
+ import type { OpenDataConfig } from '../../modules/OpenData/types';
22
24
  export interface BaseSalesState {
23
25
  order?: OrderModule;
24
26
  products?: ProductList;
@@ -283,6 +285,9 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
283
285
  setPickupTimingBooking(input: PickupTimingBookingInput): import("../../modules/Order").PickupTimingBooking;
284
286
  clearPickupTimingBooking(): void;
285
287
  updateTempOrderContactsInfo(contactsInfo: Record<string, any> | null): Record<string, any> | null;
288
+ protected loadContactInfoOpenData(): Promise<OpenDataConfig | null>;
289
+ getContactInfoConfig(): Promise<ContactInfoConfig>;
290
+ validateContactInfo(): Promise<void>;
286
291
  /** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化。 */
287
292
  setEnableTempOrderPersist(enabled: boolean): void;
288
293
  /** 是否允许 saveDraft 写入 IndexedDB 草稿。 */
@@ -359,6 +364,8 @@ export declare class BaseSalesImpl extends BaseModule implements Module {
359
364
  * payments 会交给 OrderModule 统一映射为 Sales 协议支付项。
360
365
  */
361
366
  submitSalesOrder<T = any>(params?: BaseSalesSubmitSalesOrderParams): Promise<T>;
367
+ /** Validate required contact fields before a new payment is initiated. */
368
+ ensureSalesOrderReadyForPayment(): Promise<void>;
362
369
  saveSalesOrderDraft<T = any>(params?: {
363
370
  platform?: string;
364
371
  businessCode?: string;
@@ -25,6 +25,7 @@ var _Order = require("../../modules/Order");
25
25
  var _pickupTiming = require("../../modules/Order/pickupTiming");
26
26
  var _pickupTiming2 = require("./pickupTiming");
27
27
  var _holderRelations = require("../../modules/Order/holderRelations");
28
+ var _contactInfo = require("../../modules/OpenData/contactInfo");
28
29
  var _decimal = _interopRequireDefault(require("decimal.js"));
29
30
  var _lodashEs = require("lodash-es");
30
31
  var _types2 = require("../BookingByStep/types");
@@ -1906,6 +1907,27 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
1906
1907
  if (!this.store.order) throw new Error('order 模块未初始化');
1907
1908
  return this.store.order.updateTempOrderContactsInfo(contactsInfo);
1908
1909
  }
1910
+ async loadContactInfoOpenData() {
1911
+ return this.otherParams?.openData ?? null;
1912
+ }
1913
+ async getContactInfoConfig() {
1914
+ const scope = this.getPickupTimingScope();
1915
+ const data = await this.loadContactInfoOpenData();
1916
+ if (scope !== this.getPickupTimingScope()) throw new Error('Store settings changed. Please try again.');
1917
+ return (0, _contactInfo.resolveContactInfoConfig)(data);
1918
+ }
1919
+ async validateContactInfo() {
1920
+ const order = this.getTempOrder();
1921
+ const identity = order?._extend?.localId || order?.external_sale_number || order?.order_id || order;
1922
+ const customerId = order?.customer_id;
1923
+ const config = await this.getContactInfoConfig();
1924
+ const current = this.getTempOrder();
1925
+ if (identity !== (current?._extend?.localId || current?.external_sale_number || current?.order_id || current) || customerId !== current?.customer_id) {
1926
+ throw new Error('The order changed. Please try again.');
1927
+ }
1928
+ const missing = (0, _contactInfo.getMissingContactInfoFields)(config, current?.contacts_info);
1929
+ if (missing.length) throw new _contactInfo.ContactInfoRequiredError(missing);
1930
+ }
1909
1931
 
1910
1932
  /** @deprecated OrderModule 不再负责 tempOrder localStorage 持久化。 */
1911
1933
  setEnableTempOrderPersist(enabled) {
@@ -2706,6 +2728,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2706
2728
  if (!this.store.order) {
2707
2729
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2708
2730
  }
2731
+ await this.validateContactInfo();
2709
2732
  if (this.isPickupTimingEnabled()) await this.preparePickupTimingForCheckout({
2710
2733
  freeze: true
2711
2734
  });
@@ -2779,6 +2802,11 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2779
2802
  };
2780
2803
  return this.shouldUseCheckoutSyncTask() ? this.store.order.submitTempOrder(submitParams) : this.store.order.submitTempOrderAsync(submitParams);
2781
2804
  }
2805
+
2806
+ /** Validate required contact fields before a new payment is initiated. */
2807
+ async ensureSalesOrderReadyForPayment() {
2808
+ await this.validateContactInfo();
2809
+ }
2782
2810
  async saveSalesOrderDraft(params) {
2783
2811
  if (!this.store.order) {
2784
2812
  throw new Error('BaseSales 解决方案需要 order 模块支持');
@@ -2801,6 +2829,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
2801
2829
  if (!this.store.order) {
2802
2830
  throw new Error('BaseSales 解决方案需要 order 模块支持');
2803
2831
  }
2832
+ await this.validateContactInfo();
2804
2833
  if (this.isPickupTimingEnabled()) {
2805
2834
  await this.preparePickupTimingForCheckout({
2806
2835
  freeze: true
@@ -3637,6 +3666,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3637
3666
  */
3638
3667
  async confirmWalletPayment() {
3639
3668
  if (!this.store.order) throw new Error('order 模块未初始化');
3669
+ await this.validateContactInfo();
3640
3670
  const beforePayments = (0, _lodashEs.cloneDeep)(this.store.order.getOrderPayments());
3641
3671
  const pendingWalletAmount = this.getPendingWalletPaymentAmount(beforePayments);
3642
3672
  const amountSnapshot = this.store.order.getOrderAmountSnapshot?.();
@@ -3708,6 +3738,7 @@ class BaseSalesImpl extends _BaseModule.BaseModule {
3708
3738
  */
3709
3739
  async payCash(params) {
3710
3740
  if (!this.store.order) throw new Error('order 模块未初始化');
3741
+ await this.validateContactInfo();
3711
3742
  const amount = Number(params.amount || 0);
3712
3743
  if (!Number.isFinite(amount) || amount <= 0) {
3713
3744
  throw new Error('现金支付金额必须大于 0');
@@ -326,7 +326,7 @@ export declare class BookingByStepImpl extends BaseModule implements Module {
326
326
  date: string;
327
327
  status: string;
328
328
  week: string;
329
- weekNum: 0 | 2 | 1 | 3 | 4 | 5 | 6;
329
+ weekNum: 0 | 6 | 5 | 1 | 2 | 3 | 4;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  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;
@@ -58,6 +59,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
58
59
  private getIdGeneratorPlugin;
59
60
  protected loadOpenDataConfig(): Promise<OpenDataConfig | null>;
60
61
  getOpenData(): Promise<OpenDataConfig | null>;
62
+ protected loadContactInfoOpenData(): Promise<OpenDataConfig | null>;
61
63
  getInvoiceLayoutConfig(): Promise<InvoiceLayoutConfig>;
62
64
  getInvoiceTemplateSettings(): Promise<InvoiceTemplateSettings>;
63
65
  protected isInvoiceOpenDataCacheCurrent(): boolean;
@@ -275,6 +277,16 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
275
277
  setOrderCustomer(customer: ICustomer | null, options?: {
276
278
  source?: OrderCustomerChangePayload['source'];
277
279
  }): void;
280
+ private resetOrderCustomerContactInfo;
281
+ /** Explicit edits, including clearing the contact, must win over a pending default. */
282
+ updateTempOrderContactsInfo(contact: Record<string, any> | null): Record<string, any> | null;
283
+ private getCustomerContactScope;
284
+ private getCustomerContactOrderKey;
285
+ replaceTempOrder(...args: Parameters<BaseSalesImpl['replaceTempOrder']>): Promise<import("../../modules/Order/types").OrderTempOrder>;
286
+ private applyDefaultOrderCustomerContactInfo;
287
+ private prefillOrderCustomerContactInfo;
288
+ protected waitForOrderCustomerContactInfo(): Promise<void>;
289
+ validateContactInfo(): Promise<void>;
278
290
  /**
279
291
  * 读取 tempOrder 上的下单客户协议字段;customer_id 缺失时返回 null。
280
292
  */
@@ -286,7 +298,9 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
286
298
  /**
287
299
  * 清空下单客户(协议字段 + snapshot)。
288
300
  */
289
- clearOrderCustomer(): void;
301
+ clearOrderCustomer(options?: {
302
+ preserveContactInfo?: boolean;
303
+ }): void;
290
304
  /**
291
305
  * 重置 tempOrder,并同步清空下单客户运行态。
292
306
  *
@@ -346,7 +360,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
346
360
  * 获取当前的客户搜索条件
347
361
  * @returns 当前搜索条件
348
362
  */
349
- getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "num" | "skip">;
363
+ getCurrentCustomerSearchParams(): Omit<import("../../modules").ShopGetCustomerListParams, "skip" | "num">;
350
364
  /**
351
365
  * 获取客户列表状态(包含滚动加载相关状态)
352
366
  * @returns 客户状态
@@ -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
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
43
46
  const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'documents'];
44
47
  const OPEN_DATA_CACHE_TTL = 5 * 60 * 1000;
@@ -162,6 +165,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
162
165
  orderCustomerDiscountRefreshCustomerId = null;
163
166
  orderCustomerDiscountRefreshTarget = null;
164
167
  orderCustomerPromotionRefreshInFlight = null;
168
+ orderCustomerContactRequest = null;
165
169
  customerResolveRequests = new Map();
166
170
  loadOpenDataInFlight = null;
167
171
  loadOpenDataInFlightTarget = null;
@@ -289,6 +293,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
289
293
  async getOpenData() {
290
294
  return this.loadOpenDataConfig();
291
295
  }
296
+ async loadContactInfoOpenData() {
297
+ return this.store.openData ? this.getOpenData() : super.loadContactInfoOpenData();
298
+ }
292
299
  async getInvoiceLayoutConfig() {
293
300
  const openData = await this.loadOpenDataConfig();
294
301
  if (!openData) return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
@@ -1009,7 +1016,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1009
1016
  */
1010
1017
  setOrderCustomer(customer, options) {
1011
1018
  if (!customer) {
1012
- this.clearOrderCustomer();
1019
+ this.clearOrderCustomer({
1020
+ preserveContactInfo: options?.source === 'detailHydrate'
1021
+ });
1013
1022
  return;
1014
1023
  }
1015
1024
  const previousCustomerId = this.store.order.getOrderCustomer()?.customer_id;
@@ -1020,7 +1029,16 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1020
1029
  this.discountConfigCacheKey = null;
1021
1030
  this.hasManualDiscountSelection = false;
1022
1031
  }
1032
+ if (!customerUnchanged && options?.source !== 'detailHydrate') {
1033
+ this.resetOrderCustomerContactInfo();
1034
+ this.store.order.updateTempOrderContactsInfo((0, _lodashEs.cloneDeep)((0, _contactInfo.extractCustomerContactInfo)(customer)));
1035
+ }
1023
1036
  this.store.order.setOrderCustomer(patch, customer);
1037
+ if (options?.source === 'detailHydrate') {
1038
+ this.orderCustomerContactRequest = null;
1039
+ } else {
1040
+ this.prefillOrderCustomerContactInfo(customer, !customerUnchanged);
1041
+ }
1024
1042
  const snapshotAfterOrderSet = this.store.order.getOrderCustomerSnapshot();
1025
1043
  if (customerUnchanged) {
1026
1044
  // hydrate 后 customer_id 已存在但 _extend.customerSnapshot 仍为空时,
@@ -1053,6 +1071,105 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1053
1071
  this.orderCustomerPromotionRefreshInFlight = null;
1054
1072
  });
1055
1073
  }
1074
+ resetOrderCustomerContactInfo() {
1075
+ this.orderCustomerContactRequest = null;
1076
+ const order = this.getTempOrder();
1077
+ if (order?._extend) delete order._extend.contactInfoEdited;
1078
+ }
1079
+
1080
+ /** Explicit edits, including clearing the contact, must win over a pending default. */
1081
+ updateTempOrderContactsInfo(contact) {
1082
+ const order = this.store.order.ensureTempOrder();
1083
+ order._extend || (order._extend = {});
1084
+ order._extend.contactInfoEdited = true;
1085
+ this.orderCustomerContactRequest = null;
1086
+ return super.updateTempOrderContactsInfo(contact);
1087
+ }
1088
+ getCustomerContactScope() {
1089
+ return JSON.stringify([this.getPickupTimingScope(), this.otherParams?.channel, this.otherParams?.shop_id, this.otherParams?.tenantId]);
1090
+ }
1091
+ getCustomerContactOrderKey() {
1092
+ const order = this.getTempOrder();
1093
+ // Cart mutations can replace the object without changing the order.
1094
+ const identity = order?.external_sale_number || order?.request_unique_idempotency_token || order?._extend?.localId || order?.order_id;
1095
+ return identity ? JSON.stringify([identity, order?.order_id]) : order;
1096
+ }
1097
+ async replaceTempOrder(...args) {
1098
+ this.orderCustomerContactRequest = null;
1099
+ return super.replaceTempOrder(...args);
1100
+ }
1101
+ async applyDefaultOrderCustomerContactInfo(contact) {
1102
+ this.store.order.updateTempOrderContactsInfo((0, _lodashEs.cloneDeep)(contact));
1103
+ // Metadata may finish after promotion/discount refresh; publish the updated cart explicitly.
1104
+ await this.effectsEmit('onTempOrderReplaced', {
1105
+ tempOrder: this.getTempOrder()
1106
+ });
1107
+ }
1108
+ prefillOrderCustomerContactInfo(customer, customerChanged) {
1109
+ const order = this.getTempOrder();
1110
+ const customerId = Number(order?.customer_id);
1111
+ if (!order || !Number.isInteger(customerId) || customerId <= 1 || order.contacts_info != null || order._extend?.contactInfoEdited) return;
1112
+ // A restored new cart may already have its customer ID. Existing sales keep their snapshot,
1113
+ // even when it is empty; only an explicit customer switch replaces it.
1114
+ if (!customerChanged && order.order_id && !String(order.order_id).startsWith('local_order')) return;
1115
+ const contact = (0, _contactInfo.extractCustomerContactInfo)(customer);
1116
+ if (contact) {
1117
+ this.orderCustomerContactRequest = null;
1118
+ void this.applyDefaultOrderCustomerContactInfo(contact);
1119
+ return;
1120
+ }
1121
+ // A relation explicitly returned as null is authoritative. Only a missing relation needs fetching.
1122
+ if (customer.contacts_info !== undefined) return;
1123
+ const userPlatform = (0, _platform.isCustomerUserPlatform)(this.otherParams?.platform);
1124
+ const getAuthenticatedCustomerId = () => Number(this.core?.getPlugin?.('user')?.get?.()?.id);
1125
+ if (userPlatform && getAuthenticatedCustomerId() !== customerId) return;
1126
+ const scope = this.getCustomerContactScope();
1127
+ const orderKey = this.getCustomerContactOrderKey();
1128
+ const previous = this.orderCustomerContactRequest;
1129
+ if (previous?.orderKey === orderKey && previous.customerId === customerId && previous.scope === scope) return;
1130
+ const pending = {
1131
+ orderKey,
1132
+ customerId,
1133
+ scope,
1134
+ promise: Promise.resolve()
1135
+ };
1136
+ this.orderCustomerContactRequest = pending;
1137
+ const loadContact = async () => {
1138
+ if (userPlatform) {
1139
+ // Same endpoint as BookingByStep.getContactInfo; the PC/H5 host adds /h5 and login headers.
1140
+ const result = await this.request.get('/customer/metadata', {
1141
+ key: 'contacts_info'
1142
+ });
1143
+ if (result?.code !== 200 || result?.status === false) throw new Error('Customer contact lookup failed');
1144
+ return (0, _contactInfo.isContactRecord)(result.data) ? result.data : null;
1145
+ }
1146
+ // POS and physical Kiosk share the /shop request plugin and current-shop customer resolver.
1147
+ const resolved = await this.resolveCustomerById(customerId);
1148
+ return Number(resolved?.id) === customerId ? (0, _contactInfo.extractCustomerContactInfo)(resolved) : null;
1149
+ };
1150
+ pending.promise = loadContact().then(async value => {
1151
+ const current = this.getTempOrder();
1152
+ 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;
1153
+ if (value) await this.applyDefaultOrderCustomerContactInfo(value);
1154
+ }).catch(() => {
1155
+ // A failed default does not prevent manual entry; a subsequent sync can retry.
1156
+ if (this.orderCustomerContactRequest === pending) this.orderCustomerContactRequest = null;
1157
+ console.warn('[BookingTicket] Failed to load saved customer contact information');
1158
+ });
1159
+ }
1160
+ waitForOrderCustomerContactInfo() {
1161
+ return this.orderCustomerContactRequest?.promise ?? Promise.resolve();
1162
+ }
1163
+ async validateContactInfo() {
1164
+ const orderKey = this.getCustomerContactOrderKey();
1165
+ const customerId = this.getTempOrder()?.customer_id;
1166
+ const scope = this.getCustomerContactScope();
1167
+ await this.waitForOrderCustomerContactInfo();
1168
+ if (this.getCustomerContactOrderKey() !== orderKey || this.getTempOrder()?.customer_id !== customerId || this.getCustomerContactScope() !== scope) {
1169
+ throw new Error('The order changed. Please try again.');
1170
+ }
1171
+ await super.validateContactInfo();
1172
+ }
1056
1173
 
1057
1174
  /**
1058
1175
  * 读取 tempOrder 上的下单客户协议字段;customer_id 缺失时返回 null。
@@ -1071,7 +1188,10 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1071
1188
  /**
1072
1189
  * 清空下单客户(协议字段 + snapshot)。
1073
1190
  */
1074
- clearOrderCustomer() {
1191
+ clearOrderCustomer(options) {
1192
+ this.orderCustomerContactRequest = null;
1193
+ if (!options?.preserveContactInfo) this.resetOrderCustomerContactInfo();
1194
+ if (!options?.preserveContactInfo) this.store.order.updateTempOrderContactsInfo(null);
1075
1195
  this.store.order.clearOrderCustomer();
1076
1196
  this.discountConfigCacheKey = null;
1077
1197
  this.hasManualDiscountSelection = false;
@@ -1891,6 +2011,11 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1891
2011
  async setOtherParams(params, {
1892
2012
  cover = false
1893
2013
  } = {}) {
2014
+ const nextParams = cover ? params : {
2015
+ ...this.otherParams,
2016
+ ...params
2017
+ };
2018
+ if (['businessCode', 'business_code', 'channel', 'platform', 'shop_id', 'tenantId', 'cacheId'].some(key => nextParams?.[key] !== this.otherParams?.[key])) this.orderCustomerContactRequest = null;
1894
2019
  await super.setOtherParams(params, {
1895
2020
  cover
1896
2021
  });
@@ -2006,6 +2131,7 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
2006
2131
  * 再清理 BookingTicket 自有资源(scan 监听 + scan 内存缓存)。
2007
2132
  */
2008
2133
  async destroy() {
2134
+ this.orderCustomerContactRequest = null;
2009
2135
  await super.destroy();
2010
2136
  try {
2011
2137
  this.scan?.removeAllListeners();
@@ -1718,7 +1718,7 @@ class UnifiedBookingSalesImpl extends _BookingTicket.BookingTicket {
1718
1718
  }
1719
1719
  this.store.customer?.setSelectedCustomer?.(authenticatedCustomer);
1720
1720
  this.setOrderCustomer(authenticatedCustomer);
1721
- await Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh()]);
1721
+ await Promise.all([this.refreshDiscountConfigAfterOrderCustomerChange(authenticatedCustomerId), this.waitForOrderCustomerPromotionRefresh(), this.waitForOrderCustomerContactInfo()]);
1722
1722
  return true;
1723
1723
  }
1724
1724
  isAuthenticatedCustomerUserPlatform() {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "@pisell/pisellos",
4
- "version": "0.10.65",
4
+ "version": "0.10.67",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",