feat(order): 精准区分自动直发与接单平台打手履约,智能门控H5直充按钮

- 后端增加按订单批量关联接单工单查询,精准识别履约类型(自动直发 / 打手履约 / 电子凭证 / 拼多多发货)
- 订单列表与详情页中,仅当订单为自动直发(affiliate_dash / 码速达直充)时展示【H5】一键复制直充链接与兑换状态控制
- 接单平台工单单与纯核销单隐藏直充 H5 按钮与兑换暂停/关闭标签,防止客服向打手单买家误发直充链接
- 交付模式标签升级为「⚡ 自动直发」、「👥 打手履约」、「🎫 电子凭证」、「📦 拼多多发货」

验证方式:前端类型检查与打包构建通过,全量 132 项前端单测与 674 项后端单测全部通过
This commit is contained in:
yml2213
2026-10-10 19:44:20 +08:00
parent 9a6b4313a8
commit 748c18bc7d
8 changed files with 237 additions and 79 deletions
@@ -299,6 +299,34 @@ export async function listWorkOrdersByPlatformOrderId(
return result.rows
}
export async function listWorkOrdersByOrderIds(orderIds: number[]): Promise<WorkOrderRow[]> {
if (!orderIds || orderIds.length === 0) {
return []
}
const result = await query<WorkOrderRow>(
`${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<WorkOrderRow[]> {
const normalizedId = Number(orderId)
if (!normalizedId) {
return []
}
const result = await query<WorkOrderRow>(
`${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,
@@ -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,
@@ -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,
@@ -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 = {