feat: 支持取消指派打手,可重新指派他人
- 后台可取消进行中订单的指派:订单回未分配,冻结押金全额退还,记录事件与审计 - 工单列表新增「取消指派」按钮(带确认弹窗),客服/管理员/运营可用 - 取消后可重新指派给其他打手
This commit is contained in:
@@ -1063,6 +1063,96 @@ export async function cancelWorkerWorkOrder(input: {
|
||||
})
|
||||
}
|
||||
|
||||
export async function unassignWorkOrder(input: {
|
||||
workOrderId: number
|
||||
now: string
|
||||
}): Promise<{
|
||||
order: WorkOrderRow | null
|
||||
failureReason: 'work_order_not_in_progress' | 'work_order_no_worker' | null
|
||||
}> {
|
||||
return withTransaction(async (client) => {
|
||||
const currentResult = await client.query<WorkOrderRow>(
|
||||
`
|
||||
SELECT *
|
||||
FROM work_orders
|
||||
WHERE id = $1
|
||||
FOR UPDATE
|
||||
`,
|
||||
[input.workOrderId],
|
||||
)
|
||||
const workOrder = currentResult.rows[0] || null
|
||||
if (!workOrder || workOrder.status !== 'in_progress') {
|
||||
return { order: null, failureReason: 'work_order_not_in_progress' }
|
||||
}
|
||||
const workerId = Number(workOrder.assigned_worker_id || 0)
|
||||
if (!workerId) {
|
||||
return { order: null, failureReason: 'work_order_no_worker' }
|
||||
}
|
||||
|
||||
await ensureWorkerWalletWithClient(client, workerId, input.now)
|
||||
const wallet = await getWorkerWalletWithClient(client, workerId)
|
||||
const releaseAmount = Math.min(
|
||||
await getOutstandingDepositAmountWithClient(client, workerId, input.workOrderId),
|
||||
Number(wallet?.frozen_deposit_amount || 0),
|
||||
)
|
||||
|
||||
await client.query(
|
||||
`
|
||||
UPDATE work_orders
|
||||
SET
|
||||
status = 'unassigned',
|
||||
assigned_worker_id = NULL,
|
||||
assigned_at = NULL,
|
||||
deadline_at = NULL,
|
||||
updated_at = $1
|
||||
WHERE id = $2
|
||||
`,
|
||||
[input.now, input.workOrderId],
|
||||
)
|
||||
|
||||
if (releaseAmount > 0) {
|
||||
const nextAvailable = Number(wallet?.available_amount || 0) + releaseAmount
|
||||
const nextFrozen = Math.max(
|
||||
0,
|
||||
Number(wallet?.frozen_deposit_amount || 0) - releaseAmount,
|
||||
)
|
||||
await client.query(
|
||||
`
|
||||
UPDATE worker_wallets
|
||||
SET available_amount = $1, frozen_deposit_amount = $2, updated_at = $3
|
||||
WHERE worker_id = $4
|
||||
`,
|
||||
[nextAvailable, nextFrozen, input.now, workerId],
|
||||
)
|
||||
await client.query(
|
||||
`
|
||||
INSERT INTO worker_wallet_ledgers (
|
||||
worker_id, ledger_type, amount, balance_after, frozen_after,
|
||||
related_work_order_id, note, payload_json, created_at
|
||||
) VALUES ($1, 'deposit_release', $2, $3, $4, $5, '后台取消指派退还押金', '{}'::jsonb, $6)
|
||||
`,
|
||||
[workerId, releaseAmount, nextAvailable, nextFrozen, input.workOrderId, input.now],
|
||||
)
|
||||
}
|
||||
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'admin',
|
||||
actorId: '',
|
||||
eventType: 'unassigned_by_admin',
|
||||
fromStatus: 'in_progress',
|
||||
toStatus: 'unassigned',
|
||||
payloadJson: JSON.stringify({ workerId, releaseAmount }),
|
||||
now: input.now,
|
||||
})
|
||||
|
||||
return {
|
||||
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function countWorkerCancellationsSince(
|
||||
workerId: number | string,
|
||||
sinceIso: string,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Router } from 'express'
|
||||
import {
|
||||
acceptAdminWorkOrder,
|
||||
assignAdminWorkOrderToWorker,
|
||||
unassignAdminWorkOrder,
|
||||
creditAdminWorkerWallet,
|
||||
createAdminMockWorkOrder,
|
||||
deductAdminWorkOrderPendingDeposit,
|
||||
@@ -361,6 +362,22 @@ router.post(
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/orders/:workOrderId/unassign',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
createJsonHandler((req) => unassignAdminWorkOrder(String(req.params.workOrderId || '')), {
|
||||
successMessage: '已取消指派',
|
||||
errorMessage: '取消指派失败',
|
||||
scope: '[admin/worker-platform/orders/:workOrderId/unassign]',
|
||||
audit: (req, data) => ({
|
||||
action: 'work_order_worker_unassigned',
|
||||
targetType: 'work_order',
|
||||
targetId: String(req.params.workOrderId || ''),
|
||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||
}),
|
||||
}),
|
||||
)
|
||||
|
||||
router.delete(
|
||||
'/worker-platform/orders/:workOrderId',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
acceptWorkOrderAndSettle,
|
||||
addWorkerWalletCredit,
|
||||
assignWorkOrderToWorker,
|
||||
unassignWorkOrder,
|
||||
countWorkerAcceptedOrders,
|
||||
countWorkerActiveOrders,
|
||||
countWorkCategoryUsages,
|
||||
@@ -1146,6 +1147,43 @@ export async function acceptAdminWorkOrder(workOrderId: number | string) {
|
||||
return { order: mapWorkOrderAdmin(updated || workOrder) }
|
||||
}
|
||||
|
||||
export async function unassignAdminWorkOrder(workOrderId: number | string) {
|
||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||
if (
|
||||
workOrder.status !== WORK_ORDER_STATUS.IN_PROGRESS ||
|
||||
!workOrder.assigned_worker_id
|
||||
) {
|
||||
throw createHttpError('只有进行中且已指派打手的订单可以取消指派', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_unassign_status_invalid',
|
||||
})
|
||||
}
|
||||
const now = nowIso()
|
||||
const { order: updated, failureReason } = await unassignWorkOrder({
|
||||
workOrderId: workOrder.id,
|
||||
now,
|
||||
})
|
||||
if (failureReason === 'work_order_not_in_progress') {
|
||||
throw createHttpError('订单状态已变化,无法取消指派', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_unassign_status_invalid',
|
||||
})
|
||||
}
|
||||
if (failureReason === 'work_order_no_worker') {
|
||||
throw createHttpError('该订单未指派打手', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_unassign_no_worker',
|
||||
})
|
||||
}
|
||||
if (!updated) {
|
||||
throw createHttpError('取消指派失败,订单状态可能已变化', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_unassign_conflict',
|
||||
})
|
||||
}
|
||||
return { order: mapWorkOrderAdmin(updated) }
|
||||
}
|
||||
|
||||
export async function getAdminWorkerPlatformSummary() {
|
||||
const [pendingWorkers, pendingMaterial, openOrders, inProgressOrders] = await Promise.all([
|
||||
listWorkerUsers({ page: 1, pageSize: 1, status: 'pending_review' }),
|
||||
|
||||
Reference in New Issue
Block a user