接单工单新增流转记录(订单详情时间线)

- 后端:新增工单事件查询接口(管理端 + 打手端),事件带操作人显示名(管理员用户名 / 打手昵称 / 系统)
- 补齐关键节点事件:抢单接手(grabbed)、验收通过(accepted 带操作人)
- 管理端/打手端事件写入操作人(admin session username 贯通至事件)
- 前端:订单详情新增「流转记录」时间线,按订单生命周期节点展示(创建/发单/接手/资料/验收/问题/取消),时间+操作人+状态流转
This commit is contained in:
yml2213
2026-08-16 19:25:20 +08:00
parent 81ae04dae0
commit 950c4fd578
13 changed files with 699 additions and 92 deletions
@@ -1056,6 +1056,8 @@ export async function assignWorkOrderToWorker(input: {
maxActiveOrders: number
deadlineAt: string | null
now: string
/** 操作人显示名(管理员用户名等) */
actorName?: string
}): Promise<{
order: WorkOrderRow | null
failureReason:
@@ -1143,7 +1145,7 @@ export async function assignWorkOrderToWorker(input: {
await createWorkOrderEventWithClient(client, {
workOrderId: input.workOrderId,
actorType: 'admin',
actorId: '',
actorId: input.actorName || '',
eventType: 'assigned_by_admin',
fromStatus,
toStatus: 'in_progress',
@@ -1245,7 +1247,11 @@ export async function cancelWorkerWorkOrder(input: {
})
}
export async function unassignWorkOrder(input: { workOrderId: number; now: string }): Promise<{
export async function unassignWorkOrder(input: {
workOrderId: number
now: string
actorName?: string
}): Promise<{
order: WorkOrderRow | null
failureReason: 'work_order_not_in_progress' | 'work_order_no_worker' | null
}> {
@@ -1314,7 +1320,7 @@ export async function unassignWorkOrder(input: { workOrderId: number; now: strin
await createWorkOrderEventWithClient(client, {
workOrderId: input.workOrderId,
actorType: 'admin',
actorId: '',
actorId: input.actorName || '',
eventType: 'unassigned_by_admin',
fromStatus: 'in_progress',
toStatus: 'unassigned',
@@ -1333,6 +1339,7 @@ export async function returnAssignedWorkOrderToHall(input: {
workOrderId: number
now: string
reason?: string
actorName?: string
}): Promise<{
order: WorkOrderRow | null
failureReason: 'work_order_not_in_progress' | 'work_order_no_worker' | null
@@ -1403,7 +1410,7 @@ export async function returnAssignedWorkOrderToHall(input: {
await createWorkOrderEventWithClient(client, {
workOrderId: input.workOrderId,
actorType: 'admin',
actorId: '',
actorId: input.actorName || '',
eventType: 'returned_to_hall_by_admin',
fromStatus: 'in_progress',
toStatus: 'open',
@@ -1426,6 +1433,7 @@ export async function cancelAssignedWorkOrder(input: {
workOrderId: number
now: string
reason?: string
actorName?: string
}): Promise<{
order: WorkOrderRow | null
failureReason: 'work_order_not_in_progress' | 'work_order_no_worker' | null
@@ -1496,7 +1504,7 @@ export async function cancelAssignedWorkOrder(input: {
await createWorkOrderEventWithClient(client, {
workOrderId: input.workOrderId,
actorType: 'admin',
actorId: '',
actorId: input.actorName || '',
eventType: 'cancelled_by_admin',
fromStatus: 'in_progress',
toStatus: 'cancelled',
@@ -1537,6 +1545,10 @@ export async function acceptWorkOrderAndSettle(input: {
workOrderId: number
unfreezeDays: number
now: string
/** 操作人类型:admin / worker / system */
actorType?: string
/** 操作人显示名 */
actorName?: string
}): Promise<{
order: WorkOrderRow | null
failureReason: 'sharing_pending_submissions' | null
@@ -1809,8 +1821,8 @@ export async function acceptWorkOrderAndSettle(input: {
await createWorkOrderEventWithClient(client, {
workOrderId: input.workOrderId,
actorType: 'admin',
actorId: '',
actorType: input.actorType || 'admin',
actorId: input.actorName || '',
eventType: 'accepted',
fromStatus: 'pending_acceptance',
toStatus: 'accepted',
@@ -1834,6 +1846,7 @@ export async function resolveProblemWorkOrder(input: {
action: ProblemWorkOrderResolutionAction
note: string
now: string
actorName?: string
}): Promise<WorkOrderRow | null> {
return withTransaction(async (client) => {
const currentResult = await client.query<WorkOrderRow>(
@@ -1937,7 +1950,7 @@ export async function resolveProblemWorkOrder(input: {
await createWorkOrderEventWithClient(client, {
workOrderId: input.workOrderId,
actorType: 'admin',
actorId: '',
actorId: input.actorName || '',
eventType: `problem_${input.action}`,
fromStatus: 'problem',
toStatus: targetStatus,
@@ -2409,6 +2422,33 @@ export async function countWorkerTimeoutEvents(workerId: number | string): Promi
return counts.get(Number(workerId)) || 0
}
export type WorkOrderEventRow = {
id: number
work_order_id: number
actor_type: string
actor_id: string
event_type: string
from_status: string
to_status: string
payload_json: string | Record<string, unknown>
created_at: string
}
export async function listWorkOrderEventsByOrderId(
workOrderId: number | string,
): Promise<WorkOrderEventRow[]> {
const result = await query<WorkOrderEventRow>(
`
SELECT *
FROM work_order_events
WHERE work_order_id = $1
ORDER BY id ASC
`,
[Number(workOrderId)],
)
return result.rows
}
export async function createWorkOrderEvent(input: {
workOrderId: number
actorType: string