import { BigSalePaidCheckoutPayload, BigSaleProps } from "../bigSale/types.js"; import { CSSProperties } from "react"; //#region src/components/vendingMachine/types.d.ts interface VendingContext { tenantDomain: string; businessCode: string; shopId: string; deviceId: string; locale: string; } type LocalizedText = string | Record; type PaidSale = BigSalePaidCheckoutPayload; interface DispenseItem { /** Frozen sales variant code, resolved before the cart is cleared. */ sku?: string; key: string; orderId: string | number; lineId: string; productId: string | number; variantId?: string | number; title: LocalizedText; /** Display-only specification captured from the paid order, never a dispensing key. */ specification?: LocalizedText; unitIndex: number; } interface DispenseResult { success: boolean; message?: string; code?: string; /** The command may have run, but its physical result could not be confirmed. Never retry automatically. */ uncertain?: boolean; } interface DispenseProgressItem extends DispenseItem { status: 'pending' | 'dispensing' | 'success' | 'failed'; message?: string; code?: string; uncertain?: boolean; } interface DispenseProgress { status: 'running' | 'complete' | 'interrupted'; items: DispenseProgressItem[]; /** Inventory persistence is independent of the confirmed physical result. */ inventorySync?: { status: 'syncing' | 'synced' | 'review'; message?: string; }; } type DispenseOne = (item: DispenseItem, context: VendingContext, signal: AbortSignal) => Promise; /** The host owns the serial queue and its session-wide order deduplication. */ interface DispenseJob { getSnapshot(): DispenseProgress; subscribe(listener: (progress: DispenseProgress) => void): () => void; cancel(): void; } /** Return an unsubscribe function; the host supplies its router's blocking API. */ type VendingNavigationBlocker = (prompt: () => string | false | undefined) => () => void; interface VendingSalesRuntime { inventory?: BigSaleProps['inventory']; checkoutHooks?: BigSaleProps['checkoutHooks']; osModuleName: string; otherParams?: BigSaleProps['otherParams']; ready: boolean; failed: boolean; retry(): void; } /** Device events and diagnostics are supplied by the host, not by the material. */ interface VendingScanSource { subscribe(listener: (payload: { data?: unknown; }) => void, options?: { priority?: number; }): () => void; /** Metadata contains request/scope identifiers and outcome only, never the scanned code. */ log?(event: string, metadata: Record): void; } /** Runtime capabilities are injected; no device plugin or application singleton is imported. */ interface VendingFulfillmentService { isCurrentContext(context: VendingContext): boolean; shouldDispenseAfterSale(context: VendingContext): Promise; startDispenseQueue(sale: PaidSale, context: VendingContext): DispenseJob; setIdlePaused(paused: boolean): Promise; } interface VendingMachineProps { context: VendingContext; sales: VendingSalesRuntime; fulfillment: VendingFulfillmentService; blockNavigation?: VendingNavigationBlocker; scanSource?: VendingScanSource; /** Suspend entry scanning while the host's staff dialog is open. */ scanEnabled?: boolean; brand: string; subtitle: string; machine: string; isActive?: boolean; isVisible?: boolean; idleRequest?: number; onRequestSettings(): void; onRequestStaff(): void; onPrerenderComplete?: () => void; className?: string; style?: CSSProperties; } interface RestockProduct { id: string; /** Sales product IDs are optional for inventory-backed product options. */ productId?: string | number; variantId?: string | number; inventoryItemId?: number; title: LocalizedText; sku: string; color?: string; disabled?: boolean; } type RestockChannelIssue = 'restockUnconfigured' | 'restockSpaceMissing' | 'restockSpaceInactive' | 'restockMultipleProducts' | 'restockProductMissing' | 'restockProductInactive' | 'restockStockInvalid' | 'stockSyncPending' | 'stockSyncReview' | 'restockCapacityExceeded'; /** View model only. Backend response mapping belongs to the service adapter. */ interface RestockChannel { id: string; row: string; groupName?: LocalizedText; code: string; /** References RestockProduct.id, not a sales product ID. */ productId: string | null; /** null means unknown/invalid stock, never an empty channel. */ quantity: number | null; /** Read-only available stock; omitted by adapters that only provide physical stock. */ availableQuantity?: number | null; capacity: number; lowThreshold: number; status: 'enabled' | 'disabled' | 'blocked'; boardDeviceId?: number; laneId?: number; spaceId?: number; stockId?: number; inventoryItemId?: number; /** Service-owned receipt for a submitted binding whose new stock ID is not known yet. */ submissionId?: string; issue?: RestockChannelIssue; } interface RestockData { channels: RestockChannel[]; products: RestockProduct[]; groupType?: 'row' | 'board'; /** Explicit false keeps quantity editing local until the host can persist it. */ canSubmit?: boolean; inventorySync?: DispenseProgress['inventorySync']; } interface RestockChange { channelId: string; before: RestockChannel; after: RestockChannel; } type RestockSubmitResult = { status: 'accepted'; data: RestockData; } | NonNullable; interface VendingStaff { id: string; name: string; permissions: string[]; } interface RestockService { load(context: VendingContext, signal: AbortSignal): Promise; /** Runs one physical channel test. The host owns hardware execution; stock is unchanged. */ testChannel?(context: VendingContext, channel: RestockChannel, signal: AbortSignal): Promise; submit(context: VendingContext, changes: RestockChange[], staff: VendingStaff | null, signal: AbortSignal): Promise; } //#endregion export { DispenseItem, DispenseJob, DispenseOne, DispenseProgress, DispenseProgressItem, DispenseResult, LocalizedText, PaidSale, RestockChange, RestockChannel, RestockData, RestockProduct, RestockService, RestockSubmitResult, VendingContext, VendingFulfillmentService, VendingMachineProps, VendingNavigationBlocker, VendingSalesRuntime, VendingScanSource, VendingStaff };