增加取消订单重新可以发布
This commit is contained in:
@@ -319,6 +319,7 @@ export type WorkOrderCapabilities = {
|
||||
canAccept: boolean
|
||||
canMarkProblem: boolean
|
||||
canDelete: boolean
|
||||
canReopen: boolean
|
||||
canPublish: boolean
|
||||
canCancel: boolean
|
||||
canResolveProblem: boolean
|
||||
|
||||
@@ -1981,6 +1981,84 @@ export async function cancelAssignedWorkOrder(input: {
|
||||
})
|
||||
}
|
||||
|
||||
/** 将已取消工单恢复为未分配状态,不变更历史押金流水。 */
|
||||
export async function reopenCancelledWorkOrder(input: {
|
||||
workOrderId: number
|
||||
now: string
|
||||
actorName?: string
|
||||
}): Promise<{
|
||||
order: WorkOrderRow | null
|
||||
failureReason: 'work_order_not_cancelled' | 'work_order_has_active_shares' | 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 !== 'cancelled') {
|
||||
return { order: null, failureReason: 'work_order_not_cancelled' }
|
||||
}
|
||||
|
||||
const activeShareResult = await client.query<{ total: number }>(
|
||||
`
|
||||
SELECT COUNT(*)::int AS total
|
||||
FROM work_order_shares
|
||||
WHERE work_order_id = $1
|
||||
AND status != 'cancelled'
|
||||
`,
|
||||
[input.workOrderId],
|
||||
)
|
||||
if (Number(activeShareResult.rows[0]?.total || 0) > 0) {
|
||||
return { order: null, failureReason: 'work_order_has_active_shares' }
|
||||
}
|
||||
|
||||
await client.query(
|
||||
`
|
||||
UPDATE work_orders
|
||||
SET
|
||||
status = 'unassigned',
|
||||
assigned_worker_id = NULL,
|
||||
assigned_at = NULL,
|
||||
deadline_at = NULL,
|
||||
acceptance_json = '{}'::jsonb,
|
||||
draft_acceptance_json = '{}'::jsonb,
|
||||
submitted_at = NULL,
|
||||
accepted_at = NULL,
|
||||
problem_note = '',
|
||||
published_at = NULL,
|
||||
pinned_at = NULL,
|
||||
updated_at = $1
|
||||
WHERE id = $2
|
||||
`,
|
||||
[input.now, input.workOrderId],
|
||||
)
|
||||
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'admin',
|
||||
actorId: input.actorName || '',
|
||||
eventType: 'reopened_from_cancelled',
|
||||
fromStatus: 'cancelled',
|
||||
toStatus: 'unassigned',
|
||||
payloadJson: JSON.stringify({
|
||||
previousAssignedWorkerId: Number(workOrder.last_assigned_worker_id || 0) || null,
|
||||
}),
|
||||
now: input.now,
|
||||
})
|
||||
|
||||
return {
|
||||
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function countWorkerCancellationsSince(
|
||||
workerId: number | string,
|
||||
sinceIso: string,
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
markAdminWorkOrderProblem,
|
||||
pinAdminWorkOrder,
|
||||
publishAdminWorkOrder,
|
||||
reopenAdminCancelledWorkOrder,
|
||||
reprocessAdminKuaishouSendCodeWorkOrders,
|
||||
reviewAdminWorkerFinanceRequest,
|
||||
resolveAdminProblemWorkOrder,
|
||||
@@ -651,6 +652,29 @@ router.delete(
|
||||
}),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/orders/:workOrderId/reopen',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
createJsonHandler(
|
||||
(req) =>
|
||||
reopenAdminCancelledWorkOrder(
|
||||
String(req.params.workOrderId || ''),
|
||||
req.adminSession?.username || '',
|
||||
),
|
||||
{
|
||||
successMessage: '订单已恢复为未分配状态',
|
||||
errorMessage: '重新启用订单失败',
|
||||
scope: '[admin/worker-platform/orders/:workOrderId/reopen]',
|
||||
audit: (req, data) => ({
|
||||
action: 'work_order_reopened',
|
||||
targetType: 'work_order',
|
||||
targetId: String(req.params.workOrderId || ''),
|
||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||
}),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/orders/:workOrderId/publish',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
sumPendingUnfreezeByOrderIds,
|
||||
resolveProblemWorkOrder,
|
||||
reviewWorkerFinanceRequest,
|
||||
reopenCancelledWorkOrder,
|
||||
updateWorkOrder,
|
||||
updateWorkOrderBasic,
|
||||
updateWorkerUser,
|
||||
@@ -1795,6 +1796,37 @@ export async function deleteAdminWorkOrder(workOrderId: number | string) {
|
||||
return { deleted: true }
|
||||
}
|
||||
|
||||
export async function reopenAdminCancelledWorkOrder(workOrderId: number | string, actorName = '') {
|
||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||
if (!mapWorkOrderAdmin(workOrder).capabilities.canReopen) {
|
||||
throw createHttpError('只有没有有效拼单份额的已取消订单可以重新启用', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_reopen_status_invalid',
|
||||
})
|
||||
}
|
||||
|
||||
const { order: updated, failureReason } = await reopenCancelledWorkOrder({
|
||||
workOrderId: Number(workOrder.id),
|
||||
now: nowIso(),
|
||||
actorName,
|
||||
})
|
||||
if (failureReason === 'work_order_has_active_shares') {
|
||||
throw createHttpError('该订单仍有有效拼单份额,不能重新启用', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_reopen_has_active_shares',
|
||||
})
|
||||
}
|
||||
if (!updated) {
|
||||
throw createHttpError('订单状态已变化,无法重新启用', {
|
||||
statusCode: 409,
|
||||
errorCode: 'work_order_reopen_conflict',
|
||||
})
|
||||
}
|
||||
|
||||
publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true })
|
||||
return { order: mapWorkOrderAdmin(updated) }
|
||||
}
|
||||
|
||||
export async function publishAdminWorkOrder(workOrderId: number | string, actorName = '') {
|
||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||
if (workOrder.status !== WORK_ORDER_STATUS.UNASSIGNED) {
|
||||
|
||||
@@ -411,6 +411,7 @@ export function mapWorkOrderAdmin(workOrder: WorkOrderRow, shares?: WorkOrderSha
|
||||
WORK_ORDER_STATUS.UNASSIGNED,
|
||||
WORK_ORDER_STATUS.CANCELLED,
|
||||
].includes(workOrder.status as never),
|
||||
canReopen: workOrder.status === WORK_ORDER_STATUS.CANCELLED && !hasActiveShares,
|
||||
canPublish:
|
||||
workOrder.status === WORK_ORDER_STATUS.UNASSIGNED && Number(workOrder.reward_amount || 0) > 0,
|
||||
canCancel: status === WORK_ORDER_STATUS.IN_PROGRESS && Boolean(workOrder.assigned_worker_id),
|
||||
|
||||
@@ -734,6 +734,27 @@ test('后台拼单聚合状态同时收敛筛选语义和操作能力', () => {
|
||||
assert.equal(filled.capabilities.canUnpublish, false)
|
||||
})
|
||||
|
||||
test('已取消工单可重新启用,恢复未分配后可编辑和发布', () => {
|
||||
const cancelled = mapWorkOrderAdmin(buildWorkOrderRow({ status: 'cancelled' }))
|
||||
assert.equal(cancelled.capabilities.canReopen, true)
|
||||
assert.equal(cancelled.capabilities.canEdit, false)
|
||||
assert.equal(cancelled.capabilities.canPublish, false)
|
||||
|
||||
const restored = mapWorkOrderAdmin(buildWorkOrderRow({ status: 'unassigned' }))
|
||||
assert.equal(restored.capabilities.canReopen, false)
|
||||
assert.equal(restored.capabilities.canEdit, true)
|
||||
assert.equal(restored.capabilities.canPublish, true)
|
||||
|
||||
const cancelledSharing = mapWorkOrderAdmin(
|
||||
buildWorkOrderRow({
|
||||
status: 'cancelled',
|
||||
sharing_enabled: true,
|
||||
active_share_quantity: 1,
|
||||
}),
|
||||
)
|
||||
assert.equal(cancelledSharing.capabilities.canReopen, false)
|
||||
})
|
||||
|
||||
test('拼单打手汇总展示当前参与者并合并同一打手的份数', () => {
|
||||
const workers = mapWorkOrderSharingWorkers([
|
||||
buildWorkOrderShareRow({ worker_id: 1, quantity: 2, worker_username: 'alpha' }),
|
||||
|
||||
@@ -189,6 +189,8 @@ export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
||||
return { title: '问题单已处置:取消订单并扣除押金', color: 'orange' }
|
||||
|
||||
// —— 取消节点 ——
|
||||
case 'reopened_from_cancelled':
|
||||
return { title: '重新启用已取消订单,恢复为未分配状态', color: 'orange' }
|
||||
case 'cancelled_by_admin':
|
||||
return {
|
||||
title: note ? `取消订单(${note})` : '取消订单',
|
||||
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
markAdminWorkOrderProblem,
|
||||
pinAdminWorkOrder,
|
||||
publishAdminWorkOrder,
|
||||
reopenAdminCancelledWorkOrder,
|
||||
resolveAdminProblemWorkOrder,
|
||||
submitAdminWorkOrderMaterial,
|
||||
unpublishAdminWorkOrder,
|
||||
@@ -863,6 +864,26 @@ export default function WorkOrdersPanel() {
|
||||
删除
|
||||
</Button>
|
||||
) : null}
|
||||
{row.capabilities?.canReopen ? (
|
||||
<Button
|
||||
icon={<UndoOutlined />}
|
||||
onClick={() => {
|
||||
modal.confirm({
|
||||
title: '确认重新启用该订单?',
|
||||
content: '订单将恢复为未分配状态。请确认资料与金额后,再发布到抢单大厅。',
|
||||
okText: '重新启用',
|
||||
cancelText: '取消',
|
||||
onOk: () =>
|
||||
runAction(
|
||||
() => reopenAdminCancelledWorkOrder(row.workOrderId),
|
||||
'订单已恢复为未分配状态,可编辑后发布',
|
||||
),
|
||||
})
|
||||
}}
|
||||
>
|
||||
重新启用
|
||||
</Button>
|
||||
) : null}
|
||||
{row.sharing?.enabled || row.capabilities?.canConfigureSharing ? (
|
||||
<Button icon={<TeamOutlined />} onClick={() => openSharingConfigModal(row)}>
|
||||
{row.sharing?.enabled && !row.capabilities?.canConfigureSharing
|
||||
|
||||
@@ -391,6 +391,10 @@ export function publishAdminWorkOrder(workOrderId: number) {
|
||||
}>(`/api/v1/admin/worker-platform/orders/${workOrderId}/publish`)
|
||||
}
|
||||
|
||||
export function reopenAdminCancelledWorkOrder(workOrderId: number) {
|
||||
return apiPost<{ order: WorkOrder }>(`/api/v1/admin/worker-platform/orders/${workOrderId}/reopen`)
|
||||
}
|
||||
|
||||
export function updateAdminWorkOrder(
|
||||
workOrderId: number,
|
||||
payload: {
|
||||
|
||||
@@ -365,6 +365,7 @@ export type WorkOrder = {
|
||||
canAccept: boolean
|
||||
canMarkProblem: boolean
|
||||
canDelete: boolean
|
||||
canReopen: boolean
|
||||
canPublish: boolean
|
||||
canCancel: boolean
|
||||
canResolveProblem: boolean
|
||||
|
||||
Reference in New Issue
Block a user