diff --git a/apps/backend/src/routes/admin/worker-platform.ts b/apps/backend/src/routes/admin/worker-platform.ts index 4f84a85c..24ff2798 100644 --- a/apps/backend/src/routes/admin/worker-platform.ts +++ b/apps/backend/src/routes/admin/worker-platform.ts @@ -2,6 +2,7 @@ import { Router } from 'express' import { acceptAdminWorkOrder, + acceptAdminWorkOrders, assignAdminWorkOrderToWorker, unassignAdminWorkOrder, creditAdminWorkerWallet, @@ -512,6 +513,24 @@ router.post( }), ) +router.post( + '/worker-platform/orders/accept-batch', + requireAdminRoles(['admin', 'operator']), + createJsonHandler((req) => acceptAdminWorkOrders(req.body || {}), { + successMessage: '选中订单已验收通过', + errorMessage: '批量验收接单订单失败', + scope: '[admin/worker-platform/orders/accept-batch]', + audit: (req, data) => ({ + action: 'work_orders_batch_accepted', + targetType: 'work_order', + targetId: Array.isArray(req.body?.workOrderIds) + ? req.body.workOrderIds.map((item: unknown) => String(item)).join(',') + : '', + data: data && typeof data === 'object' ? (data as Record) : {}, + }), + }), +) + router.post( '/worker-platform/orders/:workOrderId/deduct-pending-deposit', requireAdminRoles(['admin', 'operator']), diff --git a/apps/backend/src/services/worker-platform/admin-service.ts b/apps/backend/src/services/worker-platform/admin-service.ts index a0bb7a0f..b6568eb8 100644 --- a/apps/backend/src/services/worker-platform/admin-service.ts +++ b/apps/backend/src/services/worker-platform/admin-service.ts @@ -1163,6 +1163,46 @@ export async function acceptAdminWorkOrder(workOrderId: number | string) { return { order: mapWorkOrderAdmin(updated || workOrder) } } +export async function acceptAdminWorkOrders(payload: JsonObject = {}) { + const workOrderIds = [ + ...new Set( + (Array.isArray(payload.workOrderIds) ? payload.workOrderIds : []) + .map((item) => normalizeOptionalId(item)) + .filter((item): item is number => item !== null), + ), + ] + if (workOrderIds.length === 0) { + throw createHttpError('请至少选择一个待验收订单', { + statusCode: 400, + errorCode: 'work_order_batch_accept_required', + }) + } + + const workOrders = await Promise.all(workOrderIds.map((workOrderId) => getRequiredWorkOrder(workOrderId))) + if (workOrders.some((workOrder) => workOrder.status !== WORK_ORDER_STATUS.PENDING_ACCEPTANCE)) { + throw createHttpError('选中的订单包含非待验收状态,请刷新后重试', { + statusCode: 409, + errorCode: 'work_order_batch_accept_status_invalid', + }) + } + + const pendingSharingCounts = await Promise.all( + workOrders.map((workOrder) => countWorkOrderPendingSharingSubmissions(workOrder.id)), + ) + if (pendingSharingCounts.some((count) => count > 0)) { + throw createHttpError('选中的订单存在拼单打手未提交验收资料,暂不能统一验收', { + statusCode: 409, + errorCode: 'work_order_batch_accept_sharing_pending_submissions', + }) + } + + const results = [] + for (const workOrder of workOrders) { + results.push(await acceptAdminWorkOrder(workOrder.id)) + } + return { orders: results.map((result) => result.order) } +} + export async function unassignAdminWorkOrder(workOrderId: number | string) { const workOrder = await getRequiredWorkOrder(workOrderId) if ( diff --git a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx index dc0f867d..f20a67b0 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx @@ -44,6 +44,7 @@ import ImagePreviewList from '@/components/files/ImagePreviewList' import PageHeader from '@/components/admin/PageHeader' import { acceptAdminWorkOrder, + acceptAdminWorkOrders, assignAdminWorkOrderToWorker, createAdminMockWorkOrder, unassignAdminWorkOrder, @@ -124,6 +125,7 @@ export default function WorkOrdersPanel() { const [keywordInput, setKeywordInput] = useState('') const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE) + const [selectedAcceptanceOrderIds, setSelectedAcceptanceOrderIds] = useState([]) const [detailOrder, setDetailOrder] = useState(null) const [problemOrder, setProblemOrder] = useState(null) const [resolutionOrder, setResolutionOrder] = useState(null) @@ -176,6 +178,10 @@ export default function WorkOrdersPanel() { enabled: Boolean(assignOrder), }) + useEffect(() => { + setSelectedAcceptanceOrderIds([]) + }, [status, keyword, page, pageSize]) + function openAssignModal(row: WorkOrder) { setAssignOrder(row) setAssignWorkerId(undefined) @@ -304,6 +310,27 @@ export default function WorkOrdersPanel() { } } + function confirmBatchAcceptance() { + const workOrderIds = selectedAcceptanceOrderIds + if (workOrderIds.length === 0) { + message.warning('请先选择待验收订单') + return + } + modal.confirm({ + title: `确认验收 ${workOrderIds.length} 个订单?`, + content: '验收通过后将结算打手奖励,并按配置处理押金。', + okText: '一键验收', + cancelText: '取消', + onOk: async () => { + const succeeded = await runAction( + () => acceptAdminWorkOrders(workOrderIds), + `已验收 ${workOrderIds.length} 个订单`, + ) + if (succeeded) setSelectedAcceptanceOrderIds([]) + }, + }) + } + async function submitProblem(values: { note?: string }) { if (!problemOrder) return const succeeded = await runAction( @@ -821,6 +848,16 @@ export default function WorkOrdersPanel() { title="接单工单" extra={ + {status === 'pending_acceptance' ? ( + + ) : null}