diff --git a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts index 6e746495..0e2605e5 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-query-repo.ts @@ -299,6 +299,34 @@ export async function listWorkOrdersByPlatformOrderId( return result.rows } +export async function listWorkOrdersByOrderIds(orderIds: number[]): Promise { + if (!orderIds || orderIds.length === 0) { + return [] + } + const result = await query( + `${WORK_ORDER_SELECT} + WHERE wo.order_id = ANY($1::bigint[]) + ORDER BY wo.id ASC`, + [orderIds], + ) + return result.rows +} + +export async function listWorkOrdersByOrderId(orderId: number | string): Promise { + const normalizedId = Number(orderId) + if (!normalizedId) { + return [] + } + const result = await query( + `${WORK_ORDER_SELECT} + WHERE wo.order_id = $1 + ORDER BY wo.id ASC`, + [normalizedId], + ) + return result.rows +} + + export async function listWorkOrders({ page = 1, pageSize = 20, diff --git a/apps/backend/src/services/admin/admin-order-read-helpers.ts b/apps/backend/src/services/admin/admin-order-read-helpers.ts index d7582ec0..4f37e23e 100644 --- a/apps/backend/src/services/admin/admin-order-read-helpers.ts +++ b/apps/backend/src/services/admin/admin-order-read-helpers.ts @@ -20,10 +20,12 @@ export function mapAdminOrderListItem( tasks = [], orderItems = [], vouchers = [], + workOrderCount = 0, }: { tasks?: TaskRow[] orderItems?: OrderItemRow[] vouchers?: KuaishouIndustryVoucherRow[] + workOrderCount?: number } = {}, ): AdminOrderListItem { const fulfillmentProgress = buildOrderFulfillmentProgress(tasks) @@ -67,6 +69,13 @@ export function mapAdminOrderListItem( ) const hasResolvedFallback = (resolvedItemTitle || resolvedSkuNick) && rawItemCount === 0 + const { fulfillmentType, canClaimH5 } = resolveOrderFulfillmentType({ + provider: item.provider || '', + tasks, + workOrderCount, + vouchers, + }) + return { orderId: item.id, provider: item.provider || '', @@ -92,6 +101,9 @@ export function mapAdminOrderListItem( skuNick: resolvedSkuNick, items: itemsList, taskCount: tasks.length, + workOrderCount, + fulfillmentType, + canClaimH5, resourceStatus: fulfillmentProgress.resourceStatus, customerStatus: fulfillmentProgress.customerStatus, preparedTaskCount: fulfillmentProgress.preparedTaskCount, @@ -102,6 +114,57 @@ export function mapAdminOrderListItem( } } +export function resolveOrderFulfillmentType({ + provider, + tasks = [], + workOrderCount = 0, + vouchers = [], +}: { + provider: string + tasks?: TaskRow[] + workOrderCount?: number + vouchers?: KuaishouIndustryVoucherRow[] +}): { + fulfillmentType: 'auto_claim' | 'worker_dispatch' | 'voucher_only' | 'manual' + canClaimH5: boolean +} { + const normProvider = String(provider || '').trim().toLowerCase() + const hasAutoClaimTask = + tasks.some( + (t) => + t.executor_key === 'affiliate_dash' || + t.executor_key === 'kuaishou_ct_assisted' || + t.executor_key === 'kuaishou_feifei' || + Boolean(t.requires_claim), + ) || normProvider === 'douyin_mashida' + + if (hasAutoClaimTask) { + return { + fulfillmentType: 'auto_claim', + canClaimH5: true, + } + } + + if (workOrderCount > 0) { + return { + fulfillmentType: 'worker_dispatch', + canClaimH5: false, + } + } + + if (vouchers.length > 0 || normProvider === '91kaquan' || normProvider === 'kuaishou_industry') { + return { + fulfillmentType: 'voucher_only', + canClaimH5: false, + } + } + + return { + fulfillmentType: 'manual', + canClaimH5: false, + } +} + export function summarizeOrderItems( items: OrderItemRow[] | null | undefined, fallbackOrder?: OrderListRow | null | undefined, diff --git a/apps/backend/src/services/admin/admin-read-service.ts b/apps/backend/src/services/admin/admin-read-service.ts index da3a92f5..e9ce5e50 100644 --- a/apps/backend/src/services/admin/admin-read-service.ts +++ b/apps/backend/src/services/admin/admin-read-service.ts @@ -60,7 +60,15 @@ import { resolveOrderItemTitle, } from './admin-read-shared-helpers.js' import { isRetiredFulfillmentExecutor } from '../fulfillment/executors/types.js' -import { mapAdminOrderListItem, summarizeOrderItems } from './admin-order-read-helpers.js' +import { + mapAdminOrderListItem, + resolveOrderFulfillmentType, + summarizeOrderItems, +} from './admin-order-read-helpers.js' +import { + listWorkOrdersByOrderId, + listWorkOrdersByOrderIds, +} from '../../repositories/worker-platform/work-order-query-repo.js' import { buildOrderFulfillmentProgress, getRequiredTask, @@ -103,22 +111,28 @@ export async function getAdminOrders( const orderIds = items.map((item) => Number(item.id)).filter((id) => id > 0) const oids = items.map((item) => item.platform_order_id).filter(Boolean) - const [orderItems, tasks, vouchers] = await Promise.all([ + const [orderItems, tasks, vouchers, workOrders] = await Promise.all([ listOrderItemsByOrderIds(orderIds), listTasksByOrderIds(orderIds), listKuaishouIndustryVouchersByOids(oids), + listWorkOrdersByOrderIds(orderIds), ]) const orderItemsByOrderId = groupRowsByOrderId(orderItems) const tasksByOrderId = groupRowsByOrderId(tasks) const vouchersByOid = groupRowsByOid(vouchers) + const workOrdersByOrderId = groupRowsByOrderId(workOrders) return { items: items.map((item) => { const orderId = Number(item.id) + const orderTasks = tasksByOrderId.get(orderId) || [] + const orderVouchers = vouchersByOid.get(item.platform_order_id) || [] + const orderWorkOrders = workOrdersByOrderId.get(orderId) || [] return mapAdminOrderListItem(item, { orderItems: orderItemsByOrderId.get(orderId) || [], - tasks: tasksByOrderId.get(orderId) || [], - vouchers: vouchersByOid.get(item.platform_order_id) || [], + tasks: orderTasks, + vouchers: orderVouchers, + workOrderCount: orderWorkOrders.length, }) }), pagination: { page, pageSize, total }, @@ -163,15 +177,22 @@ export async function getAdminOrderDetail( }) } - const [items, tasks] = await Promise.all([ + const [items, tasks, workOrders] = await Promise.all([ listOrderItemsByOrderId(order.id), listTasksByOrderId(order.id), + listWorkOrdersByOrderId(order.id), ]) const kuaishouIndustryVouchers = await listKuaishouIndustryVouchersByOid(order.platform_order_id) const itemSummary = summarizeOrderItems(items, order, kuaishouIndustryVouchers) const orderItemTitle = resolveOrderItemTitle(items[0], order, kuaishouIndustryVouchers) const orderSkuNick = resolveOrderItemSkuNick(items[0], order, kuaishouIndustryVouchers) const viewerContext = createAdminViewerContext(session) + const { fulfillmentType, canClaimH5 } = resolveOrderFulfillmentType({ + provider: order.provider || '', + tasks, + workOrderCount: workOrders.length, + vouchers: kuaishouIndustryVouchers, + }) return { order: { @@ -203,6 +224,9 @@ export async function getAdminOrderDetail( claimStatus: order.claim_status || 'active', claimStatusReason: order.claim_status_reason || '', claimStatusUpdatedAt: order.claim_status_updated_at || null, + fulfillmentType, + workOrderCount: workOrders.length, + canClaimH5, }, items: items.map((item) => ({ orderItemId: item.id, diff --git a/apps/backend/src/types/admin/read-models.ts b/apps/backend/src/types/admin/read-models.ts index cfa41dcc..5ec6bdd3 100644 --- a/apps/backend/src/types/admin/read-models.ts +++ b/apps/backend/src/types/admin/read-models.ts @@ -44,6 +44,9 @@ export type AdminOrderListItem = { claimStatus?: string claimStatusReason?: string claimStatusUpdatedAt?: string | null + fulfillmentType?: 'auto_claim' | 'worker_dispatch' | 'voucher_only' | 'manual' + workOrderCount?: number + canClaimH5?: boolean } export type AdminTaskListItem = { diff --git a/apps/frontend/src/pages/admin/AdminOrderDetailPage.tsx b/apps/frontend/src/pages/admin/AdminOrderDetailPage.tsx index d9b33543..704b70af 100644 --- a/apps/frontend/src/pages/admin/AdminOrderDetailPage.tsx +++ b/apps/frontend/src/pages/admin/AdminOrderDetailPage.tsx @@ -30,6 +30,7 @@ import StatusTag from '@/components/admin/StatusTag' import { fetchAdminOrderDetail, updateAdminOrderClaimStatus } from '@/services/admin' import type { AdminOrderDetail, AdminTaskListItem } from '@/types/admin' import { formatAdminDateTime } from '@/utils/admin-time' +import { resolveOrderFulfillmentMode } from '@/utils/admin-display' export default function AdminOrderDetailPage() { const navigate = useNavigate() @@ -164,6 +165,10 @@ export default function AdminOrderDetailPage() { const isOrderRefundedOrCancelled = detail.order.payStatus === 'refunded' || detail.order.orderStatus === 'cancelled' const canPauseOrClose = !isOrderFulfilled && !isOrderRefundedOrCancelled + const isAutoClaimOrder = Boolean( + detail.order.canClaimH5 || detail.order.fulfillmentType === 'auto_claim', + ) + const fulfillmentMode = resolveOrderFulfillmentMode(detail.order) return (
@@ -176,40 +181,42 @@ export default function AdminOrderDetailPage() { - - {detail.order.claimStatus === 'paused' || detail.order.claimStatus === 'closed' ? ( - - ) : canPauseOrClose ? ( - - ) : null} - {canPauseOrClose && detail.order.claimStatus !== 'closed' ? ( - - ) : null} - + {detail.order.claimStatus === 'paused' || detail.order.claimStatus === 'closed' ? ( + + ) : canPauseOrClose ? ( + + ) : null} + {canPauseOrClose && detail.order.claimStatus !== 'closed' ? ( + + ) : null} + + ) : null } > @@ -218,34 +225,41 @@ export default function AdminOrderDetailPage() { {detail.order.shopName || detail.order.shopId || '-'} {detail.order.platformOrderId} + + + {fulfillmentMode.label} + + - - - {detail.order.claimStatus === 'paused' ? ( - }> - 已暂停兑换 - - ) : detail.order.claimStatus === 'closed' ? ( - }> - 已关闭兑换 - - ) : ( - }> - 正常有效 - - )} - {detail.order.claimStatusReason ? ( - - ({detail.order.claimStatusReason}) - - ) : null} - - + {isAutoClaimOrder ? ( + + + {detail.order.claimStatus === 'paused' ? ( + }> + 已暂停兑换 + + ) : detail.order.claimStatus === 'closed' ? ( + }> + 已关闭兑换 + + ) : ( + }> + 正常有效 + + )} + {detail.order.claimStatusReason ? ( + + ({detail.order.claimStatusReason}) + + ) : null} + + + ) : null} {detail.order.totalAmount} {detail.order.currency} diff --git a/apps/frontend/src/pages/admin/AdminOrdersPage.tsx b/apps/frontend/src/pages/admin/AdminOrdersPage.tsx index 63cc66fd..5b35cbd5 100644 --- a/apps/frontend/src/pages/admin/AdminOrdersPage.tsx +++ b/apps/frontend/src/pages/admin/AdminOrdersPage.tsx @@ -309,7 +309,12 @@ export default function AdminOrdersPage() { )} - 数量 {row.totalQuantity || 1} · 关联任务 {row.taskCount || 0} + 数量 {row.totalQuantity || 1} + {row.canClaimH5 + ? ` · 关联任务 ${row.taskCount || 0}` + : row.workOrderCount + ? ` · 关联工单 ${row.workOrderCount}` + : ''} ) @@ -332,11 +337,11 @@ export default function AdminOrdersPage() { - {row.claimStatus === 'paused' ? ( + {row.canClaimH5 && row.claimStatus === 'paused' ? ( H5已暂停 - ) : row.claimStatus === 'closed' ? ( + ) : row.canClaimH5 && row.claimStatus === 'closed' ? ( H5已关闭 @@ -361,7 +366,7 @@ export default function AdminOrdersPage() { }, { title: '操作', - minWidth: 140, + minWidth: 130, fixed: 'right', render: (_, row) => { const orderNo = row.platformOrderId || String(row.orderId) @@ -378,15 +383,17 @@ export default function AdminOrdersPage() { > 详情 - - - + {row.canClaimH5 ? ( + + + + ) : null} ) }, diff --git a/apps/frontend/src/types/admin/orders.ts b/apps/frontend/src/types/admin/orders.ts index cd432398..5adce6af 100644 --- a/apps/frontend/src/types/admin/orders.ts +++ b/apps/frontend/src/types/admin/orders.ts @@ -46,6 +46,9 @@ export interface AdminOrderListItem { claimStatus?: string claimStatusReason?: string claimStatusUpdatedAt?: string | null + fulfillmentType?: 'auto_claim' | 'worker_dispatch' | 'voucher_only' | 'manual' + workOrderCount?: number + canClaimH5?: boolean } export interface AdminOrderDetail { @@ -61,6 +64,9 @@ export interface AdminOrderDetail { claimStatus?: string claimStatusReason?: string claimStatusUpdatedAt?: string | null + fulfillmentType?: 'auto_claim' | 'worker_dispatch' | 'voucher_only' | 'manual' + workOrderCount?: number + canClaimH5?: boolean buyerId: string buyerName: string receiverContact: string diff --git a/apps/frontend/src/utils/admin-display.ts b/apps/frontend/src/utils/admin-display.ts index 0b1fea61..bd62cb4a 100644 --- a/apps/frontend/src/utils/admin-display.ts +++ b/apps/frontend/src/utils/admin-display.ts @@ -221,21 +221,34 @@ export function resolveOrderPlatformBadge(row: { export function resolveOrderFulfillmentMode(row: { provider?: string platform?: string + fulfillmentType?: 'auto_claim' | 'worker_dispatch' | 'voucher_only' | 'manual' + workOrderCount?: number + canClaimH5?: boolean + taskCount?: number }): { label: string; color: string } { const provider = String(row.provider || '').toLowerCase() - if (provider === 'douyin_mashida') { - return { label: 'H5直绑', color: 'blue' } + if (row.canClaimH5 || row.fulfillmentType === 'auto_claim' || provider === 'douyin_mashida') { + return { label: '⚡ 自动直发', color: 'blue' } } - if (provider === 'kuaishou_industry' || provider === '91kaquan' || provider === 'open_91') { - return { label: '电子凭证', color: 'gold' } + if (row.fulfillmentType === 'worker_dispatch' || Number(row.workOrderCount || 0) > 0) { + return { label: '👥 打手履约', color: 'purple' } + } + + if ( + provider === 'kuaishou_industry' || + provider === '91kaquan' || + provider === 'open_91' || + row.fulfillmentType === 'voucher_only' + ) { + return { label: '🎫 电子凭证', color: 'gold' } } if (provider === 'pdd') { - return { label: '拼多多发货', color: 'red' } + return { label: '📦 拼多多发货', color: 'red' } } - return { label: '自动交付', color: 'cyan' } + return { label: '手动交付', color: 'default' } }