@pisell/pisellos 0.10.65 → 0.10.66

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.
@@ -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 | 2 | 1 | 6 | 4 | 5 | 3;
330
330
  }[]>;
331
331
  submitTimeSlot(timeSlots: TimeSliceItem): void;
332
332
  private getScheduleDataByIds;
@@ -58,6 +58,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
58
58
  private getIdGeneratorPlugin;
59
59
  protected loadOpenDataConfig(): Promise<OpenDataConfig | null>;
60
60
  getOpenData(): Promise<OpenDataConfig | null>;
61
+ protected loadContactInfoOpenData(): Promise<OpenDataConfig | null>;
61
62
  getInvoiceLayoutConfig(): Promise<InvoiceLayoutConfig>;
62
63
  getInvoiceTemplateSettings(): Promise<InvoiceTemplateSettings>;
63
64
  protected isInvoiceOpenDataCacheCurrent(): boolean;
@@ -286,7 +287,9 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
286
287
  /**
287
288
  * 清空下单客户(协议字段 + snapshot)。
288
289
  */
289
- clearOrderCustomer(): void;
290
+ clearOrderCustomer(options?: {
291
+ preserveContactInfo?: boolean;
292
+ }): void;
290
293
  /**
291
294
  * 重置 tempOrder,并同步清空下单客户运行态。
292
295
  *
@@ -465,19 +468,7 @@ export declare class BookingTicketImpl extends BaseSalesImpl implements Module {
465
468
  * 加车两阶段主决策(规格弹窗 / 资源编辑 / 直接加车)。
466
469
  * 与 ticketBooking `handleSelectProduct` + `handleBooking4Service` 等价。
467
470
  */
468
- decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): {
469
- action: "reloadCatalog";
470
- } | {
471
- action: "add";
472
- cacheItem: any;
473
- } | {
474
- action: "requiresDetail";
475
- payload: AddProductRequiresDetailPayload;
476
- } | {
477
- action: "requiresBookingEdit";
478
- cacheItem: any;
479
- payload: import("./utils/addProductDecision").AddProductRequiresBookingEditPayload;
480
- };
471
+ decideAddProduct(item: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
481
472
  /** 规格弹窗 callback 后的第二段决策。 */
482
473
  decideAfterDetail(cacheItem: any, options?: Partial<AddProductDecideContext>): import("./utils/addProductDecision").AddProductDecision;
483
474
  /**
@@ -39,6 +39,8 @@ 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 _lodashEs = require("lodash-es");
42
44
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
43
45
  const OPEN_DATA_SECTION_CODES = ['sale', 'reservation', 'fulfillment', 'menu', 'workflow', 'documents'];
44
46
  const OPEN_DATA_CACHE_TTL = 5 * 60 * 1000;
@@ -289,6 +291,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
289
291
  async getOpenData() {
290
292
  return this.loadOpenDataConfig();
291
293
  }
294
+ async loadContactInfoOpenData() {
295
+ return this.store.openData ? this.getOpenData() : super.loadContactInfoOpenData();
296
+ }
292
297
  async getInvoiceLayoutConfig() {
293
298
  const openData = await this.loadOpenDataConfig();
294
299
  if (!openData) return (0, _OpenData.getDefaultInvoiceLayoutConfig)();
@@ -1009,7 +1014,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1009
1014
  */
1010
1015
  setOrderCustomer(customer, options) {
1011
1016
  if (!customer) {
1012
- this.clearOrderCustomer();
1017
+ this.clearOrderCustomer({
1018
+ preserveContactInfo: options?.source === 'detailHydrate'
1019
+ });
1013
1020
  return;
1014
1021
  }
1015
1022
  const previousCustomerId = this.store.order.getOrderCustomer()?.customer_id;
@@ -1020,6 +1027,9 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1020
1027
  this.discountConfigCacheKey = null;
1021
1028
  this.hasManualDiscountSelection = false;
1022
1029
  }
1030
+ if (!customerUnchanged && options?.source !== 'detailHydrate') {
1031
+ this.store.order.updateTempOrderContactsInfo((0, _lodashEs.cloneDeep)((0, _contactInfo.extractCustomerContactInfo)(customer)));
1032
+ }
1023
1033
  this.store.order.setOrderCustomer(patch, customer);
1024
1034
  const snapshotAfterOrderSet = this.store.order.getOrderCustomerSnapshot();
1025
1035
  if (customerUnchanged) {
@@ -1071,7 +1081,8 @@ class BookingTicketImpl extends _BaseSales.BaseSalesImpl {
1071
1081
  /**
1072
1082
  * 清空下单客户(协议字段 + snapshot)。
1073
1083
  */
1074
- clearOrderCustomer() {
1084
+ clearOrderCustomer(options) {
1085
+ if (!options?.preserveContactInfo) this.store.order.updateTempOrderContactsInfo(null);
1075
1086
  this.store.order.clearOrderCustomer();
1076
1087
  this.discountConfigCacheKey = null;
1077
1088
  this.hasManualDiscountSelection = false;
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.66",
5
5
  "description": "一个可扩展的前端模块化SDK框架,支持插件系统",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",