import { apiDelete, apiGet, apiPost } from '@/lib/http' /** 待分类队列(按内部 SKU 聚合)。 */ export type PendingChannelGroup = { group_key: string scope_type: 'internal_sku' | 'source_sku' internal_sku: string | null sku_name: string providers: string[] shop_ids: string[] shops: Array<{ id: string; name: string }> sku_codes: string[] pending_reasons: string[] pending_subreasons: string[] configured_channels: string[] locked_item_count: number item_count: number revenue_fen: number first_seen_at: string last_seen_at: string issue_breakdown: Array<{ issue_code: string issue_category: string item_count: number locked_item_count: number retryable: boolean auto_retryable: boolean owner_role: string }> retryable_item_count: number retryable_unlocked_item_count: number auto_retryable_item_count: number auto_retryable_unlocked_item_count: number config_issue_item_count: number manual_review_item_count: number after_sales_item_count: number system_issue_item_count: number oldest_age_minutes: number } export type PendingChannelGroupListResult = { items: PendingChannelGroup[] total: number page: number pageSize: number } /** 渠道规则(L1)。 */ export type ChannelRule = { id: number match_key_type: 'internal_sku' match_key: string channel: 'work_order' | 'affiliate_dash' | 'third_party_voucher' | 'manual' | 'on_account' priority: number enabled: boolean is_fallback: boolean source: string config_json: Record note: string } export type ChannelRuleListResult = { items: ChannelRule[] total: number page: number pageSize: number } export type InternalSkuMappingMatchType = | 'sku_code' | 'sku_nick' | 'out_sku_sn' | 'spec' | 'sku_name' export type InternalSkuMapping = { id: number provider: string shop_id: string match_type: InternalSkuMappingMatchType match_value: string internal_sku: string priority: number enabled: boolean source: string note: string updated_at: string } export type InternalSkuMappingListResult = { items: InternalSkuMapping[] total: number page: number pageSize: number } export type ChannelException = { id: number reason: string status: string scope_key: string item_count: number first_seen_at: string last_seen_at: string detail_json: unknown } export type ChannelExceptionListResult = { items: ChannelException[] total: number page: number pageSize: number } export type PipelineRunResult = Record export type PendingWorkOrderRetryResult = { scanned: number createdWorkOrders: number assigned: number remaining: number carrierFailed: number skipReasons: Record } export type PendingCarrierRetryResult = { scanned: number workOrdersCreated: number affiliateTasksCreated: number assigned: number failed: number remaining: number } export type DeletePendingChannelGroupResult = { archivedItems: number preservedOrders: number deletedCostSnapshots: number resolvedExceptions: number refreshedDays: number skippedClosedDays: number } export type FinancePipelineStatus = { pipeline: string label: string lastItemId: number lastStatDate: string | null lastSuccessAt: string | null lastError: string jobId: string scheduler: { running: boolean enabled: boolean lastRunAt: string lastFinishedAt: string nextRunAt: string lastStatus: string lastMessage: string } | null } export type VoucherConsumeProvider = 'kuaishou_industry' | 'douyin_mashida' export type VoucherConsumeChannel = | 'affiliate_dash' | 'work_order' | 'manual' | 'on_account' | 'third_party_voucher' export type VoucherConsumeMode = | 'manual' | 'after_order_received' | 'after_link_generated' | 'after_task_created' | 'after_work_order_published' | 'after_work_order_assigned' | 'after_work_order_published_or_assigned' export type VoucherChannelConsumePolicy = { enabled: boolean mode: VoucherConsumeMode delaySeconds: number } export type VoucherConsumePolicy = { version: number enabled: boolean platforms: Record< VoucherConsumeProvider, { enabled: boolean channels: Record } > } export type VoucherConsumeJob = { id: number provider: VoucherConsumeProvider channel: VoucherConsumeChannel trigger_event: string platform_order_id: string order_id: number | null order_item_id: number | null task_id: number | null work_order_id: number | null due_at: string status: 'pending' | 'processing' | 'succeeded' | 'failed' | 'cancelled' attempt_count: number max_attempts: number last_error: string voucher_codes: string[] created_at: string updated_at: string finished_at: string | null } export type VoucherConsumeJobListResult = { items: VoucherConsumeJob[] total: number page: number pageSize: number } export function fetchPendingChannelGroups( params: { page?: number pageSize?: number keyword?: string issueCategory?: string } = {}, ) { return apiGet('/api/v1/admin/fulfillment/pending', params) } export function fetchVoucherConsumePolicy() { return apiGet('/api/v1/admin/fulfillment/voucher-consume-policy') } export function saveVoucherConsumePolicy(payload: VoucherConsumePolicy) { return apiPost('/api/v1/admin/fulfillment/voucher-consume-policy', payload) } export function fetchVoucherConsumeJobs( params: { keyword?: string provider?: VoucherConsumeProvider channel?: VoucherConsumeChannel status?: VoucherConsumeJob['status'] page?: number pageSize?: number } = {}, ) { return apiGet( '/api/v1/admin/fulfillment/voucher-consume-jobs', params, ) } export function retryVoucherConsumeJob(id: number) { return apiPost( `/api/v1/admin/fulfillment/voucher-consume-jobs/${id}/retry`, {}, ) } export function fetchChannelExceptions( params: { reason?: string status?: string page?: number pageSize?: number } = {}, ) { return apiGet('/api/v1/admin/fulfillment/exceptions', params) } export function retryPendingWorkOrderRouting( payload: { internalSku?: string; limit?: number } = {}, ) { return apiPost( '/api/v1/admin/fulfillment/pending/retry-work-orders', payload, ) } export function retryPendingCarriers(payload: { internalSku: string retryInactiveCarrier?: boolean includeLocked?: boolean issueCodes?: string[] }) { return apiPost( '/api/v1/admin/fulfillment/pending/retry-carriers', payload, ) } export function unlockPendingChannelGroup(payload: { groupKey?: string internalSku?: string issueCategory?: string }) { return apiPost<{ updated: number }>('/api/v1/admin/fulfillment/pending/unlock', payload) } export function deletePendingChannelGroup(payload: { groupKey: string; issueCategory?: string }) { return apiPost( '/api/v1/admin/fulfillment/pending/delete', payload, ) } export function assignChannelByInternalSku(payload: { groupKey?: string internalSku: string mappedInternalSku?: string channel: string issueCategory?: string note?: string }) { return apiPost<{ updated: number carrierFailed: number ruleCreated: boolean channelSaved: boolean draftProductId: number | null carrierFailures: Array<{ orderItemId: number; reason: string; message: string }> }>('/api/v1/admin/fulfillment/assign', payload) } export function assignChannelToItems(payload: { orderItemIds: number[] channel: string note?: string }) { return apiPost<{ updated: number; carrierFailed: number }>( '/api/v1/admin/fulfillment/assign', payload, ) } export function switchChannelForItems(payload: { orderItemIds: number[] channel: string note?: string }) { return apiPost<{ switched: number failed: number errors: Array<{ orderItemId: number; reason: string }> }>('/api/v1/admin/fulfillment/switch', payload) } export function fetchChannelRules( params: { channel?: string source?: string keyword?: string enabled?: boolean page?: number pageSize?: number } = {}, ) { return apiGet('/api/v1/admin/fulfillment/rules', params) } export function fetchInternalSkuMappings( params: { keyword?: string provider?: string shopId?: string matchType?: string page?: number pageSize?: number } = {}, ) { return apiGet( '/api/v1/admin/fulfillment/internal-sku-mappings', params, ) } export function saveInternalSkuMapping(payload: { provider?: string shopId?: string matchType: InternalSkuMappingMatchType matchValue: string internalSku: string priority?: number note?: string enabled?: boolean }) { return apiPost<{ id: number }>('/api/v1/admin/fulfillment/internal-sku-mappings', payload) } export function deleteInternalSkuMapping(id: number) { return apiDelete(`/api/v1/admin/fulfillment/internal-sku-mappings/${id}`) } export function saveChannelRule(payload: { id?: number matchKeyType: string matchKey: string channel: string priority?: number enabled?: boolean note?: string }) { return apiPost<{ id: number }>('/api/v1/admin/fulfillment/rules', payload) } export function deleteChannelRule(ruleId: number) { return apiDelete<{ deleted: boolean }>(`/api/v1/admin/fulfillment/rules/${ruleId}`) } export function deleteChannelRules(ids: number[]) { return apiPost<{ deletedCount: number deletedIds: number[] missingIds: number[] }>('/api/v1/admin/fulfillment/rules/batch-delete', { ids }) } export function forceDeleteChannelRules(ids: number[]) { return apiPost<{ deletedCount: number deletedIds: number[] missingIds: number[] }>('/api/v1/admin/fulfillment/rules/force-delete', { ids }) } export function fetchPipelineStatus() { return apiGet('/api/v1/admin/fulfillment/pipeline/status') } export function runFinancePipeline(payload: { days?: number; pipeline?: string } = {}) { return apiPost('/api/v1/admin/fulfillment/pipeline/run', payload, { timeout: 300_000, }) }