/** * Sales SDK 公开类型契约。 * * 该文件不依赖 `@pisell/pisellos` 类型(运行时通过 `appHelper.utils.pisellos` 注入), * 因此对底层 OS 类型采用结构性 alias:仅描述 SDK 直接消费/暴露的字段, * 其它字段以 `Record` 透传。所有 alias 命名严格沿用 OS 协议(`parseSalesResponse` / * `ScanOrder` types),避免重新发明等价关系(遵循 .cursor/rules/data-field-semantics.mdc)。 * * SDK 自有的 Provider props / Context value / 视图类型均带 `SalesSdk` 前缀, * 以避免与 `components/Sales` 既有 UI 模块同名混淆。 */ import type React from 'react'; /** 商品行 identity(OS BaseSalesOrderProductIdentity / ScanOrderOrderProductIdentity 子集) */ export interface ScanOrderOrderProductIdentity { product_id: number | null; product_variant_id: number; /** @deprecated 行级唯一值统一使用 metadata.unique_identification_number */ identity_key?: string; unique_identification_number?: string; product_option_item?: any[]; product_bundle?: any[]; } /** 商品行(OS ScanOrderOrderProduct 结构性子集) */ export interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity { order_detail_id: number | null; num: number; product_option_item: any[]; selling_price: string; original_price: string; tax_fee: string; is_charge_tax: number; discount_list: any[]; product_bundle: any[]; metadata: Record; note?: string; } /** OS Order/SalesSummary 汇总结构 */ export interface ScanOrderSummary { product_quantity: number; product_original_amount: string; product_amount: string; product_expect_amount: string; product_tax_fee: string; shipping_fee: string; shipping_tax_fee: string; tax_fee: string; surcharge_fee: string; discount_amount: string; deposit_amount: string; expect_amount: string; total_amount: string; amount_gap: string; rounding_amount: string; pay_service_charge_amount: string; tax_title?: string; tax_rate?: number; [key: string]: any; } /** tempOrder(OS ScanOrderTempOrder 结构性子集) */ export interface ScanOrderTempOrder { order_id: number | null; order_number: string; shop_order_number: string; shop_full_order_number?: string; type: string; business_code: string; platform: string; sales_channel: string; order_sales_channel: string; status: string; payment_status: string; shipping_status: string; customer_id: number | null; customer_name: string; country_calling_code: string; phone: string; email: string; is_price_include_tax: number; tax_title: string; tax_country_code: string; currency_code: string; currency_symbol: string; currency_format: string; is_deposit: number; deposit_amount: string; shop_discount: string; surcharge_fee: string; note: string; schedule_date: string; created_at: string; products: ScanOrderOrderProduct[]; bookings: any[]; payments: any[]; surcharges: any[]; discount_list: any[]; relation_forms: any[]; contacts: any[]; /** * 联系人信息:与 OS 真源(OrderModule)一致,缺失时为 `null`,有值时必须是对象。 * 不再使用空数组表示空,提交链路也按 object|null 处理(详见 OS TEMP_ORDER_FIELDS_SUMMARY.md)。 */ contacts_info: Record | null; holder: Record | null; metadata: Record; summary?: ScanOrderSummary; [key: string]: any; } /** 销售详情(来自 parseSalesResponse) */ export interface ISalesDetailHeader { order_id: number | string | null; order_number?: string; shop_order_number?: string; status?: string; payment_status?: string; shipping_status?: string; customer_id?: number | string | null; customer_name?: string; country_calling_code?: string; phone?: string; email?: string; metadata?: Record | null; total_amount?: string | number; [key: string]: any; } export interface ISalesBooking { schedule_event_id?: number | null; booking_id?: string; appointment_status?: string; status?: string; start_date?: string; start_time?: string; end_date?: string; end_time?: string; duration?: number; resources?: any[]; product?: any; detail?: any; parent_id?: number | null; product_uid?: string; metadata?: Record | null; relation_id?: number | string | null; [key: string]: any; } export interface ISalesPayment { amount: string; origin_amount?: string; rounding_amount?: string; status?: string; payment_time?: string | null; type?: string; code?: string; name?: string; [key: string]: any; } export interface ISalesSurcharge { amount?: string; type?: string; name?: Record; [key: string]: any; } export interface ISalesDetailCustomer { id: number | string | null; name: string; phone?: string; email?: string; country_calling_code?: string; [key: string]: any; } export interface ISalesDetail { header: ISalesDetailHeader; products: ScanOrderOrderProduct[]; bookings: ISalesBooking[]; rootBooking: ISalesBooking | null; bookingsByProductUid: Record; payments: ISalesPayment[]; surcharges: ISalesSurcharge[]; discount_list: any[]; customer: ISalesDetailCustomer | null; summary: Record; raw: Record; } /** OS CustomerModule 选中客户(结构性子集) */ export interface ICustomer { id: string | number; name?: string; phone?: string; email?: string; country_calling_code?: string; [key: string]: any; } /** * OS Order 模块协议字段视图(OrderCustomerView 结构性 alias)。 * 这些字段会随订单提交到后端;customer_id 与 OS OrderTempOrder 保持 number | null。 */ export interface OrderCustomerView { customer_id: number | null; customer_name: string; phone: string; email: string; country_calling_code: string; } /** * 写入下单客户的协议字段;customer_id 必填(string id 调用方需自行 Number() 转型), * 其余字段缺省视为「清空」。 */ export interface OrderCustomerPatch { customer_id: number | null; customer_name?: string; phone?: string; email?: string; country_calling_code?: string; } /** * `bookingTicket:onOrderCustomerChange` 事件 payload;清空时整体为 null。 */ export interface OrderCustomerChangePayload { patch: OrderCustomerView; snapshot: ICustomer | null; } export interface IPaginationInfo { page: number; pageSize: number; total: number; totalPages: number; } export interface ICustomerListResponse { list: ICustomer[]; total: number; page?: number; pageSize?: number; } export interface IGetCustomerListParams { skip?: number; num?: number; search?: string; [key: string]: any; } /** 商品列表条目(OS ProductData,UI 只用一小部分字段) */ export interface ProductData { id: number; title?: string; cover?: string; duration?: number; product_resource?: any; variants?: any[]; bundles?: any[]; options?: any[]; [key: string]: any; } /** OS BookingTicket 加载商品参数 */ export interface ILoadProductsParams { category_ids?: number[]; product_ids?: number[]; collection?: number | string[]; schedule_date?: string; customer_id?: number; menu_list_ids?: number[]; schedule_datetime?: string; with_count?: string[]; with_schedule?: number; } export interface ILoadProductDetailParams extends Omit { product_id: number; } export interface IScanResult { status: 'success' | 'fail'; scanType: string; scanCode: string; result: any; } /** Order.updateOrderProduct 入参 */ export interface UpdateOrderProductParams { product_id: number | null; product_variant_id: number; updates: Partial; unique_identification_number?: string; /** @deprecated use unique_identification_number / metadata.unique_identification_number */ identity_key?: string; product_option_item?: any[]; product_bundle?: any[]; /** 与商品行 1:1 关联的 booking;传则替换 tempOrder.bookings 中对应项(OS updateOrderProduct)。 */ booking?: Record; } /** Order.updateOrderProductQuantity 入参;行定位语义与 updateOrderProduct / removeProductFromOrder 一致 */ export interface UpdateOrderProductQuantityParams extends ScanOrderOrderProductIdentity { num: number; } /** @deprecated 使用 UpdateOrderProductParams */ export declare type UpdateProductInOrderParams = UpdateOrderProductParams; /** OS BaseSales.scanPromotionCode 返回 */ export interface BaseSalesScanCodeResult { isAvailable: boolean; type?: 'server' | string; unavailableReason?: string; } export interface BaseSalesQuotationDurationConfig { type: string; value: number; flexible_config?: { is_enable_minimum_duration?: 0 | 1; warning_threshold?: number; block_threshold?: number; }; } export interface BaseSalesCalculateProductBookingPriceParams { product: Record; booking?: { start_date?: string; start_time?: string; end_date?: string; end_time?: string; [key: string]: any; }; duration?: BaseSalesQuotationDurationConfig; customer_id?: number | string; fallback_price?: number | string; datetime?: string; channel?: string; } export interface BaseSalesProductBookingPriceSegment { start_datetime: string; end_datetime?: string; time_point: string; unit_price: string; quantity: number; total_price: string; quotation_shelf_id: number; source: 'quotation' | 'fallback'; } export interface BaseSalesBundleProductBookingPriceResult { bundle_id?: number | string; group_id?: number | string; bundle_group_id?: number | string; bundle_product_id: number | null; bundle_variant_id: number; unit_price: string; total_price: string; quantity: number; quotation_shelf_id: number; source: 'quotation' | 'fallback'; segments: BaseSalesProductBookingPriceSegment[]; } export interface BaseSalesProductBookingPriceResult { product_id: number | null; product_variant_id: number; customer_id?: number | string; unit_price: string; total_price: string; quantity: number; duration?: BaseSalesQuotationDurationConfig; quotation_shelf_id: number; segments: BaseSalesProductBookingPriceSegment[]; product_bundle?: BaseSalesBundleProductBookingPriceResult[]; } /** Order rules hooks(结构性,原生类型来自 OS RulesParamsHooks) */ export interface RulesParamsHooks { getProduct?: (...args: any[]) => any; setProduct?: (...args: any[]) => any; [key: string]: any; } /** * OS `LoadSalesDetailParams` 结构性 alias(字段命名与 Order/types 一致,勿自行发明映射)。 */ export interface SalesSdkLoadSalesDetailParams { orderId?: number | string; orderNumber?: string; externalSaleNumber?: string; external_sale_number?: string; forceRemote?: boolean; } /** * OS BaseSales.submitSalesOrder 入参(结构性 alias,与 BaseSalesImpl 一致)。 */ export interface SalesSdkSubmitSalesOrderParams { payments?: Record[]; paymentStatus?: string; smallTicketDataFlag?: number; enhancePayload?: (payload: Record, ctx: Record) => Record; } /** * BookingTicket OS 模块运行时句柄(极简结构性接口;运行时由 `pisellos.getModule(...)` 返回的实例提供)。 * * 对外(消费方)极少直接消费此引用,但 `useSalesSdk().bookingTicket` 仍提供给高级场景。 * 实际类型由 OS(@pisell/pisellos)提供,这里仅描述本 SDK 调用到的方法。 */ export interface BookingTicketHandle { name: string; store?: { customer?: any; order?: any; products?: any; [key: string]: any; }; effectsOn: (event: string, callback: (payload: any) => void) => () => void; loadSalesDetail: (orderIdOrParams: number | string | SalesSdkLoadSalesDetailParams) => Promise; refreshSalesDetail: () => Promise; /** * 更新当前预约的 appointment_status。 * 数据全部从 OrderModule.tempOrder 取(order_id / metadata / bookings), * schedule id 优先 `metadata.appointment_booking_parent_schedule_id`, * 否则使用 `parent_id === 0` 的 root booking 的 `schedule_event_id`。 * 失败时回滚本地 bookings 的 appointment_status 并抛出。 */ setBookingStatus: (status: string) => Promise; getSalesOrder: () => ISalesDetail | null; getOrderHeader: () => ISalesDetailHeader | null; getBookings: () => ISalesBooking[]; getRootBooking: () => ISalesBooking | null; getChildBookingsByProductUid: (productUid: string) => ISalesBooking[]; getPayments: () => ISalesPayment[]; getSurcharges: () => ISalesSurcharge[]; getTempOrder: () => ScanOrderTempOrder | null; getOrderProducts: () => ScanOrderOrderProduct[]; getDiscountList: () => any[]; getSummary: () => Promise; getCart?: () => SalesSdkCartView; getCustomer?: () => ICustomer | null; getProductCatalog?: () => SalesSdkProductCatalogView; addNewOrder: () => Promise; restoreOrder: () => Promise; /** * 运行时切换 tempOrder 是否写入 localStorage(OS BaseSales → OrderModule)。 * SalesSdk 默认 false;legacy ticketBooking 不传时 OS 侧仍为 true。 */ setEnableTempOrderPersist?: (enabled: boolean) => void; /** 当前是否启用 tempOrder localStorage 持久化。 */ isTempOrderPersistEnabled?: () => boolean; /** * 仅清空 tempOrder 的 products / bookings / discount_list(保留客户、备注等)。 * OS OrderModule.clearOrderCartLines;编辑态会同步清空内存 salesDetail 对应视图。 */ clearOrderCartLines: () => Promise; replaceTempOrder?: (tempOrder: ScanOrderTempOrder, options?: { recalculateSummary?: boolean; persist?: boolean; saveDraft?: boolean; }) => Promise; destroy: () => Promise; /** * 将商品 catalog / 详情选择回调转为 addProductToOrder 入参(OS BaseSales 提供)。 */ transformBaseProductToOrderProduct: (params: { payload: Record; fallbackProductId?: number | string | null; sourceProduct?: ProductData | Record | null; sourceItem?: ProductData | Record | null; }) => (Partial & ScanOrderOrderProductIdentity) | null; /** * OS `OrderModule.addProductToOrder`:把商品行写入 tempOrder.products, * 第二参 `booking` 存在时同步生成 tempOrder.bookings 项并通过 `createLinkedProductAndBooking` * 自动改写 booking.product_uid / product.booking_uid,保证商品行与 booking 一一对应。 * * `booking` 字段集严格对齐 OS `TEMP_ORDER_FIELDS_SUMMARY.md` Bookings 章节; * SDK 端推荐通过 `transformDetailToProductAndBooking` 生成 `(product, booking)` 输入, * 不要自行拼装 `_extend` 字段写入这里。 */ addProductToOrder: (product: Partial & ScanOrderOrderProductIdentity, booking?: Record) => Promise; updateOrderProduct: (params: UpdateOrderProductParams) => Promise; updateOrderProductQuantity: (params: UpdateOrderProductQuantityParams) => Promise; removeProductFromOrder: (identity: ScanOrderOrderProductIdentity) => Promise; setOrderProductLineNote: (identity: ScanOrderOrderProductIdentity, note: string) => Promise; updateTempOrderNote: (note: string) => string; /** * 写入整单优惠金额(tempOrder.shop_discount)。 * OS 内部会做 Decimal 标准化:负数归零、保留 2 位小数;返回标准化后的字符串。 */ updateTempOrderShopDiscount: (amount: string | number) => string; /** * 写入联系人信息(tempOrder.contacts_info)。 * OS 内部防御:非对象/数组都归 `null`,只接对象或 null。返回最终写入的对象或 null。 * 实际由 BaseSalesImpl.updateTempOrderContactsInfo 提供(与 OS Order 模块同名, * 与 BaseSales 既有 updateTempOrderNote 命名风格一致)。 */ updateTempOrderContactsInfo: (contactsInfo: Record | null) => Record | null; scanPromotionCode: (code: string, customerId?: number) => Promise; loadDiscountConfig?: (params?: { customerId?: number; action?: 'create' | 'edit'; }) => Promise<{ productList?: Record[]; discountList?: any[]; }>; bestDiscount?: (cb?: (result: any) => void) => Promise<{ productList?: Record[]; discountList?: any[]; }>; calculateProductBookingPrice?: (params: BaseSalesCalculateProductBookingPriceParams) => Promise; setDiscountSelected: (params: { discountId: number; isSelected: boolean; }) => Promise; submitSalesOrder: (params?: SalesSdkSubmitSalesOrderParams) => Promise; loadProducts: (params?: ILoadProductsParams, options?: { callback?: (r: any) => void; subscriberId?: string; }) => Promise; loadProductDetail: (params: ILoadProductDetailParams, options?: { callback?: (r: any) => void; subscriberId?: string; }) => Promise; getProductByIds: (ids: number[]) => Promise; setActiveCustomer: (customer: ICustomer | null) => void; getActiveCustomer: () => ICustomer | null; setSelectedCustomerById: (id: string | number) => void; getCustomers: () => ICustomer[]; getCustomerById: (id: string | number) => ICustomer | null; clearCustomers: () => void; addCustomerToFirst: (customer: ICustomer) => void; getCustomerList: (params?: IGetCustomerListParams) => Promise; changeCustomerPage: (page: number, pageSize?: number) => Promise; loadMoreCustomers: () => Promise; resetAndLoadCustomers: (params?: IGetCustomerListParams) => Promise; /** OS 同步 getter(CustomerModule.getPaginationInfo 暴露),可选;缺失时回退到默认分页 */ getCustomerPaginationInfo?: () => IPaginationInfo; setOrderCustomer?: (customer: ICustomer | null) => void; getOrderCustomer?: () => OrderCustomerView | null; getOrderCustomerSnapshot?: () => ICustomer | null; clearOrderCustomer?: () => void; /** 挂载原生扫码枪 / NFC 桥接(global.peripheralsResult);须在 scanGlobalListener 等之前调用。 */ initPeripheralsListener?: () => void; scanGlobalListener: (cb: (data: IScanResult) => void) => () => void; scanCustomerListener: (cb: (data: IScanResult) => void) => () => void; scanUniversalListener: (cb: (data: IScanResult) => void, key: string) => () => void; activateCamera: (data?: Record) => void; enableAllScanListeners: () => void; disableAllScanListeners: () => void; loadBookingConfig?: (params?: Record) => Promise | null>; loadBookingResources?: (params?: { date?: string; bookingIds?: number[]; }) => Promise; initBookingContext?: (params?: { bookingConfigParams?: Record; date?: any; loadResources?: boolean; bookingIds?: number[]; forceRefresh?: boolean; }) => Promise<{ bookingConfig: Record | null; resourcesOrigin: any[]; resourcesOriginMap: Record; date: string | null; }>; /** 是否已具备与入参等价的 BookingContext(命中则 init 不再打接口)。 */ isBookingContextReady?: (params?: { bookingConfigParams?: Record; date?: any; loadResources?: boolean; bookingIds?: number[]; }) => boolean; setBookingConfig?: (config: Record | null) => void; getBookingConfig?: () => Record | null; setResources?: (resources: any[] | null | undefined) => void; getResourcesOrigin?: () => any[]; getResourcesOriginMap?: () => Record; setBookingDate?: (date: any) => void; getBookingDate?: () => string | null; /** 取商品当前可选资源列表(与 info2 `getResourceByIds` 等价,含三特性)。 */ getResourcesForProduct?: (product: any, options?: { extraResources?: Array<{ form_id: number | string; id: number; }>; }) => any[]; /** 拼装 cacheItem 的 _extend / _data(资源 / 容量 / timeObj),与 info2 `getProductExtend` 等价。 */ getProductExtend?: (cacheItem: any, extra?: Record) => any; /** 取资源不可用原因(结构化 enum + params)。 */ getResourceErrors?: (resource: any, cacheItem: any) => SalesSdkResourceError[]; decideAddProduct?: (item: any, options?: Record) => { action: 'add' | 'requiresDetail' | 'requiresBookingEdit'; cacheItem?: any; payload?: Record; }; decideAfterDetail?: (cacheItem: any, options?: Record) => { action: 'add' | 'requiresBookingEdit'; cacheItem?: any; payload?: Record; }; /** @deprecated 请用 decideAddProduct */ decideAutoClose?: (item: any, options?: Record) => { autoClose: boolean; cacheItem?: any; }; getIsEject?: (item: any, type?: 'select' | 'detail') => 0 | 1; getIsOnlySession?: (item: any) => boolean; buildRequiresDetailPayload?: (item: any, options?: Record) => SalesSdkAddProductRequiresDetailPayload; transformDetailToProductAndBooking?: (input: { item: any; cacheItem?: any; detailResult?: { e: any; extension_type?: any; detail?: any; }; options?: Record; }) => { product: Partial & ScanOrderOrderProductIdentity; booking?: Record; cacheItem: any; }; /** 已加车 order line + booking → 资源编辑抽屉 cacheItem(OS 官方反查)。 */ buildCacheItemFromOrderLine?: (input: { product: ScanOrderOrderProduct; booking: Record; sourceProduct?: Record | null; }) => Record; /** 已加车普通商品 order line → SkuDetailModal edit cacheItem(OS 官方反查)。 */ buildNormalProductCacheItemFromOrderLine?: (input: { product: ScanOrderOrderProduct; sourceProduct?: Record | null; }) => Record; setOtherParams: (params: Record, options?: { cover?: boolean; }) => Promise; getOtherParams: () => Promise> | Record; } /** * `cart.addProduct` 返回 `status='requiresDetail'` 时的 payload。 * UI 拿到后自行打开 Info2(或新版 saleDetail)详情弹窗,弹窗回调后再调 `cart.confirmDetail`。 */ export interface SalesSdkAddProductRequiresDetailPayload { item: ProductData | Record; showConfig: { option: boolean; session: boolean; variant: boolean; package: boolean; number: boolean; }; isOnlySession: boolean; isEject: 0 | 1; customerId?: number | string; date: string | null; productData: ProductData | Record; } /** * `requiresBookingEdit` 时 UI 打开资源 / 时间编辑抽屉所需 payload。 */ export interface SalesSdkRequiresBookingEditPayload { cacheItem: any; customerId?: number | string; date: string | null; /** * 已加车行「编辑资源」:强制打开抽屉,忽略 cacheItem.autoClose。 * 加车链路(requiresBookingEdit)不传此字段;autoClose=true 时自动 confirm 跳过抽屉。 */ forceOpen?: boolean; /** * 购物车跨日编辑:先开抽屉,再在 Host 内异步展开 `_extend.items`。 * 加车 / requiresBookingEdit 链路不传此字段。 */ deferMultiDayExpand?: boolean; /** 跨日判定回退(与 editCartLineBooking 一致) */ sourceProduct?: Record | null; /** 加车链路 catalog 商品,供 Host 补拉报价 */ catalogProduct?: Record | null; } /** * `cart.addProduct` 判别式返回值。 * * - `added`:两关均通过,已写入 OS tempOrder。 * - `requiresDetail`:需打开规格 / SKU 弹窗,callback 后调 `cart.confirmDetail`。 * - `requiresBookingEdit`:规格已就绪,需打开资源 / 时间抽屉,确认后调 `cart.confirmBookingEdit`。 */ export declare type SalesSdkAddProductResult = { status: 'added'; products: ScanOrderOrderProduct[]; } | { status: 'requiresDetail'; payload: SalesSdkAddProductRequiresDetailPayload; } | { status: 'requiresBookingEdit'; payload: SalesSdkRequiresBookingEditPayload; }; /** * `cart.confirmDetail` 判别式返回值(规格选完后可能仍需资源编辑)。 */ export declare type SalesSdkConfirmDetailResult = { status: 'added'; products: ScanOrderOrderProduct[]; } | { status: 'requiresBookingEdit'; payload: SalesSdkRequiresBookingEditPayload; }; /** * `cart.confirmDetail` 输入:UI 拿到 Info2 callback 的 `(e, extension_type, detail)` 后回传。 */ /** 跨日日期弹窗确认结果(对齐 ticketBooking MultiDayTimeSelectModal callback) */ export interface SalesSdkMultiDaySelectResult { startDate: string; endDate: string; duration: number; } /** `openMultiDaySelect` Host 入参 */ export interface SalesSdkOpenMultiDaySelectPayload { productName?: string; /** 日历默认锚点日 YYYY-MM-DD */ defaultStartDate?: string | null; } /** `openHolderSelect` Host 入参 */ export interface SalesSdkOpenHolderSelectPayload { cacheItem: any; maxSelectedCount?: number; customerId?: number | string; } /** 「创建并添加一次性商品」弹窗回传数据(与 booking CustomiseItemModal 字段对齐) */ export interface SalesSdkCustomProductInput { productName: string; vouchersApplicable: boolean; priceType: 'sell' | 'deduction'; priceValue: string; quantity: number; } export interface SalesSdkConfirmDetailInput { item: ProductData | Record; detailResult: { e: any; extension_type?: any; detail?: any; }; options?: { quantity?: number; notShowToast?: boolean; /** 跨日弹窗确认后的区间(规格弹窗之后注入) */ multiDayRange?: SalesSdkMultiDaySelectResult; }; } /** * `cart.confirmBookingEdit` 输入:资源 / 时间抽屉确认后的 cacheItem。 */ export interface SalesSdkConfirmBookingEditInput { cacheItem: any; options?: { quantity?: number; note?: string; }; } /** * 页面级 UI Host:由 SalesSdkProvider 注入,供 `cart.addProductWithFlow` 编排弹窗链。 * * 实现方在页面内可用 Hook 组装后传入;Provider 只持有函数引用,不 import 具体弹窗组件。 */ export interface SalesSdkUIHosts { /** * `requiresDetail` 时打开规格 / SKU / 称重弹窗。 * 用户取消时返回 `null`。 */ openProductDetail?: (payload: SalesSdkAddProductRequiresDetailPayload) => Promise; /** * `requiresBookingEdit` 时打开资源 / 时间编辑抽屉。 * 用户取消时返回 `null`;确认时返回编辑后的 cacheItem。 */ openBookingEdit?: (payload: SalesSdkRequiresBookingEditPayload) => Promise; /** * 规格弹窗确认后、跨日商品须选日期区间(对齐 ticketBooking `MultiDayTimeSelectModal`)。 */ openMultiDaySelect?: (payload: SalesSdkOpenMultiDaySelectPayload) => Promise; /** * 加车前 Holder 选择(对齐 ticketBooking `selectHolderModal`)。 */ openHolderSelect?: (payload: SalesSdkOpenHolderSelectPayload) => Promise; } /** * `cart.addProductWithFlow` 返回值。 * * - `added`:完整链路结束并已写入 OS。 * - `cancelled`:用户在 Host 弹窗/抽屉中取消。 * - `requiresDetail` / `requiresBookingEdit`:决策命中但对应 Host 未配置,交由调用方处理。 */ /** 已加车行「编辑资源」结果 */ export declare type SalesSdkEditCartLineBookingResult = { status: 'updated'; products: ScanOrderOrderProduct[]; } | { status: 'cancelled'; }; /** 已加车普通商品行「编辑 SKU」结果 */ export declare type SalesSdkEditCartLineProductResult = SalesSdkEditCartLineBookingResult; /** 购物车行点击编辑(自动分流 booking / 普通商品) */ export declare type SalesSdkEditCartLineResult = SalesSdkEditCartLineBookingResult; export declare type SalesSdkAddProductWithFlowResult = { status: 'added'; products: ScanOrderOrderProduct[]; } | { status: 'cancelled'; } | { status: 'requiresDetail'; payload: SalesSdkAddProductRequiresDetailPayload; } | { status: 'requiresBookingEdit'; payload: SalesSdkRequiresBookingEditPayload; }; /** `cart.addProductWithFlow` 调用参数(含防抖 / Toast) */ export interface SalesSdkAddProductWithFlowOptions { type?: 'select' | 'detail'; quantity?: number; note?: string; /** 直接加车防抖毫秒,默认 100;0 表示不合并、立即加车 */ debounceMs?: number; /** 为 true 时不展示加车 Toast */ notShowToast?: boolean; } /** * 资源不可用原因(与 OS `BookingContext.ResourceUnavailableReason` 一比一对齐)。 * * i18n 文案不进 OS,UI 拿到 reason + params 后自行渲染,对照表见 `salesSdk/docs.md`。 */ export declare type SalesSdkResourceUnavailableReason = 'no_product' | 'time_out_of_range' | 'count_out_of_range' | 'resource_unbound'; export interface SalesSdkResourceError { reason: SalesSdkResourceUnavailableReason; params: { labelText?: string; startText?: string; endText?: string; isCrossDay?: boolean; }; } /** SalesSdkProvider 状态机 */ export declare type SalesSdkStatus = 'idle' | 'loading' | 'ready' | 'error'; export interface SalesSdkProviderProps { /** 多实例区分;与 ticketBooking 共享同一 osKey 时自动复用其 BookingTicket 实例 */ osKey: string; /** 透传给 OS BookingTicket 的 businessCode */ businessCode?: string; /** * 拉取 board config 的参数(`/core/board/management/config`), * 挂载后由 SalesSdkProvider 调 `bookingTicket.initBookingContext`。 */ bookingConfigParams?: Record; /** 是否在 Provider 挂载后自动初始化 BookingContext,默认 true */ autoInitBookingContext?: boolean; /** 透传给 OS 注册时的 otherParams(platform / type / channel / dineInConfig 等) */ otherParams?: Record; /** 业务自定义 rules hooks,否则用 OrderModule 默认 */ rulesHooks?: RulesParamsHooks; /** * 给定 orderId 且 autoBootstrap !== false 时,挂载后自动 loadSalesDetail; * 不传则不会自动 createNew,避免空 osKey 误建 tempOrder。 */ orderId?: number; /** 默认 true */ autoBootstrap?: boolean; /** * 挂载后是否先 restore 清空 OS 残留 tempOrder / localStorage(与 demo reset 一致), * 再 recalcSummary;有 orderId 时在 restore 之后 loadDetail。默认 true。 * 与 ticketBooking 共享 osKey 且需保留进行中订单时可设为 false。 */ autoRestoreOnInit?: boolean; /** * 页面级弹窗 / 抽屉 Host(规格弹窗、资源编辑等)。 * 配置后 `cart.addProductWithFlow` 会自动串联;未配置时仍可用 `cart.addProduct` 手动分支。 */ uiHosts?: SalesSdkUIHosts; /** 是否开启 tempOrder 主副屏同步,默认关闭,由业务入口按需启用。 */ enableTempOrderSync?: boolean; /** * 是否将 tempOrder 写入 localStorage(OS OrderModule.persistTempOrder)。 * SalesSdk 默认 false:订单仅驻留内存,避免污染 ticketBooking 等同 osKey 的本地缓存。 * ticketBooking 等 legacy 入口不传时 OS 侧仍为 true。 */ enableTempOrderPersist?: boolean; /** * 页面/Tab 是否处于激活态(与 ticketBooking isActive 对齐)。 * 为 true 时挂载或重新激活原生外设扫码桥接;默认 true。 */ isActive?: boolean; children: React.ReactNode; } /** UI 视图:购物车行(多源 enrichment 输出,仅活在 React 内存) */ export interface SalesSdkCartItemView extends ScanOrderOrderProduct { /** 给 OS removeProductFromOrder/updateOrderProduct 的稳定 identity */ _identity: ScanOrderOrderProductIdentity; _extend: { booking: ISalesBooking | null; }; title: string; cover: string; variant_title: string; bundle_titles: string[]; option_titles: string[]; note: string; duration?: number; product_resource?: any; capacity?: any; start_at: string | null; end_at: string | null; resource_id?: number | string | null; } /** Holder 展示字段(Cart enrich 注入,不写 OS) */ export interface SalesSdkHolderDisplayFields { holder_id?: string | number | Array | null; holderOptions?: Array<{ id: string | number; label: string; }>; /** 与 ProductCard cartSkuCard 契约对齐 */ holders?: Array<{ id: string | number; label: string; }>; isFormSubject?: boolean; holderType?: string; holderMaxCount?: number; } /** UI 视图:booking 行(平铺,附带关联商品 + Holder 展示 enrich) */ export interface SalesSdkCartBookingView extends ISalesBooking, SalesSdkHolderDisplayFields { _extend: { product: SalesSdkCartItemView | null; }; } /** UI 视图:客户卡片(selected → tempOrder fallback) */ export interface SalesSdkCustomerView { id: number | string; name: string; phone?: string; email?: string; country_calling_code?: string; cover?: string; isWalkIn: boolean; } /** UI 视图:BookingTicketImpl.getCart 输出(与 OS buildCartView 结构一致) */ export interface SalesSdkCartView { bookings: SalesSdkCartBookingView[]; items: ScanOrderOrderProduct[]; } /** UI 视图:BookingTicketImpl.getProductCatalog 输出 */ export interface SalesSdkProductCatalogView { products: ProductData[]; } export interface SalesSdkContextValue { bookingTicket: BookingTicketHandle | null; osKey: string; status: SalesSdkStatus; error: Error | null; isReady: boolean; shop_full_order_number: string; /** * 直读 OS tempOrder 原貌(提交契约,UI 不污染)。 * * SDK 顶层维护单一真源;Cart / Customer 等子 Provider 通过 `useSalesSdk()` 共享, * 子 Provider 不再重复持有镜像。子 Provider 的写动作(如 cart action)会在末尾 * 触发内部 RootRefresh 重新读 OS,保证此字段与 OS 状态一致。 */ tempOrder: ScanOrderTempOrder | null; /** * 服务端 sales 详情(编辑态有,新建态为 null)。 * 来源同 tempOrder,由 SDK 顶层 Provider 维护。 */ salesDetail: ISalesDetail | null; loadDetail: (orderId: number) => Promise; /** * 强制走远端拉取销售详情(跳过本地 sqlite 缓存),参数形态与 OS `loadSalesDetail` 一致。 */ loadDetailByRemote: (orderId: number) => Promise; /** * 基于当前 tempOrder.order_id 强制从远端刷新销售详情(转发 OS `refreshSalesDetail`)。 * 调用前须已 loadDetail / 存在 order_id;成功后同步顶层 `tempOrder` / `salesDetail`。 * Provider 内部已用 useMemoizedFn 包装,引用稳定。 */ refreshSalesDetail: () => Promise; /** * loadProducts 前确保 boardConfig 已就绪;与 bootstrap `initBookingContext` 同参, * 但不强制拉 resources。bootstrap 已完成且 config 命中时会直接跳过。 */ ensureBookingConfigForLoad: () => Promise; createNew: () => Promise; restore: () => Promise; /** * 仅清空购物车商品行(products / bookings / discount_list),保留客户与其它订单字段。 */ clearCartItemsOnly: () => Promise; /** * 清空所有内容并重置购物车(转发 OS restoreOrder,含清客户)。 */ clearCartAndReset: () => Promise; destroy: () => Promise; /** * 变更当前订单的预约状态,详见 BookingTicketHandle.setBookingStatus。 * Provider 内部已用 useMemoizedFn 包装,引用稳定,可直接放入 useEffect deps。 */ setBookingStatus: (status: string) => Promise; /** * 写入订单备注(tempOrder.note,字符串)。 * 直接转发到 OrderModule.updateTempOrderNote(同步赋值),写完触发内部 refreshRootSnapshot * 让 React 立即拿到新的 tempOrder.note。返回最终写入的字符串。 * Provider 内部已用 useMemoizedFn 包装,引用稳定,可直接放入 useEffect deps。 */ setOrderNote: (note: string) => string; /** * 写入整单优惠金额(tempOrder.shop_discount)。 * 直接转发到 BookingTicket.updateTempOrderShopDiscount(OS 内部 Decimal 标准化: * 负数归零、保留 2 位小数),写完触发 refreshRootSnapshot 让 React 立即拿到新值。 * 返回标准化后的字符串(如 5 -> '5.00')。 * Provider 内部已用 useMemoizedFn 包装,引用稳定,可直接放入 useEffect deps。 */ setShopDiscount: (amount: string | number) => string; /** * 写入联系人信息(tempOrder.contacts_info)。 * 直接转发到 BookingTicket.updateTempOrderContactsInfo(OS 内部防御:非对象/数组归 null), * 写完触发 refreshRootSnapshot 让 React 立即拿到新值。返回最终写入的对象或 null。 * Provider 内部已用 useMemoizedFn 包装,引用稳定,可直接放入 useEffect deps。 */ setContactsInfo: (contactsInfo: Record | null) => Record | null; /** * 激活扫码/二维码相机入口,直接转发到底层 BookingTicket。 */ activateCamera: (data?: Record) => void; /** * 打开结账面板:派发 `pisell1.handleOpenPayment`, * `baseSalesModuleName` 自动取本 Provider 的 BookingTicket moduleKey(`getBookingTicketKey(osKey)`), * 调用方无需感知模块命名规则。 * * 返回值为底层 action callback 的回传结果,便于消费方在结账完成后做后续动作(如刷新详情)。 * 当宿主环境未注入 `appHelper.utils.action` 时直接抛错。 */ openCheckout: () => Promise; /** * 读取当前订单折扣列表(转发 OS `BaseSales.getDiscountList`)。 * 返回克隆数组,避免消费方就地修改污染 OS store。 */ getDiscountList: () => any[]; /** * 扫描优惠码(转发 OS `BaseSales.scanPromotionCode`)。 * 成功后触发顶层 `tempOrder` 快照刷新;返回轻量结果(不含 discountList,需再调 `getDiscountList`)。 */ scanPromotionCode: (code: string, customerId?: number) => Promise; /** * 勾选/取消订单折扣(转发 OS `BaseSales.setDiscountSelected`)。 * OS 内部会重算 rules、行价与 summary;完成后同步顶层快照。 */ setDiscountSelected: (params: { discountId: number; isSelected: boolean; }) => Promise; } export interface SalesSdkProductsContextValue { products: ProductData[]; loading: boolean; error: Error | null; load: (params?: ILoadProductsParams, options?: { subscriberId?: string; }) => Promise; loadDetail: (params: ILoadProductDetailParams) => Promise; findByIds: (ids: number[]) => Promise; scanGlobalListener: (cb: (data: IScanResult) => void) => () => void; scanCustomerListener: (cb: (data: IScanResult) => void) => () => void; scanUniversal: (cb: (data: IScanResult) => void, key: string) => () => void; activateCamera: (data?: Record) => void; enableScan: () => void; disableScan: () => void; } /** 客户搜索选项:弹窗打开/输入 debounce 等静默刷新时可关闭 Search 按钮 loading */ export interface SalesSdkCustomerSearchOptions { /** 是否展示 Search 按钮 loading,默认 true */ showLoading?: boolean; } export interface SalesSdkCustomerContextValue { selected: ICustomer | null; list: ICustomer[]; pagination: IPaginationInfo; hasMore: boolean; loading: boolean; loadingMore: boolean; searchParams: Record; select: (customer: ICustomer | null) => void; loadList: (params?: IGetCustomerListParams) => Promise; search: (keyword: string, options?: SalesSdkCustomerSearchOptions) => Promise; loadMore: () => Promise; changePage: (page: number, pageSize?: number) => Promise; addToFirst: (customer: ICustomer) => void; clear: () => void; loadById: (id: string | number) => ICustomer | null; } export interface SalesSdkSummaryContextValue { summary: ScanOrderSummary | null; } export interface SalesSdkCartContextValue { /** 直读 OS tempOrder 原貌(提交契约,UI 不污染) */ tempOrder: ScanOrderTempOrder | null; /** 服务端 sales 详情(编辑态有,新建态为 null) */ salesDetail: ISalesDetail | null; discountList: any[]; /** 平铺 booking + 关联商品(透传 OS) */ bookings: SalesSdkCartBookingView[]; /** 未关联 booking 的商品行(透传 OS tempOrder.products) */ items: SalesSdkCartItemView[]; /** * 加车主入口:走 OS `decideAddProduct` 两阶段决策 → `added` | `requiresDetail` | `requiresBookingEdit`。 */ addProduct: (product: ProductData & Record, options?: { type?: 'select' | 'detail'; quantity?: number; note?: string; }) => Promise; /** 规格弹窗 callback 后回灌;可能返回 `requiresBookingEdit`。 */ confirmDetail: (input: SalesSdkConfirmDetailInput) => Promise; /** 资源 / 时间抽屉确认后加车。 */ confirmBookingEdit: (input: SalesSdkConfirmBookingEditInput) => Promise<{ products: ScanOrderOrderProduct[]; }>; /** * 加车编排入口:内部 `addProduct` → uiHosts 弹窗链 → `confirmDetail` / `confirmBookingEdit`。 * Host 未配置时遇 `requiresDetail` / `requiresBookingEdit` 会直接返回对应 status。 */ addProductWithFlow: (product: ProductData & Record, options?: SalesSdkAddProductWithFlowOptions) => Promise; /** * 添加自定义(一次性)商品:直接写入 tempOrder.products,不走 decideAddProduct 弹窗链。 */ addCustomProduct: (input: SalesSdkCustomProductInput) => Promise; removeItem: (item: SalesSdkCartItemView) => Promise; updateItem: (params: UpdateOrderProductParams) => Promise; /** 仅更新购物车行数量;传入 cart item / booking 行与目标数量即可 */ updateItemQuantity: (item: SalesSdkCartItemView | SalesSdkCartBookingView, num: number) => Promise; setOrderNote: (note: string) => string; setLineNote: (identity: ScanOrderOrderProductIdentity, note: string) => Promise; setDiscount: (params: { discountId: number; isSelected: boolean; }) => Promise; scanCode: (code: string, customerId?: number) => Promise; submit: (params?: SalesSdkSubmitSalesOrderParams) => Promise; recalcSummary: () => Promise; /** * 已加车预约行编辑资源/时间:OS 反查 cacheItem → openBookingEdit → updateOrderProduct + booking。 * 无 booking 的行会抛错;未配置 openBookingEdit 时抛错。 */ editCartLineBooking: (item: SalesSdkCartItemView) => Promise; /** * 已加车普通商品行编辑 SKU:OS 反查 cacheItem → SkuDetailModal → updateOrderProduct(无 booking)。 */ editCartLineProduct: (item: SalesSdkCartItemView) => Promise; /** * 购物车行点击编辑:booking 行反查 items 后,按 `_extend.booking` 分流 * `editCartLineBooking` / `editCartLineProduct`。 */ editCartLine: (target: SalesSdkCartItemView | SalesSdkCartBookingView) => Promise; }