优化下架和撤单功能
This commit is contained in:
@@ -1295,6 +1295,97 @@ export async function unassignWorkOrder(input: {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function cancelAssignedWorkOrder(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 = 'cancelled',
|
||||||
|
assigned_worker_id = NULL,
|
||||||
|
assigned_at = NULL,
|
||||||
|
deadline_at = NULL,
|
||||||
|
published_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: 'cancelled_by_admin',
|
||||||
|
fromStatus: 'in_progress',
|
||||||
|
toStatus: 'cancelled',
|
||||||
|
payloadJson: JSON.stringify({ workerId, releaseAmount }),
|
||||||
|
now: input.now,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||||
|
failureReason: null,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function countWorkerCancellationsSince(
|
export async function countWorkerCancellationsSince(
|
||||||
workerId: number | string,
|
workerId: number | string,
|
||||||
sinceIso: string,
|
sinceIso: string,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
acceptAdminWorkOrder,
|
acceptAdminWorkOrder,
|
||||||
acceptAdminWorkOrders,
|
acceptAdminWorkOrders,
|
||||||
assignAdminWorkOrderToWorker,
|
assignAdminWorkOrderToWorker,
|
||||||
|
cancelAdminWorkOrder,
|
||||||
unassignAdminWorkOrder,
|
unassignAdminWorkOrder,
|
||||||
creditAdminWorkerWallet,
|
creditAdminWorkerWallet,
|
||||||
createAdminMockWorkOrder,
|
createAdminMockWorkOrder,
|
||||||
@@ -382,6 +383,22 @@ router.post(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/worker-platform/orders/:workOrderId/cancel',
|
||||||
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
|
createJsonHandler((req) => cancelAdminWorkOrder(String(req.params.workOrderId || '')), {
|
||||||
|
successMessage: '订单已撤单,打手额度已恢复',
|
||||||
|
errorMessage: '撤单失败',
|
||||||
|
scope: '[admin/worker-platform/orders/:workOrderId/cancel]',
|
||||||
|
audit: (req, data) => ({
|
||||||
|
action: 'work_order_cancelled',
|
||||||
|
targetType: 'work_order',
|
||||||
|
targetId: String(req.params.workOrderId || ''),
|
||||||
|
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
'/worker-platform/orders/:workOrderId',
|
'/worker-platform/orders/:workOrderId',
|
||||||
requireAdminRoles(['admin', 'operator']),
|
requireAdminRoles(['admin', 'operator']),
|
||||||
@@ -416,7 +433,7 @@ router.post(
|
|||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/:workOrderId/unpublish',
|
'/worker-platform/orders/:workOrderId/unpublish',
|
||||||
requireAdminRoles(['admin', 'operator']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
createJsonHandler((req) => unpublishAdminWorkOrder(String(req.params.workOrderId || '')), {
|
createJsonHandler((req) => unpublishAdminWorkOrder(String(req.params.workOrderId || '')), {
|
||||||
successMessage: '订单已撤回到未分配',
|
successMessage: '订单已撤回到未分配',
|
||||||
errorMessage: '下架接单订单失败',
|
errorMessage: '下架接单订单失败',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
acceptWorkOrderAndSettle,
|
acceptWorkOrderAndSettle,
|
||||||
addWorkerWalletCredit,
|
addWorkerWalletCredit,
|
||||||
assignWorkOrderToWorker,
|
assignWorkOrderToWorker,
|
||||||
|
cancelAssignedWorkOrder,
|
||||||
unassignWorkOrder,
|
unassignWorkOrder,
|
||||||
countWorkerAcceptedOrders,
|
countWorkerAcceptedOrders,
|
||||||
countWorkerActiveOrders,
|
countWorkerActiveOrders,
|
||||||
@@ -1240,6 +1241,42 @@ export async function unassignAdminWorkOrder(workOrderId: number | string) {
|
|||||||
return { order: mapWorkOrderAdmin(updated) }
|
return { order: mapWorkOrderAdmin(updated) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function cancelAdminWorkOrder(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_cancel_status_invalid',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const { order: updated, failureReason } = await cancelAssignedWorkOrder({
|
||||||
|
workOrderId: workOrder.id,
|
||||||
|
now: nowIso(),
|
||||||
|
})
|
||||||
|
if (failureReason === 'work_order_not_in_progress') {
|
||||||
|
throw createHttpError('订单状态已变化,无法撤单', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'work_order_cancel_status_invalid',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (failureReason === 'work_order_no_worker') {
|
||||||
|
throw createHttpError('该订单尚未被打手接取', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'work_order_cancel_no_worker',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!updated) {
|
||||||
|
throw createHttpError('撤单失败,订单状态可能已变化', {
|
||||||
|
statusCode: 409,
|
||||||
|
errorCode: 'work_order_cancel_conflict',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return { order: mapWorkOrderAdmin(updated) }
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAdminWorkerPlatformSummary() {
|
export async function getAdminWorkerPlatformSummary() {
|
||||||
const [pendingWorkers, pendingMaterial, openOrders, inProgressOrders] = await Promise.all([
|
const [pendingWorkers, pendingMaterial, openOrders, inProgressOrders] = await Promise.all([
|
||||||
listWorkerUsers({ page: 1, pageSize: 1, status: 'pending_review' }),
|
listWorkerUsers({ page: 1, pageSize: 1, status: 'pending_review' }),
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ import {
|
|||||||
acceptAdminWorkOrder,
|
acceptAdminWorkOrder,
|
||||||
acceptAdminWorkOrders,
|
acceptAdminWorkOrders,
|
||||||
assignAdminWorkOrderToWorker,
|
assignAdminWorkOrderToWorker,
|
||||||
|
cancelAdminWorkOrder,
|
||||||
createAdminMockWorkOrder,
|
createAdminMockWorkOrder,
|
||||||
unassignAdminWorkOrder,
|
|
||||||
creditAdminWorkerWallet,
|
creditAdminWorkerWallet,
|
||||||
deductAdminWorkOrderPendingDeposit,
|
deductAdminWorkOrderPendingDeposit,
|
||||||
deleteAdminWorkCategory,
|
deleteAdminWorkCategory,
|
||||||
@@ -692,25 +692,25 @@ export default function WorkOrdersPanel() {
|
|||||||
const assignedWorker = row.worker
|
const assignedWorker = row.worker
|
||||||
if (!assignedWorker) return
|
if (!assignedWorker) return
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: '取消指派该打手?',
|
title: '确认撤单?',
|
||||||
content: (
|
content: (
|
||||||
<span>
|
<span>
|
||||||
将 {assignedWorker.displayName || assignedWorker.username} 的接单撤回,订单回到未分配,
|
将取消 {assignedWorker.displayName || assignedWorker.username} 正在代练的订单,订单不会重新发布到大厅,
|
||||||
可重新指派给其他打手;冻结押金将退还。
|
打手接单额度和冻结押金将恢复。
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
okText: '确认取消指派',
|
okText: '确认撤单',
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
okButtonProps: { danger: true },
|
okButtonProps: { danger: true },
|
||||||
onOk: () =>
|
onOk: () =>
|
||||||
runAction(
|
runAction(
|
||||||
() => unassignAdminWorkOrder(row.workOrderId),
|
() => cancelAdminWorkOrder(row.workOrderId),
|
||||||
'已取消指派',
|
'已撤单并恢复打手额度',
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
取消指派
|
撤单
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{['pending_material', 'in_progress'].includes(row.status) ? (
|
{['pending_material', 'in_progress'].includes(row.status) ? (
|
||||||
|
|||||||
@@ -149,6 +149,12 @@ export function unassignAdminWorkOrder(workOrderId: number) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cancelAdminWorkOrder(workOrderId: number) {
|
||||||
|
return apiPost<{ order: WorkOrder }>(
|
||||||
|
`/api/v1/admin/worker-platform/orders/${workOrderId}/cancel`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function creditAdminWorkerWallet(
|
export function creditAdminWorkerWallet(
|
||||||
workerId: number,
|
workerId: number,
|
||||||
payload: { amount: number; note?: string },
|
payload: { amount: number; note?: string },
|
||||||
|
|||||||
Reference in New Issue
Block a user