已取消工单在列表与详情展示取消原因来源操作人与时间

This commit is contained in:
yml2213
2026-08-22 14:17:20 +08:00
parent 91008c98c4
commit f92dc6a4c9
5 changed files with 109 additions and 14 deletions
@@ -382,6 +382,12 @@ export type WorkOrderRow = {
category_name?: string category_name?: string
worker_username?: string worker_username?: string
worker_display_name?: string worker_display_name?: string
/** 取消信息,由工单查询从取消类事件推导,仅已取消工单有值。 */
cancel_reason?: string | null
cancel_event_type?: string | null
cancel_actor_type?: string | null
cancel_actor_id?: string | null
cancelled_at?: string | null
} }
export type ProblemWorkOrderResolutionAction = export type ProblemWorkOrderResolutionAction =
@@ -12,10 +12,43 @@ export const WORK_ORDER_SELECT = `
COALESCE(share_summary.pending_share_count, 0)::int AS pending_share_count, COALESCE(share_summary.pending_share_count, 0)::int AS pending_share_count,
wc.name AS category_name, wc.name AS category_name,
wu.username AS worker_username, wu.username AS worker_username,
wu.display_name AS worker_display_name wu.display_name AS worker_display_name,
cancel_info.cancel_reason,
cancel_info.cancel_event_type,
cancel_info.cancel_actor_type,
cancel_info.cancel_actor_id,
cancel_info.cancelled_at
FROM work_orders wo FROM work_orders wo
LEFT JOIN work_categories wc ON wc.id = wo.category_id LEFT JOIN work_categories wc ON wc.id = wo.category_id
LEFT JOIN worker_users wu ON wu.id = wo.assigned_worker_id LEFT JOIN worker_users wu ON wu.id = wo.assigned_worker_id
LEFT JOIN LATERAL (
SELECT
ev.event_type AS cancel_event_type,
ev.actor_type AS cancel_actor_type,
ev.actor_id AS cancel_actor_id,
ev.created_at AS cancelled_at,
BTRIM(COALESCE(
NULLIF(BTRIM(COALESCE(ev.payload_json->>'reason', '')), ''),
NULLIF(BTRIM(COALESCE(ev.payload_json->>'note', '')), ''),
CASE ev.event_type
WHEN 'timeout_cancel_release' THEN '任务超时未完成,系统自动取消并退还押金'
WHEN 'timeout_cancel_deduct' THEN '任务超时未完成,系统自动取消并扣除押金'
ELSE ''
END
)) AS cancel_reason
FROM work_order_events ev
WHERE ev.work_order_id = wo.id
AND wo.status = 'cancelled'
AND ev.event_type IN (
'cancelled_by_admin',
'problem_cancel_release',
'problem_cancel_deduct',
'timeout_cancel_release',
'timeout_cancel_deduct'
)
ORDER BY ev.id DESC
LIMIT 1
) cancel_info ON TRUE
LEFT JOIN LATERAL ( LEFT JOIN LATERAL (
SELECT SELECT
SUM(wos.quantity) FILTER (WHERE wos.status != 'cancelled') AS active_share_quantity, SUM(wos.quantity) FILTER (WHERE wos.status != 'cancelled') AS active_share_quantity,
@@ -114,6 +114,15 @@ export function mapWorkOrderSharingWorkers(shares: WorkOrderShareRow[]) {
return [...workers.values()] return [...workers.values()]
} }
/** 取消来源:由触发取消的事件类型推导,用于列表与详情展示。 */
function resolveWorkOrderCancelSource(cancelEventType: string | null | undefined) {
const eventType = String(cancelEventType || '').trim()
if (eventType === 'cancelled_by_admin') return '后台撤单'
if (eventType.startsWith('problem_')) return '问题单处置'
if (eventType.startsWith('timeout_')) return '超时自动取消'
return eventType || '未知来源'
}
export function mapWorkOrderAdmin(workOrder: WorkOrderRow, shares?: WorkOrderShareRow[]) { export function mapWorkOrderAdmin(workOrder: WorkOrderRow, shares?: WorkOrderShareRow[]) {
const activeShareQuantity = Math.max(0, Number(workOrder.active_share_quantity || 0)) const activeShareQuantity = Math.max(0, Number(workOrder.active_share_quantity || 0))
const pendingShareCount = Math.max(0, Number(workOrder.pending_share_count || 0)) const pendingShareCount = Math.max(0, Number(workOrder.pending_share_count || 0))
@@ -232,6 +241,16 @@ export function mapWorkOrderAdmin(workOrder: WorkOrderRow, shares?: WorkOrderSha
acceptance: mapAcceptanceForResponse(workOrder.acceptance_json), acceptance: mapAcceptanceForResponse(workOrder.acceptance_json),
draftAcceptance: mapAcceptanceForResponse(workOrder.draft_acceptance_json), draftAcceptance: mapAcceptanceForResponse(workOrder.draft_acceptance_json),
problemNote: workOrder.problem_note || '', problemNote: workOrder.problem_note || '',
cancel:
workOrder.status === WORK_ORDER_STATUS.CANCELLED
? {
reason: String(workOrder.cancel_reason || ''),
source: resolveWorkOrderCancelSource(workOrder.cancel_event_type),
actorType: String(workOrder.cancel_actor_type || ''),
actorName: String(workOrder.cancel_actor_id || ''),
cancelledAt: workOrder.cancelled_at || null,
}
: null,
worker: workOrder.assigned_worker_id worker: workOrder.assigned_worker_id
? { ? {
workerId: Number(workOrder.assigned_worker_id), workerId: Number(workOrder.assigned_worker_id),
@@ -896,21 +896,34 @@ export default function WorkOrdersPanel() {
title: '状态', title: '状态',
width: 150, width: 150,
render: (_, row) => { render: (_, row) => {
const cancelText =
row.cancel?.reason || (row.cancel ? `${row.cancel.source}(未记录原因)` : '')
return ( return (
<Space wrap size={4}> <div className="cell-stack">
<Tag color={resolveStatusColor(row.status)}>{formatStatus(row.status)}</Tag> <Space wrap size={4}>
{row.rawStatus === 'open' && row.pinnedAt ? ( <Tag color={resolveStatusColor(row.status)}>{formatStatus(row.status)}</Tag>
<Tag color="gold" icon={<PushpinOutlined />}> {row.rawStatus === 'open' && row.pinnedAt ? (
<Tag color="gold" icon={<PushpinOutlined />}>
</Tag>
</Tag>
) : null}
{row.acceptanceMode === 'friend_gift' ? <Tag color="purple"></Tag> : null}
{row.acceptanceMode === 'friend_gift' && row.gift ? (
<Tag color={resolveGiftPhaseColor(row.gift.phase)}>
{formatGiftPhase(row.gift.phase)}
</Tag>
) : null}
</Space>
{row.cancel ? (
<Typography.Text
type="secondary"
style={{ fontSize: 12, maxWidth: 140 }}
ellipsis={{ tooltip: `${row.cancel.source}${cancelText}` }}
>
{cancelText}
</Typography.Text>
) : null} ) : null}
{row.acceptanceMode === 'friend_gift' ? <Tag color="purple"></Tag> : null} </div>
{row.acceptanceMode === 'friend_gift' && row.gift ? (
<Tag color={resolveGiftPhaseColor(row.gift.phase)}>
{formatGiftPhase(row.gift.phase)}
</Tag>
) : null}
</Space>
) )
}, },
}, },
@@ -1418,6 +1431,22 @@ export default function WorkOrdersPanel() {
{String(detailOrder.problemNote || '').trim() || '-'} {String(detailOrder.problemNote || '').trim() || '-'}
</span> </span>
</Descriptions.Item> </Descriptions.Item>
{detailOrder.cancel ? (
<Descriptions.Item label="取消信息" span={2}>
<span className="worker-order-detail-value">
{detailOrder.cancel.source}
{detailOrder.cancel.cancelledAt
? ` · ${formatAdminDateTime(detailOrder.cancel.cancelledAt)}`
: ''}
{detailOrder.cancel.actorType === 'admin' && detailOrder.cancel.actorName
? ` · 操作人 ${detailOrder.cancel.actorName}`
: ''}
{detailOrder.cancel.reason
? ` · 原因:${detailOrder.cancel.reason}`
: ' · 原因未记录'}
</span>
</Descriptions.Item>
) : null}
</Descriptions> </Descriptions>
</Card> </Card>
@@ -548,6 +548,14 @@ export type WorkOrder = {
/** 打手本人尚未点击查看或操作过该工单。 */ /** 打手本人尚未点击查看或操作过该工单。 */
isUnread?: boolean isUnread?: boolean
problemNote: string problemNote: string
/** 取消信息,仅已取消工单返回。 */
cancel?: {
reason: string
source: string
actorType: string
actorName: string
cancelledAt: string | null
} | null
worker: null | { worker: null | {
workerId: number workerId: number
username: string username: string