Files
order_site/apps/frontend/src/services/admin/fulfillment-channel.ts
T

442 lines
10 KiB
TypeScript

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<string, unknown>
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<string, unknown>
export type PendingWorkOrderRetryResult = {
scanned: number
createdWorkOrders: number
assigned: number
remaining: number
carrierFailed: number
skipReasons: Record<string, number>
}
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<VoucherConsumeChannel, VoucherChannelConsumePolicy>
}
>
}
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<PendingChannelGroupListResult>('/api/v1/admin/fulfillment/pending', params)
}
export function fetchVoucherConsumePolicy() {
return apiGet<VoucherConsumePolicy>('/api/v1/admin/fulfillment/voucher-consume-policy')
}
export function saveVoucherConsumePolicy(payload: VoucherConsumePolicy) {
return apiPost<VoucherConsumePolicy>('/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<VoucherConsumeJobListResult>(
'/api/v1/admin/fulfillment/voucher-consume-jobs',
params,
)
}
export function retryVoucherConsumeJob(id: number) {
return apiPost<VoucherConsumeJob | null>(
`/api/v1/admin/fulfillment/voucher-consume-jobs/${id}/retry`,
{},
)
}
export function fetchChannelExceptions(
params: {
reason?: string
status?: string
page?: number
pageSize?: number
} = {},
) {
return apiGet<ChannelExceptionListResult>('/api/v1/admin/fulfillment/exceptions', params)
}
export function retryPendingWorkOrderRouting(
payload: { internalSku?: string; limit?: number } = {},
) {
return apiPost<PendingWorkOrderRetryResult>(
'/api/v1/admin/fulfillment/pending/retry-work-orders',
payload,
)
}
export function retryPendingCarriers(payload: {
internalSku: string
retryInactiveCarrier?: boolean
includeLocked?: boolean
issueCodes?: string[]
}) {
return apiPost<PendingCarrierRetryResult>(
'/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<DeletePendingChannelGroupResult>(
'/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<ChannelRuleListResult>('/api/v1/admin/fulfillment/rules', params)
}
export function fetchInternalSkuMappings(
params: {
keyword?: string
provider?: string
shopId?: string
matchType?: string
page?: number
pageSize?: number
} = {},
) {
return apiGet<InternalSkuMappingListResult>(
'/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<boolean>(`/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<FinancePipelineStatus[]>('/api/v1/admin/fulfillment/pipeline/status')
}
export function runFinancePipeline(payload: { days?: number; pipeline?: string } = {}) {
return apiPost<PipelineRunResult>('/api/v1/admin/fulfillment/pipeline/run', payload, {
timeout: 300_000,
})
}