import { ConfigProps } from "../../pro/skuDetailModal/types.js"; import { CreateLegacyOpenProductDetailHostDeps } from "./demo/hosts/legacyOpenProductDetail.js"; import React from "react"; import { WeightUnitSymbol } from "@pisell/utils"; //#region src/plus/salesSdk/types.d.ts /** OS 商品行 SKU(v2 规格权威源:product_sku.option) */ interface ScanOrderProductSku { option?: any[]; /** 称重商品标识与订单行称重快照(CartSkuCard 直接消费)。 */ open_sold_weight?: 0 | 1; unit_data_snapshot?: Record | null; unit?: WeightUnitSymbol | string; unit_weight?: string | number; unit_price?: string | number; weight_source?: 'auto' | 'manual' | string; tare_name?: string; tare_weight?: string | number; weight?: string | number; net_weight?: string | number; [key: string]: any; } /** 商品行 identity(OS BaseSalesOrderProductIdentity / ScanOrderOrderProductIdentity 子集) */ interface ScanOrderOrderProductIdentity { product_id: number | null; product_variant_id: number; /** @deprecated 行级唯一值统一使用 metadata.unique_identification_number */ identity_key?: string; unique_identification_number?: string; product_sku?: ScanOrderProductSku; /** @deprecated 读规格请用 product_sku.option;仅 legacy 缓存/identity 兼容 */ product_option_item?: any[]; product_bundle?: any[]; } /** 商品行(OS ScanOrderOrderProduct 结构性子集) */ interface ScanOrderOrderProduct extends ScanOrderOrderProductIdentity { order_detail_id: number | null; num: number; product_sku: ScanOrderProductSku; /** enrich 输出兼容字段;运行时规格权威源为 product_sku.option */ 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; product_title?: Record; } /** OS Order/SalesSummary 汇总结构 */ 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; } interface ScanOrderRefundPayment { id?: number | string; order_payment_id?: number | string; payment_number?: string; code?: string; name?: string; payment_method?: string; payment_method_name?: string; type?: string; amount?: string | number; origin_amount?: string | number; refund_amount?: string | number; rounding_amount?: string | number; service_fee?: string | number; status?: string; payment_time?: string; created_at?: string; platform?: string; channel?: string; channel_id?: number | string; custom_payment_id?: number | string; custom_payment_type?: string; product_voucher?: unknown; voucher_id?: number | string; operator_type?: string; operator_id?: number | string; operator_name?: string; metadata?: Record; [key: string]: any; } interface ScanOrderRefund { order_refund_id?: number | string; refund_number?: string; order_id?: number | string; order_payment_id?: number | string; quantity?: number; amount?: string | number; shipping_amount?: string | number; product_amount?: string | number; diff_amount?: string | number; transaction_id?: string; note?: string; status?: string; error_message?: string; platform?: string; channel?: string; channel_id?: number | string; operator_type?: string; operator_id?: number | string; operator_name?: string; metadata?: Record; created_at?: string; updated_at?: string; payment?: ScanOrderRefundPayment; [key: string]: any; } type SalesSdkPaymentTimelineKind = 'payment' | 'refund'; type SalesSdkPaymentTimelineLifecycle = 'success' | 'processing' | 'refunded' | 'failed' | 'unknown'; interface SalesSdkPaymentTimelineMethod { code?: string; name?: string; paymentMethodName?: string; type?: string; customPaymentId?: number | string; customPaymentType?: string; productVoucher?: unknown; metadata?: Record; } interface SalesSdkPaymentTimelineRecord { key: string; kind: SalesSdkPaymentTimelineKind; lifecycle: SalesSdkPaymentTimelineLifecycle; status?: string; orderPaymentId?: number | string; transactionNumber?: string; transactionId?: string; occurredAt?: string; amount: string | number; originAmount?: string | number; /** 支付渠道实际扣款总额;EFTPOS 可包含支付手续费。 */ chargedAmount?: string | number; refundedAmount?: string | number; remainingAmount?: string | number; serviceFee?: string | number; paymentMethod: SalesSdkPaymentTimelineMethod; platform?: string; channel?: string; channelId?: number | string; operatorType?: string; operatorId?: number | string; operatorName?: string; } interface SalesSdkPaymentTimelineGroup { key: string; payment?: SalesSdkPaymentTimelineRecord; refunds: SalesSdkPaymentTimelineRecord[]; } /** 提交给订单退款接口的单笔支付;EFTPOS 成功补记时会携带支付方式信息。 */ interface SalesSdkRefundPaymentInput { id: number | string; amount: string; payment_method?: string; custom_payment_type?: string; custom_payment_id?: number | string; max?: string; product_voucher?: unknown; payment_method_name?: string; metadata?: { /** 现金退款归属的钱箱 ID。 */shop_wallet_pass_id?: string; unique_refund_number?: string; manual_marking_flag?: 1; }; } interface SalesSdkRefundPaymentsOptions { /** 由普通退款接口在退款成功后同时取消订单。 */ cancelOrder?: boolean; /** 退款备注。 */ note?: string; } /** * 普通退款接口已经成功后的结果。详情刷新失败不会把本次退款重新标记为失败, * 调用方应提示用户刷新详情,而不是重复提交退款。 */ interface SalesSdkRefundPaymentsResult { response: unknown; salesDetail?: ISalesDetail; refreshError?: Error; } type SalesSdkSalesNoteTextInput = { kind: 'localizedSelections'; selections: Array<{ id: string; label: string | Record; }>; locale: string; input?: string; } | { kind: 'plain'; input: string; } | { kind: 'resolved'; text: Record; }; interface SalesSdkSalesNoteRelation { uuid: string; relation_type: 'about' | 'related_to'; target_type: 'order' | 'order_detail'; target_uuid: string; is_primary: 0 | 1; } interface SalesSdkSalesNote { uuid: string; note_type: string; content: { text: Record; }; importance: string; note_relations: SalesSdkSalesNoteRelation[]; } /** Sales Notes 乐观写入前的精确回滚快照;调用方只应原样传回 restore。 */ interface SalesSdkSalesNotesMutationSnapshot { orderIdentity: { orderId: number | string | null; externalSaleNumber: string; }; notes: SalesSdkSalesNote[]; state: { loaded: boolean; dirty: boolean; }; } interface SalesSdkSetSalesNoteInput { note_type: string; target: { target_type: 'order_detail'; target_uuid: string; }; text: SalesSdkSalesNoteTextInput; } /** tempOrder(OS ScanOrderTempOrder 结构性子集) */ 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[]; /** Sales 一级 Notes 快照;旧版 OS 或未加载 with=notes 时可能缺失。 */ notes?: SalesSdkSalesNote[]; bookings: any[]; payments: any[]; refunds?: ScanOrderRefund[]; 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) */ 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; } 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; } 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; } interface SalesSdkPaymentMethod { id: number | string; code: string; name: string; type: string; enabled?: boolean; enable?: boolean; channel?: string; channel_application?: string[]; fixed?: string | number; percentage?: string | number; surcharge?: string | number; metadata?: Record; [key: string]: any; } interface SalesSdkCommitPaymentMethodParams { paymentMethodId?: number | string; paymentMethodCode?: string; amount: number; originAmount?: number; serviceCharge?: { amount?: string | number; percentage?: string | number; } | null; metadata?: Record; paymentData?: Record; } interface SalesSdkCommitPaymentMethodResult { payment: Record; payments: Record[]; syncResult: Record; isOrderFullyPaid: boolean; } interface SalesSdkOrderAmountSnapshot { isDeposit: boolean; depositAmount: string; expectAmount: string; totalAmount: string; amountGap: string; summary: ScanOrderSummary; } interface ISalesSurcharge { amount?: string; type?: string; name?: Record; [key: string]: any; } interface ISalesDetailCustomer { id: number | string | null; name: string; phone?: string; email?: string; country_calling_code?: string; [key: string]: any; } interface ISalesDetail { header: ISalesDetailHeader; products: ScanOrderOrderProduct[]; bookings: ISalesBooking[]; rootBooking: ISalesBooking | null; bookingsByProductUid: Record; payments: ISalesPayment[]; refunds?: ScanOrderRefund[]; surcharges: ISalesSurcharge[]; discount_list: any[]; customer: ISalesDetailCustomer | null; summary: Record; raw: Record; } /** * 赠品候选选项(来自评估器的 giftOptions 字段)。 */ interface SalesSdkGiftOption { product_id: number; product_variant_id: number; } /** * GiftSelect Host 请求 context(OS → SDK)。 * * @example * giftSelectBridge.request({ strategyId, strategyName, giftCount, giftOptions, sourceProductIds }) */ interface SalesSdkGiftSelectContext { strategyId: string; strategyName: string | Record; giftCount: number; giftOptions: SalesSdkGiftOption[]; /** 触发此赠品的主商品 metadata.unique_identification_number 列表 */ sourceProductIds: string[]; /** edit:购物车重选赠品,单候选也弹窗 */ mode?: 'add' | 'edit'; /** 预填已选(购物车重选) */ initialChoices?: SalesSdkGiftSelectChoice[]; /** legacy 弹窗 prefill 用(完整 enrich 行) */ _selectedOriginGifts?: Array>; } /** CartSkuCard Gift 行展示数据(与 legacy formatGiftData 对齐) */ interface SalesSdkCartGiftData { isShow: boolean; totalCount: number; selectedGifts: Array<{ id: number | string; title: string; image?: string; }>; _selectedOriginGifts?: SalesSdkCartItemView[]; } /** GiftSelect 弹窗回传:用户最终选择的 (variant + 数量) 列表 */ interface SalesSdkGiftSelectChoice { product_id: number; product_variant_id: number; quantity: number; /** * 弹窗内已加载的商品快照(confirm 时复用,避免重复 `/shop/product/query`)。 * 仅内存传递,不会写入 tempOrder。 */ _productSnapshot?: Record; } /** * GiftSelect resolver 返回值。 * * @example * return { products: [giftProduct], bookings: [giftBooking] }; */ interface SalesSdkGiftSelectResult { products: Array>; bookings?: Record[]; } /** * 未满足的促销策略(购物车底部 alert 渲染源)。 */ interface SalesSdkUnfulfilledPromotion { strategyId: string; strategyName: string | Record; actionType: string; needQuantity: number; currentQuantity: number; requiredQuantity: number; eligibleProducts: SalesSdkGiftOption[]; strategyMetadata?: any; display?: { text: string | Record; type: string; }; } /** 商品卡促销标签(appendPromotionTags 写入 `_promotionTags`) */ interface SalesSdkPromotionTag { text: string | Record; type: string; strategyId?: string; } /** 上次 applyPromotion 计算出的赠品操作 diff */ interface SalesSdkLastGiftActions { toAdd: Array<{ strategyId: string; strategyName: string | Record; giftCount: number; giftOptions: SalesSdkGiftOption[]; sourceProductIds: string[]; }>; toReduce: Array<{ strategyId: string; items: Array<{ uid: string; currentQuantity: number; newQuantity: number; }>; }>; toRemove: string[]; } /** OS CustomerModule 选中客户(结构性子集) */ 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。 */ interface OrderCustomerView { customer_id: number | null; customer_name: string; phone: string; email: string; country_calling_code: string; } interface IPaginationInfo { page: number; pageSize: number; total: number; totalPages: number; } interface ICustomerListResponse { list: ICustomer[]; total: number; page?: number; pageSize?: number; } interface IGetCustomerListParams { skip?: number; num?: number; search?: string; [key: string]: any; } /** 商品列表条目(OS ProductData,UI 只用一小部分字段) */ interface ProductData { id: number; title?: string; cover?: string; duration?: number; holder_config?: { status?: string | number | boolean; required?: number | string | boolean; resource_id?: number | string | null; }; product_resource?: any; variants?: any[]; bundles?: any[]; options?: any[]; [key: string]: any; } /** OS BookingTicket 加载商品参数 */ 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; extension_type?: string[]; status?: string; /** 智能定价条件上下文,仅影响策略命中,不参与商品集合筛选 */ strategy_context?: Record; } interface ILoadProductDetailParams extends Omit { product_id: number; } interface IScanResult { status: 'success' | 'fail'; scanType: string; scanCode: string; result: any; } /** Order.updateOrderProduct 入参 */ 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_sku?: ScanOrderProductSku; /** @deprecated 读规格请用 product_sku.option */ product_option_item?: any[]; product_bundle?: any[]; /** 与商品行 1:1 关联的 booking;传则替换 tempOrder.bookings 中对应项(OS updateOrderProduct)。 */ booking?: Record; /** * 预约时间 / 时长变更后允许 OS 使用本次报价覆盖已落库订单行历史价。 * * @example * await cart.updateItem({ * product_id: 1, * product_variant_id: 0, * updates: { selling_price: '6.00', metadata: { source_product_price: '6.00' } }, * booking: { duration: 2 }, * allow_booking_reprice: true, * }); */ allow_booking_reprice?: boolean; /** * Change time 重报价后,允许 OS 将已选编辑态折扣卡重新应用到当前预约商品行。 */ allow_booking_discount_reapply?: boolean; } /** Order.updateOrderBooking 入参;仅覆盖 updates 中显式给出的 booking 字段。 */ interface UpdateOrderBookingParams { unique_identification_number: string; updates: Record; } /** Order.updateOrderProductQuantity 入参;行定位语义与 updateOrderProduct / removeProductFromOrder 一致 */ interface UpdateOrderProductQuantityParams extends ScanOrderOrderProductIdentity { num: number; } /** @deprecated 使用 UpdateOrderProductParams */ type UpdateProductInOrderParams = UpdateOrderProductParams; /** OS BaseSales.scanPromotionCode 返回 */ interface BaseSalesScanCodeResult { isAvailable: boolean; /** 卡券已加入 Promotion;可能因当前没有适用商品而暂时无法应用。 */ isAccepted?: boolean; type?: 'server' | string; unavailableReason?: string; /** batchSearch 原始结果;Holder 补全等流程可从这里读取原始卡券。 */ scannedDiscountList?: Record[]; } interface BaseSalesQuotationDurationConfig { type: string; value: number; flexible_config?: { is_enable_minimum_duration?: 0 | 1; warning_threshold?: number; block_threshold?: number; }; } 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; } 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'; } 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[]; } 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[]; } interface BaseSalesQuotationCustomerScopeInfo { quotationCount: number; hasCustomerScopedQuotations: boolean; allQuotationsCustomerScoped: boolean; customerIds: string[]; customers: Array>; quotationScopes: Array<{ quotationId: number; quotationName: string; customerIds: string[]; customers: Array>; }>; } interface BaseSalesPriceRecalculationContextChange { previousCustomerId?: number | string | null; nextCustomerId?: number | string | null; [key: string]: any; } /** Order rules hooks(结构性,原生类型来自 OS RulesParamsHooks) */ interface RulesParamsHooks { getProduct?: (...args: any[]) => any; setProduct?: (...args: any[]) => any; [key: string]: any; } /** * OS `LoadSalesDetailParams` 结构性 alias(字段命名与 Order/types 一致,勿自行发明映射)。 */ interface SalesSdkLoadSalesDetailParams { orderId?: number | string; orderNumber?: string; externalSaleNumber?: string; external_sale_number?: string; /** * 已由上游搜索接口拿到的销售详情。 * 传入后 OS 会跳过按 id 远端查询,直接复用详情 hydrate 流程。 */ preloadedSalesDetail?: Record; forceRemote?: boolean; merge?: boolean; } /** 子预约状态机转移动作(对齐 OS BookingTicket BookingTransitionAction) */ type BookingTransitionAction = 'confirm' | 'reject' | 'arrive' | 'start' | 'complete' | 'cancel' | 'no_show' | 'reschedule'; /** 子预约状态转移入参 */ interface TransitionChildBookingParams { schedule_event_id: number | string; action: BookingTransitionAction; } /** * OS BaseSales.submitSalesOrder 入参(结构性 alias,与 BaseSalesImpl 一致)。 */ interface SalesSdkSubmitSalesOrderParams { payments?: Record[]; paymentStatus?: string; smallTicketDataFlag?: number; confirmPendingVoucherPayments?: boolean; enhancePayload?: (payload: Record, ctx: Record) => Record; } /** SDK 提交行为选项,仅控制前端提交前流程,不透传给 OS submitSalesOrder。 */ interface SalesSdkSubmitOptions { /** 仅特定业务按钮显式开启 holder 必填确认,默认不校验。 */ validateHolder?: boolean; /** Checkout Wallet 数据来源;BigSale 使用 shared,其它入口默认 legacy。 */ walletPassMode?: 'legacy' | 'shared'; /** * 支付结果展示方式。默认由 PaymentModal 以 toast 展示;page 模式只把 * PaymentResultToastV2 ViewModel 回传给调用页面,由页面自行渲染。 */ paymentResultDisplayMode?: 'toast' | 'page'; /** * 提交成功后跳过 forceRemote 重拉详情,改为读 OS 本地内存快照 * (`getTempOrder()` / `getSalesOrder()`)同步到顶层 state。 * * 适用于「提交接口本身已返回更新后的完整详情、且已写入 OS 内存快照」的场景 * (如 add time 保存),省掉一次按 id 的远端详情请求。默认 false(仍 forceRemote 重拉)。 */ skipRemoteRefresh?: boolean; } interface AddTimePlanLine { product_id: string | number; product_title?: string; product_add_schedule_time: number; num: number; price: number; _originalProduct?: unknown; } interface ResolveBestAddTimePlanResult { available: boolean; unavailableReason?: 'NO_PRODUCTS' | 'NO_COVERAGE'; targetMinutes: number; coveredMinutes: number; totalPrice: number; lines: AddTimePlanLine[]; } type SalesSdkWalletAssetId = string | number; type SalesSdkWalletAssetSelectionSource = 'manual' | 'scan'; interface SalesSdkWalletAssetsState { status: 'idle' | 'loading' | 'ready' | 'error'; items: any[]; selectedIds: SalesSdkWalletAssetId[]; selectedItems: any[]; committedIds: SalesSdkWalletAssetId[]; selectionSources: Record; totalSelectedAmount: number; customerId?: string | number; error: string | null; revision: number; } /** Shared Checkout 跨 BaseSales 运行时传递的钱包资产快照。 */ interface SalesSdkWalletAssetsSnapshot { state: SalesSdkWalletAssetsState; searchResults: any[]; pendingAutoSelectSearchCodes?: string[]; walletParams: Record | null; calculationContext: { orderTotalAmount: number; products: any[]; }; } interface SalesSdkScanWalletAssetResult { type: 'walletCode' | 'normalCode'; asset?: any; selected: boolean; state: SalesSdkWalletAssetsState; } interface SalesSdkCashPaymentConfig { available: boolean; paymentMethod?: Record; amountDue: number; recommendedAmounts: number[]; } interface SalesSdkCashPaymentResult { payment: Record; payments: Record[]; syncResult: Record; isOrderFullyPaid: boolean; } interface SalesSdkConfirmWalletPaymentResult { submitResult: Record; payments: Record[]; isOrderFullyPaid: boolean; } interface ProductListStoreQueryParams { includeIds?: Array; excludeIds?: Array; categoryIds?: Array; collectionIds?: Array; extensionTypes?: string[]; statuses?: ProductData['status'][]; channelApplications?: string[]; codeOrBarcode?: string; } type SalesSdkAllergyLocalizedText = string | Record; interface SalesSdkAllergyDataSourceItem { id: string; label: SalesSdkAllergyLocalizedText; } interface SalesSdkAllergyRequirement { strategyId: string; ruleId: string; priority: number; targetType: 'product'; product_id: number; product_variant_id?: number; dataSource: { type: 'inline'; items: SalesSdkAllergyDataSourceItem[]; }; promptScope: 'once_per_order_product'; allowNote: boolean; } type SalesSdkAllergySelectionResult = { kind: 'none'; } | { kind: 'allergies'; selectedAllergenIds: string[]; note?: string; }; interface SalesSdkAllergyReceipt { flowId: string; strategyId: string; ruleId: string; productId: number; productVariantId?: number; declaration: SalesSdkAllergySelectionResult; /** 过敏声明对应的通用 OS Sales Note 草稿;kind=none 时不存在。 */ noteDraft?: SalesSdkSalesNoteDraftInput; declaredAt: number; } interface SalesSdkOpenAllergySelectionPayload { requirement: SalesSdkAllergyRequirement; locale: string; onSubmit: (result: SalesSdkAllergySelectionResult) => Promise; } interface SalesSdkEnsureAllergyBeforeCommitInput { product: ProductData & Record; flowId?: string; } /** * SDK 只负责保存过敏选择的原始多语言数据;Note 主键、Relation 与内容格式由 OS 统一生成。 */ interface SalesSdkSalesNoteDraftInput { note_type: 'allergy'; text: { kind: 'localizedSelections'; selections: SalesSdkAllergyDataSourceItem[]; locale: string; input?: string; }; } interface SalesSdkAllergyPreparationResult { allowed: boolean; noteDraft?: SalesSdkSalesNoteDraftInput; } /** * BookingTicket OS 模块运行时句柄(极简结构性接口;运行时由 `pisellos.getModule(...)` 返回的实例提供)。 * * 对外(消费方)极少直接消费此引用,但 `useSalesSdk().bookingTicket` 仍提供给高级场景。 * 实际类型由 OS(@pisell/pisellos)提供,这里仅描述本 SDK 调用到的方法。 */ 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: (params?: { forceRemote?: boolean; }) => Promise; /** 是否允许 OS 根据后台 order:onOrdersChanged 自动刷新当前 sales detail。 */ setServerOrderChangeSyncEnabled?: (enabled: boolean) => void; /** * 更新当前预约的 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; /** * 子预约状态机转移(POST /schedule/booking-transition/{schedule_event_id})。 * 按 schedule_event_id 定位子预约,body `{ action }`;成功后刷新销售详情。 * * @example * await bookingTicket.transitionChildBooking({ schedule_event_id: 301, action: 'start' }); */ transitionChildBooking: (params: TransitionChildBookingParams) => Promise; getSalesOrder: () => ISalesDetail | null; getOrderHeader: () => ISalesDetailHeader | null; getBookings: () => ISalesBooking[]; getRootBooking: () => ISalesBooking | null; getChildBookingsByProductUid: (productUid: string) => ISalesBooking[]; getPayments: () => ISalesPayment[]; getSurcharges: () => ISalesSurcharge[]; getTempOrder: () => ScanOrderTempOrder | null; createSalesNotesMutationSnapshot?: () => SalesSdkSalesNotesMutationSnapshot; restoreSalesNotesMutationSnapshot?: (snapshot: SalesSdkSalesNotesMutationSnapshot) => void | Promise; setSalesNote?: (input: SalesSdkSetSalesNoteInput) => Promise; removeSalesNote?: (uuid: string) => Promise; getOrderProducts: () => ScanOrderOrderProduct[]; getDiscountList: () => any[]; getOriginalDiscountList?: () => any[]; replaceDiscountStoreLists?: (params: { discountList: any[]; originalDiscountList?: any[]; }) => Promise; getSummary: () => Promise; getCart?: () => SalesSdkCartView; getCustomer?: () => ICustomer | null; getProductCatalog?: () => SalesSdkProductCatalogView; /** * 读取 OS 已缓存的 Pico OpenData 配置。 * * @example * const openData = await bookingTicket.getOpenData?.(); */ getOpenData?: () => Promise | null>; /** 将自助点餐固定为柜台取餐。 */ setPickupReferenceMode?: (mode: 'counter_pickup') => unknown | Promise; /** 写入顾客选择的堂食/外卖订单类型。 */ setServiceType?: (type: 'dine_in' | 'takeaway') => unknown | Promise; /** UnifiedBookingSales item_rules:返回当前渠道和商品命中的单条过敏要求。 */ resolveProductAllergyRequirement?: (params: { productId: number | string; productVariantId?: number | string; channel?: string; }) => Promise; /** UnifiedBookingSales exposes an explicit target-aware OpenData loader. */ loadOpenData?: (params: { businessCode: string; channel: string; force?: boolean; }) => Promise; /** Shared Wallet Assets:新 UI 统一通过 BaseSales/BookingTicket 调用。 */ getWalletAssetsState?: () => SalesSdkWalletAssetsState; getOrderAmountSnapshot?: () => SalesSdkOrderAmountSnapshot | null; configureWalletAssetsBehavior?: (options: { autoSelectAfterSearch?: boolean; }) => void; exportWalletAssetsSnapshot?: () => SalesSdkWalletAssetsSnapshot; importWalletAssetsSnapshot?: (snapshot: SalesSdkWalletAssetsSnapshot) => Promise; refreshWalletAssets?: (params?: { order_wait_pay_amount?: number; preserveSelection?: boolean; }) => Promise; selectWalletAsset?: (params: { assetId: SalesSdkWalletAssetId; selected: boolean; source?: SalesSdkWalletAssetSelectionSource; }) => Promise; scanWalletAsset?: (code: string) => Promise; clearWalletAssetSelection?: () => Promise; getPaymentMethodsAsync?: () => Promise; commitPaymentMethodPayment?: (params: SalesSdkCommitPaymentMethodParams) => Promise; getCashPaymentConfig?: () => Promise; payCash?: (params: { amount: number; actualPaidAmount?: number; changeAmount?: number; roundingAmount?: number; }) => Promise; confirmWalletPayment?: () => Promise; roundAmount?: (params: { amount: string | number; interval?: string | number; rule?: string; }) => { originalAmount: string; roundedAmount: string; roundingDifference: string; }; 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, options?: { insertAfterProductUid?: string; disableMerge?: boolean; salesNotes?: SalesSdkSalesNoteDraftInput[]; }) => Promise; updateOrderProduct: (params: UpdateOrderProductParams) => Promise; updateOrderProducts?: (paramsList: UpdateOrderProductParams[]) => Promise; /** 按 booking UID 更新预约;OS 会保留未出现在 updates 中的原字段。 */ updateOrderBooking?: (params: UpdateOrderBookingParams) => Record[]; updateOrderProductQuantity: (params: UpdateOrderProductQuantityParams) => Promise; removeProductFromOrder: (identity: ScanOrderOrderProductIdentity, options?: { preserveDisplayPosition?: { remainingProductUid: string; anchorProductUid: string; }; }) => Promise; /** 批量删除购物车行,末尾仅一次促销重算(合并预约行删除/减数量场景) */ removeProductsFromOrder?: (identities: ScanOrderOrderProductIdentity[], options?: { preserveDisplayPosition?: { remainingProductUid: string; anchorProductUid: string; }; }) => 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[]; availableWalletIds?: number[]; }>; /** PC/H5 登录后把当前登录用户正式同步为 tempOrder 的下单客户。 */ syncAuthenticatedOrderCustomer?: () => Promise; getAvailableWalletIds?: () => number[]; bestDiscount?: (cb?: (result: any) => void) => Promise<{ productList?: Record[]; discountList?: any[]; }>; calculateProductBookingPrice?: (params: BaseSalesCalculateProductBookingPriceParams) => Promise; calculateProductBookingPrices?: (paramsList: BaseSalesCalculateProductBookingPriceParams[]) => Promise; getQuotationCustomerScopeInfo?: () => BaseSalesQuotationCustomerScopeInfo; shouldRecalculateProductBookingPricesForContextChange?: (params: BaseSalesPriceRecalculationContextChange) => boolean; setDiscountSelected: (params: { discountId: number; isSelected: boolean; }) => Promise; submitSalesOrder: (params?: SalesSdkSubmitSalesOrderParams) => Promise; /** * 注入促销评估器(默认由 SalesSdkProvider 从 `appHelper.utils.promotionEvaluator` 自动注入)。 * 业务侧一般不直接调用。 */ setPromotionEvaluator?: (evaluator: any | null) => void; /** * 注入赠品选择 resolver。SDK 默认通过 GiftSelect host bridge 注入,业务侧一般不直接调用。 * resolver 返回完整 OrderProduct 列表(含 metadata._giftInfo 标记)。 */ setGiftSelectResolver?: (resolver: ((ctx: SalesSdkGiftSelectContext) => Promise> | SalesSdkGiftSelectResult | null>) | null) => void; /** * 为商品目录追加促销标签。新版 OS 由 BaseSales/BookingTicket 统一提供; * 旧版 OS 缺失时 SDK 会回退到本地兼容实现。 */ appendPromotionTags?: >(products: T[]) => T[]; /** 主动触发一次促销重算(一般写路径会自动触发)。 */ applyPromotion?: () => Promise; /** 上一次促销计算输出的未满足促销提示(购物车底部 alert 渲染源)。 */ getUnfulfilledPromotions?: () => SalesSdkUnfulfilledPromotion[]; /** 上一次促销计算输出的赠品差异化操作 diff。 */ getLastGiftActions?: () => SalesSdkLastGiftActions | null; loadProducts: (params?: ILoadProductsParams, options?: { callback?: (r: any) => void; subscriberId?: string; }) => Promise; /** * 获取加时商品列表,返回 OS 商品 catalog 原始结构。 * * @example * const products = await bookingTicket.getAddTimeProducts?.({ schedule_date: '2026-06-16' }); */ getAddTimeProducts?: (params?: ILoadProductsParams) => Promise; /** 取消 /product/query WS 订阅(与 loadProducts 的 subscriberId 配对) */ unsubscribeProductQuery?: (subscriberId?: string) => void; loadProductDetail: (params: ILoadProductDetailParams, options?: { callback?: (r: any) => void; subscriberId?: string; }) => Promise; /** * 只读查询单商品详情:不写 productCatalog、不触发 onProductsLoaded。 * 旧版 pisellos 无此方法时,购物车编辑预约行会回退 loadProductDetail。 */ queryProductDetail?: (params: ILoadProductDetailParams, options?: { callback?: (r: any) => void; subscriberId?: string; }) => Promise; getProductByIds: (ids: number[], options?: { loadMissing?: boolean; }) => Promise; /** * 只读查询商品列表 Store,不覆盖当前页面共享 catalog。 * 新版 OS 用 includeIds 为独立业务组件补拉指定商品。 */ queryProducts?: (params?: ProductListStoreQueryParams) => 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; /** * pubsub 全局扫码入口:搜索 + 业务分发;bridge 由 SalesSdk 注入 uiHosts / cart / action。 */ handleGlobalScanCode?: (code: string, bridge?: Record) => Promise<{ status: 'success' | 'pending' | 'cancelled' | 'failed'; kind?: string; reason?: string; meta?: { count?: number; }; }>; 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' | 'reloadCatalog'; 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; /** UnifiedBookingSales cacheId-scoped session state; optional for older OS builds. */ getSessionStateValue?: (key: string) => T | undefined; setSessionStateValue?: (key: string, value: unknown) => void; /** 清除当前 cacheId 的可恢复订单快照;旧版 OS 中可不存在。 */ clearSessionStorageOrderSnapshot?: () => void; resolveBestAddTimePlan?: (addTimeProducts: any[], targetMinutes: number) => ResolveBestAddTimePlanResult | null; /** * 将加时商品作为独立商品行绑定到已有 booking。 * * @example * await bookingTicket.addAddTimeProductToBooking?.({ booking, product, product_add_schedule_time: 30 }); */ addAddTimeProductToBooking?: (input: SalesSdkAddAddTimeProductInput) => Promise; /** * 批量将加时商品作为独立商品行绑定到已有 booking。 * * @example * await bookingTicket.addAddTimeProductsToBooking?.({ booking, products: [{ product }] }); */ addAddTimeProductsToBooking?: (input: SalesSdkAddAddTimeProductsInput) => Promise; } /** * Shared BaseSales surface used by solution-backed page components. * BookingTicket remains available as the legacy full-featured alias; this smaller * contract deliberately avoids making new page code depend on BookingTicket-only APIs. */ interface SalesSdkModuleHandle { name: string; effectsOn: (event: string, callback: (payload: any) => void) => () => void; getTempOrder: () => ScanOrderTempOrder | null; createSalesNotesMutationSnapshot?: () => SalesSdkSalesNotesMutationSnapshot; restoreSalesNotesMutationSnapshot?: (snapshot: SalesSdkSalesNotesMutationSnapshot) => void | Promise; setSalesNote?: (input: SalesSdkSetSalesNoteInput) => Promise; removeSalesNote?: (uuid: string) => Promise; getCart?: () => SalesSdkCartView; getProductCatalog?: () => SalesSdkProductCatalogView; addNewOrder: () => Promise; restoreOrder: () => Promise; loadProducts: (params?: ILoadProductsParams, options?: { callback?: (result: any) => void; subscriberId?: string; }) => Promise; /** * 只读查询单商品详情,不更新页面共享商品目录。 * UnifiedBookingSales 预约投影用它补齐 product_resource / capacity 等详情。 */ queryProductDetail?: (params: ILoadProductDetailParams, options?: { callback?: (result: any) => void; subscriberId?: string; }) => Promise; /** UnifiedBookingSales board OpenData loader used by standalone page skins. */ loadOpenData?: (params: { businessCode: string; channel: string; force?: boolean; }) => Promise>; getOpenData?: () => Record | null; addRetailProduct?: (params: { product?: ProductData | Record; productId?: number | string; quantity?: number; productLoadParams?: ILoadProductsParams; }) => Promise; /** UnifiedBookingSales:为商品详情预约选择准备短生命周期可用性上下文。 */ prepareAvailabilityProjection?: (params: { productIds: Array; dateRange?: { startDate: string; endDate: string; }; rawProducts?: ProductData[]; editingProductLineUids?: string[]; }) => Promise<{ contextId: string; }>; /** UnifiedBookingSales:按商品配置生成带 context/version 的候选时间段。 */ getProductTimeRanges?: (contextId: string, request: Record) => Promise; /** UnifiedBookingSales:查询 date/time/resource 等目标的实时可用性。 */ queryBookingAvailability?: (contextId: string, request: Record) => Promise<{ target: string; data: any[]; [key: string]: unknown; }>; /** UnifiedBookingSales:读取资源绑定协议,供 Flow 离开资源步骤前确认。 */ getProtocol?: (policyId: string | number) => Promise<{ data?: { title?: any; content?: string; [key: string]: unknown; }; [key: string]: unknown; }>; /** UnifiedBookingSales:商品详情关闭后释放可用性上下文。 */ releaseAvailabilityContext?: (contextId: string) => void; /** UnifiedBookingSales:强刷校验并把预约选择写入 tempOrder。 */ commitAvailabilitySelection?: (contextId: string, params: { mode: 'add' | 'edit'; productLineUid?: string; orderProduct: Record; candidate: Record; requirementSelections: Array<{ requirementGroupId?: string; formId?: number | string; resourceIds: Array; }>; partySize: number; bookingCount?: number; /** 新增商品行时随同一次 Order mutation 写入的 Sales Notes。 */ salesNotes?: SalesSdkSalesNoteDraftInput[]; /** 编辑预约的时间/SKU/客户价格上下文变化时,允许 OS 写入本次权威报价。 */ allowBookingReprice?: boolean; /** 自动报价变化后,允许 OS 按新基准重新应用仍有效的编辑态折扣。 */ allowBookingDiscountReapply?: boolean; }) => Promise<{ tempOrder: ScanOrderTempOrder | null; validation: { ok: boolean; conflicts?: Array<{ message?: string; code?: string; }>; }; [key: string]: unknown; }>; updateOrderProductQuantity: (params: UpdateOrderProductQuantityParams) => Promise; removeProductFromOrder: (identity: ScanOrderOrderProductIdentity) => Promise; getSummary: () => Promise; submitSalesOrder: (params?: SalesSdkSubmitSalesOrderParams) => Promise; setPromotionEvaluator?: (evaluator: any | null) => void; setGiftSelectResolver?: BookingTicketHandle['setGiftSelectResolver']; } /** * `cart.addProduct` 返回 `status='requiresDetail'` 时的 payload。 * UI 拿到后自行打开 Info2(或新版 saleDetail)详情弹窗,弹窗回调后再调 `cart.confirmDetail`。 */ 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; /** @internal 本次详情 Flow 的初始数量。 */ initialQuantity?: number; /** @internal 本次详情 Flow 的 Holder 初始选择。 */ initialHolderIds?: Array; /** @internal 登录被关闭后,仅把 Holder 必填延后到提交前校验。 */ deferHolderUntilSubmit?: boolean; /** @internal 同一次 addProductWithFlow 的过敏门禁去重 ID。 */ _allergyFlowId?: string; } /** * `requiresBookingEdit` 时 UI 打开资源 / 时间编辑抽屉所需 payload。 */ interface SalesSdkRequiresBookingEditPayload { cacheItem: any; customerId?: number | string; date: string | null; /** @internal 仅供加车前 Selection Flow 初始化 Holder 草稿。 */ initialHolderIds?: Array; /** @internal 登录被关闭后,仅把 Holder 必填延后到提交前校验。 */ deferHolderUntilSubmit?: boolean; /** * 已加车行「编辑资源」:强制打开抽屉,忽略 cacheItem.autoClose。 * 加车链路(requiresBookingEdit)不传此字段;autoClose=true 时自动 confirm 跳过抽屉。 */ forceOpen?: boolean; /** * 购物车跨日编辑:先开抽屉,再在 Host 内异步展开 `_extend.items`。 * 加车 / requiresBookingEdit 链路不传此字段。 */ deferMultiDayExpand?: boolean; /** 跨日判定回退(与 editCartLineBooking 一致) */ sourceProduct?: Record | null; /** 加车链路 catalog 商品,供 Host 补拉报价 */ catalogProduct?: Record | null; /** @internal 同一次 addProductWithFlow 的过敏门禁去重 ID。 */ _allergyFlowId?: string; } /** 加车前预约 Selection Flow 的判别式结果。 */ type SalesSdkOpenBookingSelectionResult = { status: 'confirmed'; cacheItem: any; } | { status: 'committed'; products: ScanOrderOrderProduct[]; } | { status: 'cancelled'; } | { status: 'not_applicable'; }; /** 已加车预约行进入 Selection Flow 编辑时所需的稳定输入。 */ interface SalesSdkBookingSelectionEditPayload { /** Flow 初始化所需的完整 cacheItem,包含当前 SKU、日期、资源和 Holder。 */ cacheItem: any; /** 当前目录商品;用于解析商品级步骤和重新查询 Availability。 */ catalogProduct: ProductData | Record; /** tempOrder 中被编辑的真实商品行。 */ orderProduct: ScanOrderOrderProduct; /** UBS edit 的真实订单行 UID,必须同时传给 prepare 和 commit。 */ productLineUid: string; /** 合并展示行内全部真实 UID;用于同一次编辑准备完整 Availability 上下文。 */ productLineUids?: string[]; /** 编辑数量由调用方按真实预约行同步时,Host 只返回 Flow 结果,不直接提交单行。 */ deferCommit?: boolean; /** 当前商品行关联的 booking,用于编辑态日期、时间和资源回填。 */ booking: Record; customerId?: number | string; date: string | null; } /** 已加车预约行 Selection Flow 的判别式结果。 */ type SalesSdkOpenBookingSelectionEditResult = { status: 'committed'; products: ScanOrderOrderProduct[]; } | { status: 'confirmed'; cacheItem: any; } | { status: 'cancelled'; } | { status: 'not_applicable'; }; /** * `cart.addProduct` 判别式返回值。 * * - `added`:两关均通过,已写入 OS tempOrder。 * - `requiresDetail`:需打开规格 / SKU 弹窗,callback 后调 `cart.confirmDetail`。 * - `requiresBookingEdit`:规格已就绪,需打开资源 / 时间抽屉,确认后调 `cart.confirmBookingEdit`。 */ type SalesSdkAddProductResult = { status: 'added'; products: ScanOrderOrderProduct[]; } | { status: 'cancelled'; } | { status: 'requiresDetail'; payload: SalesSdkAddProductRequiresDetailPayload; } | { status: 'requiresBookingEdit'; payload: SalesSdkRequiresBookingEditPayload; }; /** * `cart.confirmDetail` 判别式返回值(规格选完后可能仍需资源编辑)。 */ type SalesSdkConfirmDetailResult = { status: 'added'; products: ScanOrderOrderProduct[]; } | { status: 'cancelled'; } | { status: 'requiresBookingEdit'; payload: SalesSdkRequiresBookingEditPayload; }; /** * `cart.confirmDetail` 输入:UI 拿到 Info2 callback 的 `(e, extension_type, detail)` 后回传。 */ /** 跨日日期弹窗确认结果(对齐 ticketBooking MultiDayTimeSelectModal callback) */ interface SalesSdkMultiDaySelectResult { startDate: string; endDate: string; duration: number; /** 跨日预约开始营业时间;BigSale 默认来自 getData('operating_day_boundary').time。 */ startTime?: string; /** 跨日预约结束营业时间;默认与 startTime 一致。 */ endTime?: string; } /** `openMultiDaySelect` Host 入参 */ interface SalesSdkOpenMultiDaySelectPayload { productName?: string; /** 日历默认锚点日 YYYY-MM-DD */ defaultStartDate?: string | null; } /** `openHolderSelect` Host 入参 */ interface SalesSdkOpenHolderSelectPayload { cacheItem: any; productSource?: Record | null; maxSelectedCount?: number; customerId?: number | string; /** 仅用于弹窗默认选中,不会在确认前写入商品 Holder */ initialHolderIds?: Array; } /** 「创建并添加一次性商品」弹窗回传数据(与 booking CustomiseItemModal 字段对齐) */ interface SalesSdkCustomProductInput { productName: string; vouchersApplicable: boolean; priceType: 'sell' | 'deduction'; priceValue: string; quantity: number; } /** * 添加加时商品输入。 * * @example * await cart.addAddTimeProduct({ * booking, * product, * num: 1, * price: '10.00', * product_add_schedule_time: 30, * }); */ interface SalesSdkAddAddTimeProductInput { /** 当前已有 booking,必须带 metadata.unique_identification_number。 */ booking: SalesSdkCartBookingView | Record; /** 加时商品原始数据。 */ product: ProductData & Record; /** 本次添加数量,缺省为 1。 */ num?: number; /** 本次加时商品价格。 */ price?: number | string; /** 加时时长,单位分钟;最终写入商品行 metadata.product_add_schedule_time。 */ product_add_schedule_time?: number; /** 本次加时方案实际覆盖分钟数,用于同步更新 booking 结束时间与 duration。 */ coveredMinutes?: number; /** 直接写入 booking 的时间补丁,restart 场景使用。 */ bookingPatch?: { start_date?: string; start_time?: string; end_date?: string; end_time?: string; duration?: number; }; } /** * 批量添加加时商品的单行输入。 * * @example * const line: SalesSdkAddAddTimeProductLineInput = { product, product_add_schedule_time: 30 }; */ type SalesSdkAddAddTimeProductLineInput = Omit; /** * 批量添加加时商品输入。 * * @example * await cart.addAddTimeProducts({ * booking, * products: [{ product, product_add_schedule_time: 30 }], * }); */ interface SalesSdkAddAddTimeProductsInput { /** 当前已有 booking,必须带 metadata.unique_identification_number。 */ booking: SalesSdkCartBookingView | Record; /** 一次性写入购物车的加时商品行。 */ products: SalesSdkAddAddTimeProductLineInput[]; /** 本次加时方案实际覆盖分钟数,用于同步更新 booking 结束时间与 duration。 */ coveredMinutes?: number; /** 直接写入 booking 的时间补丁,restart 场景使用。 */ bookingPatch?: SalesSdkAddAddTimeProductInput['bookingPatch']; } interface SalesSdkConfirmDetailInput { item: ProductData | Record; detailResult: { e: any; extension_type?: any; detail?: any; /** @internal UnifiedBookingSales Host 已完成预约提交时,阻止外层重复 confirmDetail。 */ _committedProducts?: ScanOrderOrderProduct[]; /** @internal 新版规格 Host 已处理过敏门禁后继续透传同一 flow。 */ _allergyFlowId?: string; }; options?: { quantity?: number; notShowToast?: boolean; /** Holder 弹窗的初始选中项;最终仍以用户确认结果为准 */ initialHolderIds?: Array; /** 跨日弹窗确认后的区间(规格弹窗之后注入) */ multiDayRange?: SalesSdkMultiDaySelectResult; /** @internal 当前确认延后 Holder 到提交前补录。 */ deferHolderUntilSubmit?: boolean; }; } /** * `cart.confirmBookingEdit` 输入:资源 / 时间抽屉确认后的 cacheItem。 */ interface SalesSdkConfirmBookingEditInput { cacheItem: any; options?: { quantity?: number; note?: string; /** @internal 同一次 addProductWithFlow 的过敏门禁去重 ID。 */ _allergyFlowId?: string; }; } /** * 页面级 UI Host:由 SalesSdkProvider 注入,供 `cart.addProductWithFlow` 编排弹窗链。 * * 实现方在页面内可用 Hook 组装后传入;Provider 只持有函数引用,不 import 具体弹窗组件。 */ interface SalesSdkUIHosts { /** * `requiresDetail` 时打开规格 / SKU / 称重弹窗。 * 用户取消时返回 `null`。 */ openProductDetail?: (payload: SalesSdkAddProductRequiresDetailPayload) => Promise; /** * `requiresBookingEdit` 时打开资源 / 时间编辑抽屉。 * 用户取消时返回 `null`;确认时返回编辑后的 cacheItem。 */ openBookingEdit?: (payload: SalesSdkRequiresBookingEditPayload) => Promise; /** * `requiresBookingEdit` 进入旧 enrich/抽屉前尝试提交前 Selection Flow。 * not_applicable 才回到旧抽屉;cancelled 表示用户已取消本次加车。 */ openBookingSelectionFlow?: (payload: SalesSdkRequiresBookingEditPayload) => Promise; /** * 已加车预约行使用同一 SkuDetailModal Selection Flow 编辑。 * Host 内通过 UnifiedBookingSales `mode: 'edit'` 原位替换当前商品行。 */ openBookingSelectionEdit?: (payload: SalesSdkBookingSelectionEditPayload) => Promise; /** * 规格弹窗确认后、跨日商品须选日期区间(对齐 ticketBooking `MultiDayTimeSelectModal`)。 */ openMultiDaySelect?: (payload: SalesSdkOpenMultiDaySelectPayload) => Promise; /** * 加车前 Holder 选择(对齐 ticketBooking `selectHolderModal`)。 */ openHolderSelect?: (payload: SalesSdkOpenHolderSelectPayload) => Promise; /** * Kiosk holder-required 商品加购前的客户身份验证。 * 成功时返回 customer 快照;取消时返回 null。 */ openCustomerAuth?: (payload: SalesSdkOpenCustomerAuthPayload) => Promise; /** 当前商品命中 ALLERGY_CHECK 时打开受控过敏选择弹窗。 */ openAllergySelection?: (payload: SalesSdkOpenAllergySelectionPayload) => Promise; } /** * `cart.addProductWithFlow` 返回值。 * * - `added`:完整链路结束并已写入 OS。 * - `cancelled`:用户在 Host 弹窗/抽屉中取消。 * - `requiresDetail` / `requiresBookingEdit`:决策命中但对应 Host 未配置,交由调用方处理。 */ /** 已加车行「编辑资源」结果 */ type SalesSdkEditCartLineBookingResult = { status: 'updated'; products: ScanOrderOrderProduct[]; } | { status: 'cancelled'; }; /** 已加车普通商品行「编辑 SKU」结果 */ type SalesSdkEditCartLineProductResult = SalesSdkEditCartLineBookingResult | { status: 'removed'; products?: ScanOrderOrderProduct[]; }; /** 购物车行点击编辑(自动分流 booking / 普通商品) */ type SalesSdkEditCartLineResult = SalesSdkEditCartLineBookingResult | Extract; type SalesSdkAddProductWithFlowResult = { status: 'added'; products: ScanOrderOrderProduct[]; } | { status: 'cancelled'; } | { status: 'requiresDetail'; payload: SalesSdkAddProductRequiresDetailPayload; } | { status: 'requiresBookingEdit'; payload: SalesSdkRequiresBookingEditPayload; }; /** `cart.addProductWithFlow` 调用参数(含防抖 / Toast) */ interface SalesSdkAddProductWithFlowOptions { type?: 'select' | 'detail'; quantity?: number; note?: string; /** Holder 弹窗的初始选中项;不会跳过 Holder 确认 */ initialHolderIds?: Array; /** 连点合并窗口毫秒,默认 200(首击立即加车,窗口内后续点击合并追加);0 表示每次独立立即加车 */ debounceMs?: number; /** 为 true 时不展示加车 Toast */ notShowToast?: boolean; /** * 仅供页面区分“直加已由 SDK 安排即时 Toast”与“详情确认后再加车”。 * 此回调不会透传给 OS。 * @internal */ onDirectAddToastScheduled?: () => void; /** * 防抖层已决策的 cacheItem;传入后 cart.addProduct 跳过第二次 decideAddProduct。 * @internal 由 createAddProductWithFlowDebounced 注入,业务调用方勿传。 */ preparedCacheItem?: any; /** * 当前商品 Flow 已关闭登录;详情/加购阶段延后 Holder,提交前仍按原配置校验。 * @internal 由 Kiosk 模板登录门禁注入,业务调用方勿传。 */ deferHolderUntilSubmit?: boolean; /** @internal 每次 addProductWithFlow 生成,供多个最终提交点幂等复用。 */ _allergyFlowId?: string; } /** * 资源不可用原因(与 OS `BookingContext.ResourceUnavailableReason` 一比一对齐)。 * * i18n 文案不进 OS,UI 拿到 reason + params 后自行渲染,对照表见 `salesSdk/docs.md`。 */ type SalesSdkResourceUnavailableReason = 'no_product' | 'time_out_of_range' | 'count_out_of_range' | 'resource_unbound'; interface SalesSdkResourceError { reason: SalesSdkResourceUnavailableReason; params: { labelText?: string; startText?: string; endText?: string; isCrossDay?: boolean; }; } /** SalesSdkProvider 状态机 */ type SalesSdkStatus = 'idle' | 'loading' | 'ready' | 'error'; /** Selects the OS solution instantiated by SalesSdkProvider. */ type SalesSdkSolution = 'bookingTicket' | 'unifiedBookingSales'; /** Controls missing-module behavior for UnifiedBookingSales page skins. */ type SalesSdkModuleAccess = 'ensure' | 'existing'; /** Declares whether SalesSdk or the embedding surface owns order bootstrap. */ type SalesSdkLifecycleOwner = 'provider' | 'external'; /** Product catalog bootstrap state used to avoid exposing stale preloaded catalogs. */ type SalesSdkCatalogState = 'pending' | 'ready' | 'empty'; interface SalesSdkOpenCustomerAuthPayload { reason: 'holder_required'; product: ProductData & Record; currentCustomerId?: number | string | null; } interface SalesSdkCustomerAuthResult { customer: ICustomer; raw?: unknown; } interface SalesSdkProviderProps { /** 多实例区分;与 ticketBooking 共享同一 osKey 时自动复用其 BookingTicket 实例 */ osKey: string; /** Defaults to bookingTicket. Unified retail pages use unifiedBookingSales. */ solution?: SalesSdkSolution; /** * Only applies to `solution="unifiedBookingSales"`: `ensure` registers a missing module; * `existing` only reuses a caller-owned module and throws when it cannot be found. * BookingTicket keeps its original registration behavior. Defaults to `ensure`. */ moduleAccess?: SalesSdkModuleAccess; /** * `provider` waits for the configured restore/loadDetail bootstrap before ready; * `external` leaves that lifecycle to the embedding surface. Defaults to `provider`. */ lifecycleOwner?: SalesSdkLifecycleOwner; /** 透传给 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; /** * Kiosk 模板专用:用户关闭商品 Holder 登录后继续详情/加购,并延后到提交前补录。 * @internal 由 BigSale 根据 resolvedTemplate 注入。 */ deferHolderAuthOnClose?: boolean; /** * 是否启用 SalesSdk 内置 CustomerAuthHost;H5 应使用宿主 pisell1.login。 * @internal 由 BigSale 根据模板和运行平台注入。 */ enableCustomerAuthHost?: boolean; /** * 运行时商品行合并策略;返回 false 时快速加购不做防抖合并,OS 始终新增商品行, * 预约商品也保持独立展示。默认合并。 */ shouldMergeProductLines?: () => boolean; /** 业务自定义 rules hooks,否则用 OrderModule 默认 */ rulesHooks?: RulesParamsHooks; /** * 给定 orderId 且 autoBootstrap !== false 时,挂载后自动 loadSalesDetail; * 不传则不会自动 createNew,避免空 osKey 误建 tempOrder。 */ orderId?: number; /** 已查询到的销售详情对象,用于详情首屏免二次查询。 */ preloadedSalesDetail?: Record; /** 默认 true */ autoBootstrap?: boolean; /** * 挂载后是否先 restore 清空 OS 残留 tempOrder / localStorage(与 demo reset 一致), * 再 recalcSummary;有 orderId 时在 restore 之后 loadDetail。默认 true。 * 与 ticketBooking 共享 osKey 且需保留进行中订单时可设为 false。 */ autoRestoreOnInit?: boolean; /** * PaymentModal 全额支付完成后是否自动 restoreOrder。 * 默认 true,用于卖票页/新建单在结账完成后清空临时订单;订单详情页可设为 false 保留详情态。 */ restoreOrderAfterPaidCheckout?: boolean; /** * Provider 卸载时是否销毁当前 BookingTicket 上下文。 * 默认 false,避免误销毁与 ticketBooking / 预加载弹窗共享的 osKey;短生命周期详情实例可显式开启。 */ destroyOnUnmount?: boolean; /** * 页面级弹窗 / 抽屉 Host(规格弹窗、资源编辑等)。 * 未传字段时 Provider 使用实例级内置 Host;传入字段覆盖对应内置 Host。 */ uiHosts?: Partial; /** * 过敏声明保存后的员工确认扩展点。当前默认直接返回 true;返回 false 或抛错时不写购物车。 */ awaitAllergyConfirmation?: (receipt: SalesSdkAllergyReceipt) => Promise; /** 内置普通商品规格弹窗的默认展示配置。 */ normalProductDetailConfig?: ConfigProps; /** * 是否仅在购物车展示层合并相同预约行,默认 true,保持历史行为。 * 设为 false 时不会修改 OS 中的预约数据,只让每条预约独立展示、独立编辑。 */ mergeBookingLinesForDisplay?: boolean; /** * LCE 预约商品详情 Host(`pisell1.handleOpenProductModal`)。 * Provider 会与实例级 normalHost 组合为 `openProductDetail`。 */ legacyOpenProductDetail?: CreateLegacyOpenProductDetailHostDeps; /** 是否开启 tempOrder 主副屏同步,默认关闭,由业务入口按需启用。 */ enableTempOrderSync?: boolean; /** * 是否将 tempOrder 写入 localStorage(OS OrderModule.persistTempOrder)。 * SalesSdk 默认 false:订单仅驻留内存,避免污染 ticketBooking 等同 osKey 的本地缓存。 * ticketBooking 等 legacy 入口不传时 OS 侧仍为 true。 */ enableTempOrderPersist?: boolean; /** * 已展示 sales detail 时,是否允许后台订单变更同步到当前详情。 * 默认 false;下单/编辑入口避免覆盖当前编辑态,纯展示详情可按需开启。 */ syncDetailOnOrderChange?: boolean; /** * 页面/Tab 是否处于激活态(与 ticketBooking isActive 对齐)。 * 为 true 时挂载或重新激活原生外设扫码桥接;默认 true。 */ isActive?: boolean; /** * 非激活态 restore 清空客户时保留当前商品目录,不按匿名客户重拉。 * 仅适用于常驻挂载、下次打开会重新 hydrate 订单的弹窗。 */ preserveProductCatalogOnInactiveCustomerClear?: boolean; /** * 是否订阅 app.pubsub 原生扫码(`nativeScanResult`)并调用 OS handleGlobalScanCode。 * 默认 false,业务入口(如 BookingPos)显式开启。 */ enableGlobalScanListener?: boolean; /** 券码通过全局扫码成功时展示的 Toast 文案 key;不传则保持原行为。 */ promotionScanSuccessMessageKey?: string; /** 全局扫码使优惠区项目数增加时展示的统一 Toast 文案 key。 */ promotionItemIncreaseMessageKey?: string; /** * 每次 loadProducts 都会合并的固定参数(如 dine_in 弹窗固定餐牌 menu_list_ids), * 优先级高于 boardConfig 推导值,低于单次 load(params) 显式传入。 */ defaultProductLoadParams?: ILoadProductsParams; children: React.ReactNode; } /** UI 视图:购物车行(多源 enrichment 输出,仅活在 React 内存) */ interface SalesSdkCartItemView extends ScanOrderOrderProduct { /** 给 OS removeProductFromOrder/updateOrderProduct 的稳定 identity */ _identity: ScanOrderOrderProductIdentity; _extend: { booking: ISalesBooking | null; /** 合并 booking 展示行展开后的真实 booking/product 来源,仅 UI 层使用 */ mergedBookingSources?: SalesSdkMergedBookingSource[]; /** 标记当前商品行来自合并 booking 展示代表行,仅 UI 层使用 */ isMergedBookingLine?: boolean; /** 合并 booking 展示数量,仅 UI 层使用 */ displayQuantity?: number; }; title: string; cover: string; product_cover: string; variant_title: string; bundle_titles: string[]; option_titles: string[]; product_option_string?: string; note: string; duration?: number; product_resource?: any; capacity?: any; start_at: string | null; end_at: string | null; resource_id?: number | string | null; /** 主商品促销赠品行(CartSkuCard Gift 组件) */ giftData?: SalesSdkCartGiftData | null; _promotionTags?: SalesSdkPromotionTag[]; promotions?: SalesSdkPromotionTag[]; _isGiftPromotion?: boolean; _giftInfo?: Record; } /** Holder 展示字段(Cart enrich 注入,不写 OS) */ 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; } /** 合并 booking 展示行对应的真实 OS 行,写操作需展开到这些来源。 */ interface SalesSdkMergedBookingSource { booking: ISalesBooking; product: SalesSdkCartItemView; identity: ScanOrderOrderProductIdentity; } /** UI 视图:booking 行(平铺,附带关联商品 + Holder 展示 enrich) */ interface SalesSdkCartBookingView extends ISalesBooking, SalesSdkHolderDisplayFields { _extend: { product: SalesSdkCartItemView | null; /** 可合并预约组的稳定 React 展示身份;仅 UI 层使用。 */ displayKey?: string; /** 当前 booking 绑定的加时商品行,已从 cart.items 中剔除。 */ addTimeProduct?: SalesSdkCartItemView[]; /** OS BookingTicket 计算出的旧 ticketBooking 加时入口可用性。 */ canAddTime?: boolean; /** 合并 booking 展示行展开后的真实 booking/product 来源,仅 UI 层使用 */ mergedFrom?: SalesSdkMergedBookingSource[]; /** 标记当前 booking 为合并展示代表行,仅 UI 层使用 */ isMergedBookingLine?: boolean; /** 合并 booking 展示数量,仅 UI 层使用 */ displayQuantity?: number; /** 合并 booking 展示行聚合出的 holder id,仅 UI 层使用 */ mergedHolderIds?: Array; }; } /** UI 统一购物车行:预约行仍通过 `_extend.product` 承载关联商品。 */ type SalesSdkCartDisplayLine = SalesSdkCartBookingView | SalesSdkCartItemView; /** UI 视图:客户卡片(selected → tempOrder fallback) */ interface SalesSdkCustomerView { id: number | string; name: string; phone?: string; email?: string; country_calling_code?: string; cover?: string; isWalkIn: boolean; } /** UI 视图:BookingTicketImpl.getCart 输出(与 OS buildCartView 结构一致) */ interface SalesSdkCartView { bookings: SalesSdkCartBookingView[]; items: ScanOrderOrderProduct[]; lines?: Array; } /** UI 视图:BookingTicketImpl.getProductCatalog 输出 */ interface SalesSdkProductCatalogView { products: ProductData[]; } interface SalesSdkContextValue { /** The solution selected by SalesSdkProvider. */ solution: SalesSdkSolution; /** Generic BaseSales-compatible module for new page-level components. */ salesModule: SalesSdkModuleHandle | null; /** Legacy alias retained for BookingTicket-specific consumers. */ bookingTicket: BookingTicketHandle | null; osKey: string; status: SalesSdkStatus; error: Error | null; isReady: boolean; /** 全局扫码使优惠区项目数增加的递增标记;供当前页面响应扫码结果。 */ promotionScanItemIncreaseRevision: number; /** OpenData-backed catalog resolution state. Pending/empty catalogs must not reload stale data. */ catalogState: SalesSdkCatalogState; 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; /** * 由 SaleSDK 只读合并 payments / refunds 得到的支付时间线。 * 不会回写或修改 tempOrder 中的原始数据。 */ paymentTimeline: SalesSdkPaymentTimelineGroup[]; /** * 提交普通退款或补记已成功的 EFTPOS 退款,并刷新销售详情。 */ refundPayments: (payments: SalesSdkRefundPaymentInput[], options?: SalesSdkRefundPaymentsOptions) => Promise; loadDetail: (orderIdOrParams: number | SalesSdkLoadSalesDetailParams) => Promise; /** * 强制走远端拉取销售详情(跳过本地 sqlite 缓存),参数形态与 OS `loadSalesDetail` 一致。 */ loadDetailByRemote: (orderId: number) => Promise; /** * 刷新销售详情,成功后同步顶层 `tempOrder` / `salesDetail`(不翻 loading 态,避免闪烁)。 * Provider 内部已用 useMemoizedFn 包装,引用稳定。 * * - 默认(不传 `preloadedSalesDetail`):基于当前 tempOrder.order_id 从远端刷新(转发 OS * `refreshSalesDetail({ forceRemote })`),调用前须已 loadDetail / 存在 order_id。 * - 传 `preloadedSalesDetail`:用现成的完整详情对象**就地 hydrate**(转发 OS * `loadSalesDetail({ orderId, preloadedSalesDetail })`),**跳过远端请求**。 * 用于「实时推送已带完整详情」的场景(如全局搜索实时更新),避免冗余按 id 拉取。 */ refreshSalesDetail: (params?: { forceRemote?: boolean; preloadedSalesDetail?: Record; }) => Promise; /** * loadProducts 前确保 boardConfig 已就绪;与 bootstrap `initBookingContext` 同参, * 但不强制拉 resources。bootstrap 已完成且 config 命中时会直接跳过。 */ ensureBookingConfigForLoad: () => Promise; /** loadProducts 智能定价 channel,优先 engine getData('channel') */ productLoadChannel?: string; /** 透传 Provider businessCode,供 strategy_context.business_code */ businessCode?: string; /** 挂载时传入的固定 loadProducts 参数(如弹窗固定餐牌 menu_list_ids)。 */ defaultProductLoadParams?: ILoadProductsParams; /** 与 bootstrap initBookingContext 对齐的 board config 查询参数。 */ bookingConfigParams?: Record; 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; /** * 子预约状态机转移,详见 BookingTicketHandle.transitionChildBooking。 * * @example * await sales.transitionChildBooking({ schedule_event_id: 301, action: 'complete' }); */ transitionChildBooking: (params: TransitionChildBookingParams) => Promise; /** * 写入订单备注(tempOrder.note,字符串)。 * 直接转发到 OrderModule.updateTempOrderNote(同步赋值),写完触发内部 refreshRootSnapshot * 让 React 立即拿到新的 tempOrder.note。返回最终写入的字符串。 * Provider 内部已用 useMemoizedFn 包装,引用稳定,可直接放入 useEffect deps。 */ setOrderNote: (note: string) => string; /** 更新商品级 Sales Note,并立即同步顶层 tempOrder 快照。 */ setSalesNote: (input: SalesSdkSetSalesNoteInput) => Promise; /** 按业务 UUID 删除 Sales Note,并立即同步顶层 tempOrder 快照。 */ removeSalesNote: (uuid: string) => Promise; /** 捕获 Sales Notes 及其 loaded/dirty 状态,用于持久化失败时精确回滚。 */ createSalesNotesMutationSnapshot: () => SalesSdkSalesNotesMutationSnapshot; /** 仅恢复 Sales Notes 快照,不覆盖订单中的其它并发编辑。 */ restoreSalesNotesMutationSnapshot: (snapshot: SalesSdkSalesNotesMutationSnapshot) => Promise; /** * 写入整单优惠金额(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 实际使用的 moduleKey; * UnifiedBookingSales 外部实例会保留其原始模块名,不再拼接 BookingTicket 前缀。 * 调用方无需感知模块命名规则。 * * 返回值为底层 action callback 的回传结果,便于消费方在结账完成后做后续动作(如刷新详情)。 * 当宿主环境未注入 `appHelper.utils.action` 时直接抛错。 */ openCheckout: (options?: SalesSdkSubmitOptions) => Promise; /** 仅执行与提交/收款一致的 Holder 必填确认,不提交订单。 */ validateHoldersBeforePayment: () => Promise; /** * 提交销售订单并在成功后从远端刷新详情(与 cart.submit 同 Holder 确认流程)。 * 用户关闭 Holder 确认弹窗时抛 `HOLDER_CONFIRM_CANCELLED`,不触发 submit / 刷新。 * * @example * await sales.submitAndRefresh(); * await sales.submitAndRefresh({ paymentStatus: 'pending' }); */ submitAndRefresh: (params?: SalesSdkSubmitSalesOrderParams, options?: SalesSdkSubmitOptions) => Promise; /** * 读取当前订单折扣列表(转发 OS `BaseSales.getDiscountList`)。 * 返回克隆数组,避免消费方就地修改污染 OS store。 */ getDiscountList: () => any[]; /** * 绑定折扣卡 Holder 后,同步写入 OS Discount 模块 discountList / originalDiscountList。 */ patchDiscountCardHolderOnStore: (targetDiscount: any, holderId: number | string) => Promise; /** * 扫描优惠码(转发 OS `BaseSales.scanPromotionCode`)。 * 可立即应用或已加入 Promotion 后刷新顶层快照;返回轻量结果 * (不含 discountList,需再调 `getDiscountList`)。 */ scanPromotionCode: (code: string, customerId?: number) => Promise; /** * 勾选/取消订单折扣(转发 OS `BaseSales.setDiscountSelected`)。 * OS 内部会重算 rules、行价与 summary;完成后同步顶层快照。 */ setDiscountSelected: (params: { discountId: number; isSelected: boolean; }) => Promise; resolveBestAddTimePlan: (addTimeProducts: any[], targetMinutes: number) => ResolveBestAddTimePlanResult | null; } interface SalesSdkProductsContextValue { products: ProductData[]; loading: boolean; error: Error | null; load: (params?: ILoadProductsParams, options?: { subscriberId?: string; silent?: boolean; /** * 客户变更 reload 专用:以事件 snapshot 为准,null 表示显式清除 customer_id。 * 未传时回退 tempOrder.customer_id。 */ customerIdOverride?: number | null; }) => Promise; /** * 获取加时商品,并转换为可直接写入订单/购物车的商品行结构。 * * @example * const orderProducts = await products.getAddTimeProducts({ schedule_date: '2026-06-16' }); */ getAddTimeProducts: (params?: ILoadProductsParams) => Promise; loadDetail: (params: ILoadProductDetailParams) => Promise; findByIds: (ids: number[], options?: { /** 缓存未命中时由 OS 按当前订单上下文补拉。 */loadMissing?: boolean; /** 默认同步 Product Provider catalog;独立业务补拉应传 false。 */ refreshCatalog?: boolean; }) => Promise; /** * 通过 OS ProductListStore 只读查询,不修改 Product Provider 当前 catalog。 * 旧版 OS 不支持时该能力不存在。 */ queryProducts?: (params?: ProductListStoreQueryParams) => 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 */ interface SalesSdkCustomerSearchOptions { /** 是否展示 Search 按钮 loading,默认 true */ showLoading?: boolean; /** 关联表单 ID */ relation_form_id?: number | string; } 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; } interface SalesSdkSummaryContextValue { summary: ScanOrderSummary | null; } interface SalesSdkCartContextValue { /** 直读 OS tempOrder 原貌(提交契约,UI 不污染) */ tempOrder: ScanOrderTempOrder | null; /** 服务端 sales 详情(编辑态有,新建态为 null) */ salesDetail: ISalesDetail | null; discountList: any[]; /** * 上一次 OS applyPromotion 输出的未满足促销提示。 * UI(购物车底部 alert)渲染源;客户切换或购物车增减后自动刷新。 */ unfulfilledPromotions: SalesSdkUnfulfilledPromotion[]; /** 上一次 OS applyPromotion 输出的赠品操作 diff(debug 用,UI 一般不消费) */ lastGiftActions: SalesSdkLastGiftActions | null; /** 平铺 booking + 关联商品(透传 OS) */ bookings: SalesSdkCartBookingView[]; /** 未关联 booking 的商品行(透传 OS tempOrder.products) */ items: SalesSdkCartItemView[]; /** 以 product 顺序为真源、完成预约合并且最新操作在前的统一展示行。 */ displayLines?: SalesSdkCartDisplayLine[]; /** 当前购物车是否启用普通商品行与预约展示行合并。 */ productLineMergeEnabled?: boolean; /** 快速加购后的 cart/summary 一致性刷新尚未完成;外部副作用应等待。 */ consistencyPending?: boolean; /** 支付/保存边界:立即提交待合并加购,并等待购物车与金额汇总稳定。 */ waitForCartConsistency: () => Promise; /** 从 OS 重新读取购物车 UI 快照,不触发 summary 重算。 */ refreshCartSnapshot: () => void; /** 实际写购物车前执行 item_rules 过敏门禁;无匹配规则时直接返回 true。 */ ensureAllergyBeforeCommit: (input: SalesSdkEnsureAllergyBeforeCommitInput) => Promise; /** 执行过敏门禁并返回可随最终商品 mutation 交给 OS 的 Note draft。 */ prepareAllergyBeforeCommit: (input: SalesSdkEnsureAllergyBeforeCommitInput) => Promise; /** * 加车主入口:走 OS `decideAddProduct` 两阶段决策 → `added` | `requiresDetail` | `requiresBookingEdit`。 */ addProduct: (product: ProductData & Record, options?: { type?: 'select' | 'detail'; quantity?: number; note?: string; }) => Promise; /** * 将已完成规格、资源、预约与 Holder 选择的 cacheItem 直接加车。 * 调用方必须传入 SalesSDK Host 链路产出的完整 cacheItem;该入口不会重复打开选择弹窗。 */ addPreparedProduct: (cacheItem: ProductData & Record, options?: { quantity?: number; }) => Promise<{ status: 'added'; products: ScanOrderOrderProduct[]; }>; /** 规格弹窗 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; /** * 添加加时商品:绑定到已有 booking,不新增 booking,不走预约商品弹窗链。 * * @example * await addAddTimeProduct({ booking, product, product_add_schedule_time: 30 }); */ addAddTimeProduct: (input: SalesSdkAddAddTimeProductInput) => Promise; /** * 批量添加加时商品:绑定到已有 booking,并在 OS 层只重算一次购物车。 * * @example * await addAddTimeProducts({ booking, products: [{ product, product_add_schedule_time: 30 }] }); */ addAddTimeProducts: (input: SalesSdkAddAddTimeProductsInput) => Promise; removeItem: (item: SalesSdkCartItemView | SalesSdkCartBookingView) => Promise; updateItem: (params: UpdateOrderProductParams) => Promise; /** * 批量更新购物车行,底层只触发一次促销、折扣、summary 和 React 快照同步。 * * @example * await cart.updateItems([{ product_id: 1, product_variant_id: 0, updates: {} }]); */ updateItems: (paramsList: UpdateOrderProductParams[], options?: { /** 调用方会立即主动刷新 summary 时可跳过,避免一次预览内重复重算。 */skipSummarySync?: boolean; }) => 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; /** 按当前商品、客户和可用券重新计算最优折扣组合。 */ bestDiscount: () => Promise<{ productList?: Record[]; discountList?: any[]; }>; /** 取消当前订单中可操作的已选折扣卡 / 商品券;历史锁定券保持不变。 */ clearDiscountSelections: () => Promise; /** * 按当前订单客户加载最新可用折扣 / 折扣卡配置。 * 主要用于促销弹窗打开前刷新客户权益;OS 缺能力时返回当前缓存列表。 */ loadDiscountConfig: (params?: { customerId?: number; action?: 'create' | 'edit'; }) => Promise<{ productList?: Record[]; discountList?: any[]; }>; scanCode: (code: string, customerId?: number) => Promise; submit: (params?: SalesSdkSubmitSalesOrderParams, options?: SalesSdkSubmitOptions) => 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; /** 点击购物车预约行的“未分配”,打开旧 Holder 选择弹窗并写回当前行。 */ assignHolderToBookingLine?: (target: SalesSdkCartBookingView) => Promise; /** * 主商品赠品行点击:打开 legacy 赠品弹窗并重写该策略赠品。 * * @example * await selectGiftForItem(mainCartLine); */ selectGiftForItem: (target: SalesSdkCartItemView | SalesSdkCartBookingView) => Promise; } //#endregion export { BaseSalesScanCodeResult, BookingTicketHandle, BookingTransitionAction, ICustomer, ICustomerListResponse, IGetCustomerListParams, ILoadProductDetailParams, ILoadProductsParams, IPaginationInfo, ISalesBooking, ISalesDetail, ISalesDetailCustomer, ISalesDetailHeader, ISalesPayment, ISalesSurcharge, IScanResult, ProductData, ProductListStoreQueryParams, RulesParamsHooks, SalesSdkAddAddTimeProductInput, SalesSdkAddAddTimeProductLineInput, SalesSdkAddAddTimeProductsInput, SalesSdkAddProductRequiresDetailPayload, SalesSdkAddProductResult, SalesSdkAddProductWithFlowOptions, SalesSdkAddProductWithFlowResult, SalesSdkAllergyDataSourceItem, SalesSdkAllergyLocalizedText, SalesSdkAllergyPreparationResult, SalesSdkAllergyReceipt, SalesSdkAllergyRequirement, SalesSdkAllergySelectionResult, SalesSdkBookingSelectionEditPayload, SalesSdkCartBookingView, SalesSdkCartContextValue, SalesSdkCartDisplayLine, SalesSdkCartItemView, SalesSdkConfirmBookingEditInput, SalesSdkConfirmDetailInput, SalesSdkConfirmDetailResult, SalesSdkContextValue, SalesSdkCustomProductInput, SalesSdkCustomerAuthResult, SalesSdkCustomerContextValue, SalesSdkCustomerSearchOptions, SalesSdkCustomerView, SalesSdkEditCartLineBookingResult, SalesSdkEditCartLineProductResult, SalesSdkEditCartLineResult, SalesSdkEnsureAllergyBeforeCommitInput, SalesSdkGiftOption, SalesSdkGiftSelectChoice, SalesSdkGiftSelectContext, SalesSdkLastGiftActions, SalesSdkLoadSalesDetailParams, SalesSdkModuleAccess, SalesSdkModuleHandle, SalesSdkMultiDaySelectResult, SalesSdkOpenAllergySelectionPayload, SalesSdkOpenBookingSelectionEditResult, SalesSdkOpenBookingSelectionResult, SalesSdkOpenCustomerAuthPayload, SalesSdkOpenHolderSelectPayload, SalesSdkOpenMultiDaySelectPayload, SalesSdkPaymentTimelineGroup, SalesSdkPaymentTimelineLifecycle, SalesSdkPaymentTimelineMethod, SalesSdkPaymentTimelineRecord, SalesSdkProductsContextValue, SalesSdkPromotionTag, SalesSdkProviderProps, SalesSdkRefundPaymentInput, SalesSdkRefundPaymentsOptions, SalesSdkRefundPaymentsResult, SalesSdkRequiresBookingEditPayload, SalesSdkResourceError, SalesSdkResourceUnavailableReason, SalesSdkSalesNote, SalesSdkSalesNoteDraftInput, SalesSdkSalesNoteRelation, SalesSdkSalesNoteTextInput, SalesSdkSalesNotesMutationSnapshot, SalesSdkSetSalesNoteInput, SalesSdkSolution, SalesSdkStatus, SalesSdkSubmitSalesOrderParams, SalesSdkSummaryContextValue, SalesSdkUIHosts, SalesSdkUnfulfilledPromotion, ScanOrderOrderProduct, ScanOrderOrderProductIdentity, ScanOrderProductSku, ScanOrderSummary, ScanOrderTempOrder, TransitionChildBookingParams, UpdateOrderProductParams, UpdateOrderProductQuantityParams, UpdateProductInOrderParams };