接单工单新增流转记录(订单详情时间线)
- 后端:新增工单事件查询接口(管理端 + 打手端),事件带操作人显示名(管理员用户名 / 打手昵称 / 系统) - 补齐关键节点事件:抢单接手(grabbed)、验收通过(accepted 带操作人) - 管理端/打手端事件写入操作人(admin session username 贯通至事件) - 前端:订单详情新增「流转记录」时间线,按订单生命周期节点展示(创建/发单/接手/资料/验收/问题/取消),时间+操作人+状态流转
This commit is contained in:
@@ -1056,6 +1056,8 @@ export async function assignWorkOrderToWorker(input: {
|
|||||||
maxActiveOrders: number
|
maxActiveOrders: number
|
||||||
deadlineAt: string | null
|
deadlineAt: string | null
|
||||||
now: string
|
now: string
|
||||||
|
/** 操作人显示名(管理员用户名等) */
|
||||||
|
actorName?: string
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
order: WorkOrderRow | null
|
order: WorkOrderRow | null
|
||||||
failureReason:
|
failureReason:
|
||||||
@@ -1143,7 +1145,7 @@ export async function assignWorkOrderToWorker(input: {
|
|||||||
await createWorkOrderEventWithClient(client, {
|
await createWorkOrderEventWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: input.actorName || '',
|
||||||
eventType: 'assigned_by_admin',
|
eventType: 'assigned_by_admin',
|
||||||
fromStatus,
|
fromStatus,
|
||||||
toStatus: 'in_progress',
|
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
|
order: WorkOrderRow | null
|
||||||
failureReason: 'work_order_not_in_progress' | 'work_order_no_worker' | 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, {
|
await createWorkOrderEventWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: input.actorName || '',
|
||||||
eventType: 'unassigned_by_admin',
|
eventType: 'unassigned_by_admin',
|
||||||
fromStatus: 'in_progress',
|
fromStatus: 'in_progress',
|
||||||
toStatus: 'unassigned',
|
toStatus: 'unassigned',
|
||||||
@@ -1333,6 +1339,7 @@ export async function returnAssignedWorkOrderToHall(input: {
|
|||||||
workOrderId: number
|
workOrderId: number
|
||||||
now: string
|
now: string
|
||||||
reason?: string
|
reason?: string
|
||||||
|
actorName?: string
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
order: WorkOrderRow | null
|
order: WorkOrderRow | null
|
||||||
failureReason: 'work_order_not_in_progress' | 'work_order_no_worker' | null
|
failureReason: 'work_order_not_in_progress' | 'work_order_no_worker' | null
|
||||||
@@ -1403,7 +1410,7 @@ export async function returnAssignedWorkOrderToHall(input: {
|
|||||||
await createWorkOrderEventWithClient(client, {
|
await createWorkOrderEventWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: input.actorName || '',
|
||||||
eventType: 'returned_to_hall_by_admin',
|
eventType: 'returned_to_hall_by_admin',
|
||||||
fromStatus: 'in_progress',
|
fromStatus: 'in_progress',
|
||||||
toStatus: 'open',
|
toStatus: 'open',
|
||||||
@@ -1426,6 +1433,7 @@ export async function cancelAssignedWorkOrder(input: {
|
|||||||
workOrderId: number
|
workOrderId: number
|
||||||
now: string
|
now: string
|
||||||
reason?: string
|
reason?: string
|
||||||
|
actorName?: string
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
order: WorkOrderRow | null
|
order: WorkOrderRow | null
|
||||||
failureReason: 'work_order_not_in_progress' | 'work_order_no_worker' | null
|
failureReason: 'work_order_not_in_progress' | 'work_order_no_worker' | null
|
||||||
@@ -1496,7 +1504,7 @@ export async function cancelAssignedWorkOrder(input: {
|
|||||||
await createWorkOrderEventWithClient(client, {
|
await createWorkOrderEventWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: input.actorName || '',
|
||||||
eventType: 'cancelled_by_admin',
|
eventType: 'cancelled_by_admin',
|
||||||
fromStatus: 'in_progress',
|
fromStatus: 'in_progress',
|
||||||
toStatus: 'cancelled',
|
toStatus: 'cancelled',
|
||||||
@@ -1537,6 +1545,10 @@ export async function acceptWorkOrderAndSettle(input: {
|
|||||||
workOrderId: number
|
workOrderId: number
|
||||||
unfreezeDays: number
|
unfreezeDays: number
|
||||||
now: string
|
now: string
|
||||||
|
/** 操作人类型:admin / worker / system */
|
||||||
|
actorType?: string
|
||||||
|
/** 操作人显示名 */
|
||||||
|
actorName?: string
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
order: WorkOrderRow | null
|
order: WorkOrderRow | null
|
||||||
failureReason: 'sharing_pending_submissions' | null
|
failureReason: 'sharing_pending_submissions' | null
|
||||||
@@ -1809,8 +1821,8 @@ export async function acceptWorkOrderAndSettle(input: {
|
|||||||
|
|
||||||
await createWorkOrderEventWithClient(client, {
|
await createWorkOrderEventWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
actorType: 'admin',
|
actorType: input.actorType || 'admin',
|
||||||
actorId: '',
|
actorId: input.actorName || '',
|
||||||
eventType: 'accepted',
|
eventType: 'accepted',
|
||||||
fromStatus: 'pending_acceptance',
|
fromStatus: 'pending_acceptance',
|
||||||
toStatus: 'accepted',
|
toStatus: 'accepted',
|
||||||
@@ -1834,6 +1846,7 @@ export async function resolveProblemWorkOrder(input: {
|
|||||||
action: ProblemWorkOrderResolutionAction
|
action: ProblemWorkOrderResolutionAction
|
||||||
note: string
|
note: string
|
||||||
now: string
|
now: string
|
||||||
|
actorName?: string
|
||||||
}): Promise<WorkOrderRow | null> {
|
}): Promise<WorkOrderRow | null> {
|
||||||
return withTransaction(async (client) => {
|
return withTransaction(async (client) => {
|
||||||
const currentResult = await client.query<WorkOrderRow>(
|
const currentResult = await client.query<WorkOrderRow>(
|
||||||
@@ -1937,7 +1950,7 @@ export async function resolveProblemWorkOrder(input: {
|
|||||||
await createWorkOrderEventWithClient(client, {
|
await createWorkOrderEventWithClient(client, {
|
||||||
workOrderId: input.workOrderId,
|
workOrderId: input.workOrderId,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: input.actorName || '',
|
||||||
eventType: `problem_${input.action}`,
|
eventType: `problem_${input.action}`,
|
||||||
fromStatus: 'problem',
|
fromStatus: 'problem',
|
||||||
toStatus: targetStatus,
|
toStatus: targetStatus,
|
||||||
@@ -2409,6 +2422,33 @@ export async function countWorkerTimeoutEvents(workerId: number | string): Promi
|
|||||||
return counts.get(Number(workerId)) || 0
|
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: {
|
export async function createWorkOrderEvent(input: {
|
||||||
workOrderId: number
|
workOrderId: number
|
||||||
actorType: string
|
actorType: string
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
deleteAdminWorkerLevel,
|
deleteAdminWorkerLevel,
|
||||||
getAdminWorkerFinanceConfig,
|
getAdminWorkerFinanceConfig,
|
||||||
getAdminWorkerPlatformSummary,
|
getAdminWorkerPlatformSummary,
|
||||||
|
getAdminWorkOrderEvents,
|
||||||
getAdminWorkOrderSharing,
|
getAdminWorkOrderSharing,
|
||||||
listAdminWorkerFinanceRequests,
|
listAdminWorkerFinanceRequests,
|
||||||
listAdminWorkCategories,
|
listAdminWorkCategories,
|
||||||
@@ -311,7 +312,12 @@ router.post(
|
|||||||
'/worker-platform/orders/:workOrderId/material',
|
'/worker-platform/orders/:workOrderId/material',
|
||||||
requireAdminRoles(['admin', 'operator', 'support']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
createJsonHandler(
|
createJsonHandler(
|
||||||
(req) => submitAdminWorkOrderMaterial(String(req.params.workOrderId || ''), req.body || {}),
|
(req) =>
|
||||||
|
submitAdminWorkOrderMaterial(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.body || {},
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
{
|
{
|
||||||
successMessage: '订单资料已保存',
|
successMessage: '订单资料已保存',
|
||||||
errorMessage: '补充订单资料失败',
|
errorMessage: '补充订单资料失败',
|
||||||
@@ -333,6 +339,7 @@ router.put(
|
|||||||
(req) =>
|
(req) =>
|
||||||
updateAdminWorkOrder(String(req.params.workOrderId || ''), req.body || {}, {
|
updateAdminWorkOrder(String(req.params.workOrderId || ''), req.body || {}, {
|
||||||
canEditRequirementFields: req.adminSession?.role !== 'support',
|
canEditRequirementFields: req.adminSession?.role !== 'support',
|
||||||
|
actorName: req.adminSession?.username || '',
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
successMessage: '工单信息已保存',
|
successMessage: '工单信息已保存',
|
||||||
@@ -352,7 +359,12 @@ router.post(
|
|||||||
'/worker-platform/orders/:workOrderId/assign',
|
'/worker-platform/orders/:workOrderId/assign',
|
||||||
requireAdminRoles(['admin', 'operator', 'support']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
createJsonHandler(
|
createJsonHandler(
|
||||||
(req) => assignAdminWorkOrderToWorker(String(req.params.workOrderId || ''), req.body || {}),
|
(req) =>
|
||||||
|
assignAdminWorkOrderToWorker(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.body || {},
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
{
|
{
|
||||||
successMessage: '已指派打手',
|
successMessage: '已指派打手',
|
||||||
errorMessage: '指派打手失败',
|
errorMessage: '指派打手失败',
|
||||||
@@ -370,7 +382,13 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/:workOrderId/unassign',
|
'/worker-platform/orders/:workOrderId/unassign',
|
||||||
requireAdminRoles(['admin', 'operator', 'support']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
createJsonHandler((req) => unassignAdminWorkOrder(String(req.params.workOrderId || '')), {
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
unassignAdminWorkOrder(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
|
{
|
||||||
successMessage: '已取消指派',
|
successMessage: '已取消指派',
|
||||||
errorMessage: '取消指派失败',
|
errorMessage: '取消指派失败',
|
||||||
scope: '[admin/worker-platform/orders/:workOrderId/unassign]',
|
scope: '[admin/worker-platform/orders/:workOrderId/unassign]',
|
||||||
@@ -380,14 +398,20 @@ router.post(
|
|||||||
targetId: String(req.params.workOrderId || ''),
|
targetId: String(req.params.workOrderId || ''),
|
||||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||||
}),
|
}),
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/:workOrderId/cancel',
|
'/worker-platform/orders/:workOrderId/cancel',
|
||||||
requireAdminRoles(['admin', 'operator', 'support']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
createJsonHandler(
|
createJsonHandler(
|
||||||
(req) => cancelAdminWorkOrder(String(req.params.workOrderId || ''), req.body || {}),
|
(req) =>
|
||||||
|
cancelAdminWorkOrder(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.body || {},
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
{
|
{
|
||||||
successMessage: '撤单处理完成,打手额度已恢复',
|
successMessage: '撤单处理完成,打手额度已恢复',
|
||||||
errorMessage: '撤单失败',
|
errorMessage: '撤单失败',
|
||||||
@@ -424,7 +448,10 @@ router.delete(
|
|||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/:workOrderId/publish',
|
'/worker-platform/orders/:workOrderId/publish',
|
||||||
requireAdminRoles(['admin', 'operator', 'support']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
createJsonHandler((req) => publishAdminWorkOrder(String(req.params.workOrderId || '')), {
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
publishAdminWorkOrder(String(req.params.workOrderId || ''), req.adminSession?.username || ''),
|
||||||
|
{
|
||||||
successMessage: '订单已发布到抢单大厅',
|
successMessage: '订单已发布到抢单大厅',
|
||||||
errorMessage: '发布接单订单失败',
|
errorMessage: '发布接单订单失败',
|
||||||
scope: '[admin/worker-platform/orders/:workOrderId/publish]',
|
scope: '[admin/worker-platform/orders/:workOrderId/publish]',
|
||||||
@@ -434,13 +461,20 @@ router.post(
|
|||||||
targetId: String(req.params.workOrderId || ''),
|
targetId: String(req.params.workOrderId || ''),
|
||||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||||
}),
|
}),
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/:workOrderId/unpublish',
|
'/worker-platform/orders/:workOrderId/unpublish',
|
||||||
requireAdminRoles(['admin', 'operator', 'support']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
createJsonHandler((req) => unpublishAdminWorkOrder(String(req.params.workOrderId || '')), {
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
unpublishAdminWorkOrder(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
|
{
|
||||||
successMessage: '订单已撤回到未分配',
|
successMessage: '订单已撤回到未分配',
|
||||||
errorMessage: '下架接单订单失败',
|
errorMessage: '下架接单订单失败',
|
||||||
scope: '[admin/worker-platform/orders/:workOrderId/unpublish]',
|
scope: '[admin/worker-platform/orders/:workOrderId/unpublish]',
|
||||||
@@ -450,14 +484,20 @@ router.post(
|
|||||||
targetId: String(req.params.workOrderId || ''),
|
targetId: String(req.params.workOrderId || ''),
|
||||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||||
}),
|
}),
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/:workOrderId/pin',
|
'/worker-platform/orders/:workOrderId/pin',
|
||||||
requireAdminRoles(['admin', 'operator', 'support']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
createJsonHandler(
|
createJsonHandler(
|
||||||
(req) => pinAdminWorkOrder(String(req.params.workOrderId || ''), req.body || {}),
|
(req) =>
|
||||||
|
pinAdminWorkOrder(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.body || {},
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
{
|
{
|
||||||
successMessage: '置顶状态已更新',
|
successMessage: '置顶状态已更新',
|
||||||
errorMessage: '更新置顶状态失败',
|
errorMessage: '更新置顶状态失败',
|
||||||
@@ -482,11 +522,26 @@ router.get(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/worker-platform/orders/:workOrderId/events',
|
||||||
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
|
createJsonHandler((req) => getAdminWorkOrderEvents(String(req.params.workOrderId || '')), {
|
||||||
|
successMessage: 'ok',
|
||||||
|
errorMessage: '读取工单流转记录失败',
|
||||||
|
scope: '[admin/worker-platform/orders/:workOrderId/events]',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/:workOrderId/sharing',
|
'/worker-platform/orders/:workOrderId/sharing',
|
||||||
requireAdminRoles(['admin', 'operator']),
|
requireAdminRoles(['admin', 'operator']),
|
||||||
createJsonHandler(
|
createJsonHandler(
|
||||||
(req) => updateAdminWorkOrderSharing(String(req.params.workOrderId || ''), req.body || {}),
|
(req) =>
|
||||||
|
updateAdminWorkOrderSharing(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.body || {},
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
{
|
{
|
||||||
successMessage: '拼单配置已保存',
|
successMessage: '拼单配置已保存',
|
||||||
errorMessage: '保存拼单配置失败',
|
errorMessage: '保存拼单配置失败',
|
||||||
@@ -505,7 +560,12 @@ router.post(
|
|||||||
'/worker-platform/orders/:workOrderId/problem',
|
'/worker-platform/orders/:workOrderId/problem',
|
||||||
requireAdminRoles(['admin', 'operator', 'support']),
|
requireAdminRoles(['admin', 'operator', 'support']),
|
||||||
createJsonHandler(
|
createJsonHandler(
|
||||||
(req) => markAdminWorkOrderProblem(String(req.params.workOrderId || ''), req.body || {}),
|
(req) =>
|
||||||
|
markAdminWorkOrderProblem(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.body || {},
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
{
|
{
|
||||||
successMessage: '订单已标记为问题单',
|
successMessage: '订单已标记为问题单',
|
||||||
errorMessage: '标记问题单失败',
|
errorMessage: '标记问题单失败',
|
||||||
@@ -524,7 +584,12 @@ router.post(
|
|||||||
'/worker-platform/orders/:workOrderId/problem/resolve',
|
'/worker-platform/orders/:workOrderId/problem/resolve',
|
||||||
requireAdminRoles(['admin', 'operator']),
|
requireAdminRoles(['admin', 'operator']),
|
||||||
createJsonHandler(
|
createJsonHandler(
|
||||||
(req) => resolveAdminProblemWorkOrder(String(req.params.workOrderId || ''), req.body || {}),
|
(req) =>
|
||||||
|
resolveAdminProblemWorkOrder(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.body || {},
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
{
|
{
|
||||||
successMessage: '问题单已处置',
|
successMessage: '问题单已处置',
|
||||||
errorMessage: '处置问题单失败',
|
errorMessage: '处置问题单失败',
|
||||||
@@ -542,7 +607,10 @@ router.post(
|
|||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/:workOrderId/accept',
|
'/worker-platform/orders/:workOrderId/accept',
|
||||||
requireAdminRoles(['admin', 'operator']),
|
requireAdminRoles(['admin', 'operator']),
|
||||||
createJsonHandler((req) => acceptAdminWorkOrder(String(req.params.workOrderId || '')), {
|
createJsonHandler(
|
||||||
|
(req) =>
|
||||||
|
acceptAdminWorkOrder(String(req.params.workOrderId || ''), req.adminSession?.username || ''),
|
||||||
|
{
|
||||||
successMessage: '订单已验收通过',
|
successMessage: '订单已验收通过',
|
||||||
errorMessage: '验收接单订单失败',
|
errorMessage: '验收接单订单失败',
|
||||||
scope: '[admin/worker-platform/orders/:workOrderId/accept]',
|
scope: '[admin/worker-platform/orders/:workOrderId/accept]',
|
||||||
@@ -552,13 +620,16 @@ router.post(
|
|||||||
targetId: String(req.params.workOrderId || ''),
|
targetId: String(req.params.workOrderId || ''),
|
||||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||||
}),
|
}),
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/worker-platform/orders/accept-batch',
|
'/worker-platform/orders/accept-batch',
|
||||||
requireAdminRoles(['admin', 'operator']),
|
requireAdminRoles(['admin', 'operator']),
|
||||||
createJsonHandler((req) => acceptAdminWorkOrders(req.body || {}), {
|
createJsonHandler(
|
||||||
|
(req) => acceptAdminWorkOrders(req.body || {}, req.adminSession?.username || ''),
|
||||||
|
{
|
||||||
successMessage: '选中订单已验收通过',
|
successMessage: '选中订单已验收通过',
|
||||||
errorMessage: '批量验收接单订单失败',
|
errorMessage: '批量验收接单订单失败',
|
||||||
scope: '[admin/worker-platform/orders/accept-batch]',
|
scope: '[admin/worker-platform/orders/accept-batch]',
|
||||||
@@ -570,7 +641,8 @@ router.post(
|
|||||||
: '',
|
: '',
|
||||||
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
|
||||||
}),
|
}),
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
@@ -578,7 +650,11 @@ router.post(
|
|||||||
requireAdminRoles(['admin', 'operator']),
|
requireAdminRoles(['admin', 'operator']),
|
||||||
createJsonHandler(
|
createJsonHandler(
|
||||||
(req) =>
|
(req) =>
|
||||||
deductAdminWorkOrderPendingDeposit(String(req.params.workOrderId || ''), req.body || {}),
|
deductAdminWorkOrderPendingDeposit(
|
||||||
|
String(req.params.workOrderId || ''),
|
||||||
|
req.body || {},
|
||||||
|
req.adminSession?.username || '',
|
||||||
|
),
|
||||||
{
|
{
|
||||||
successMessage: '待解冻押金已扣减',
|
successMessage: '待解冻押金已扣减',
|
||||||
errorMessage: '扣减待解冻押金失败',
|
errorMessage: '扣减待解冻押金失败',
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
listWorkerProfileWalletLedgers,
|
listWorkerProfileWalletLedgers,
|
||||||
listWorkerHallOrders,
|
listWorkerHallOrders,
|
||||||
listWorkerMyOrders,
|
listWorkerMyOrders,
|
||||||
|
getWorkerMyOrderEvents,
|
||||||
loginWorker,
|
loginWorker,
|
||||||
logoutWorkerSession,
|
logoutWorkerSession,
|
||||||
registerWorker,
|
registerWorker,
|
||||||
@@ -252,6 +253,20 @@ router.get(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/orders/:workOrderId/events',
|
||||||
|
requireActiveWorker,
|
||||||
|
createRouteHandler(
|
||||||
|
(req) =>
|
||||||
|
getWorkerMyOrderEvents(String(req.params.workOrderId || ''), getRequiredWorkerSession(req)),
|
||||||
|
{
|
||||||
|
successMessage: 'ok',
|
||||||
|
errorMessage: '读取订单流转记录失败',
|
||||||
|
scope: '[worker/orders/:workOrderId/events]',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/orders/:workOrderId/note',
|
'/orders/:workOrderId/note',
|
||||||
requireActiveWorker,
|
requireActiveWorker,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
listAllWorkCategories,
|
listAllWorkCategories,
|
||||||
listWorkerFinanceRequests,
|
listWorkerFinanceRequests,
|
||||||
listWorkOrders,
|
listWorkOrders,
|
||||||
|
listWorkOrderEventsByOrderId,
|
||||||
listWorkProductRules,
|
listWorkProductRules,
|
||||||
listWorkerLevels,
|
listWorkerLevels,
|
||||||
listWorkerUsers,
|
listWorkerUsers,
|
||||||
@@ -91,6 +92,7 @@ import {
|
|||||||
normalizeUploadedFiles,
|
normalizeUploadedFiles,
|
||||||
normalizeWorkerType,
|
normalizeWorkerType,
|
||||||
resolveFreezeDepositAmount,
|
resolveFreezeDepositAmount,
|
||||||
|
mapWorkOrderEvents,
|
||||||
resolveMatchingProductRule,
|
resolveMatchingProductRule,
|
||||||
resolveRequirementFields,
|
resolveRequirementFields,
|
||||||
resolveSkuNameQuantity,
|
resolveSkuNameQuantity,
|
||||||
@@ -386,6 +388,7 @@ export async function reviewAdminWorkerUser(workerId: number | string, payload:
|
|||||||
export async function assignAdminWorkOrderToWorker(
|
export async function assignAdminWorkOrderToWorker(
|
||||||
workOrderId: number | string,
|
workOrderId: number | string,
|
||||||
payload: JsonObject = {},
|
payload: JsonObject = {},
|
||||||
|
actorName = '',
|
||||||
) {
|
) {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (!['unassigned', 'open'].includes(workOrder.status) || workOrder.assigned_worker_id) {
|
if (!['unassigned', 'open'].includes(workOrder.status) || workOrder.assigned_worker_id) {
|
||||||
@@ -430,7 +433,7 @@ export async function assignAdminWorkOrderToWorker(
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: actorName,
|
||||||
eventType: 'assign_voucher_consume_failed',
|
eventType: 'assign_voucher_consume_failed',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: workOrder.status,
|
toStatus: workOrder.status,
|
||||||
@@ -452,6 +455,7 @@ export async function assignAdminWorkOrderToWorker(
|
|||||||
maxActiveOrders: permissions.maxActiveOrders,
|
maxActiveOrders: permissions.maxActiveOrders,
|
||||||
deadlineAt: resolveAssignedDeadlineAt(Number(workOrder.timeout_minutes || 0), now),
|
deadlineAt: resolveAssignedDeadlineAt(Number(workOrder.timeout_minutes || 0), now),
|
||||||
now,
|
now,
|
||||||
|
actorName,
|
||||||
})
|
})
|
||||||
if (failureReason === 'work_order_not_assignable') {
|
if (failureReason === 'work_order_not_assignable') {
|
||||||
throw createHttpError(
|
throw createHttpError(
|
||||||
@@ -595,6 +599,12 @@ export async function reviewAdminWorkerFinanceRequest(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getAdminWorkOrderEvents(workOrderId: number | string) {
|
||||||
|
await getRequiredWorkOrder(workOrderId)
|
||||||
|
const events = await listWorkOrderEventsByOrderId(workOrderId)
|
||||||
|
return { items: await mapWorkOrderEvents(events) }
|
||||||
|
}
|
||||||
|
|
||||||
export async function listAdminWorkOrders(query: JsonObject = {}) {
|
export async function listAdminWorkOrders(query: JsonObject = {}) {
|
||||||
const page = normalizePage(query.page)
|
const page = normalizePage(query.page)
|
||||||
const pageSize = normalizePageSize(query.pageSize)
|
const pageSize = normalizePageSize(query.pageSize)
|
||||||
@@ -640,6 +650,7 @@ export async function listAdminWorkOrders(query: JsonObject = {}) {
|
|||||||
export async function deductAdminWorkOrderPendingDeposit(
|
export async function deductAdminWorkOrderPendingDeposit(
|
||||||
workOrderId: number | string,
|
workOrderId: number | string,
|
||||||
payload: JsonObject = {},
|
payload: JsonObject = {},
|
||||||
|
actorName = '',
|
||||||
) {
|
) {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (workOrder.status !== WORK_ORDER_STATUS.ACCEPTED) {
|
if (workOrder.status !== WORK_ORDER_STATUS.ACCEPTED) {
|
||||||
@@ -683,7 +694,7 @@ export async function deductAdminWorkOrderPendingDeposit(
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: actorName,
|
||||||
eventType: 'pending_deposit_deducted',
|
eventType: 'pending_deposit_deducted',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: workOrder.status,
|
toStatus: workOrder.status,
|
||||||
@@ -705,6 +716,7 @@ export async function getAdminWorkOrderSharing(workOrderId: number | string) {
|
|||||||
export async function updateAdminWorkOrderSharing(
|
export async function updateAdminWorkOrderSharing(
|
||||||
workOrderId: number | string,
|
workOrderId: number | string,
|
||||||
payload: JsonObject = {},
|
payload: JsonObject = {},
|
||||||
|
actorName = '',
|
||||||
) {
|
) {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (
|
if (
|
||||||
@@ -751,7 +763,7 @@ export async function updateAdminWorkOrderSharing(
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: actorName,
|
||||||
eventType: 'sharing_config_updated',
|
eventType: 'sharing_config_updated',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: updated?.status || workOrder.status,
|
toStatus: updated?.status || workOrder.status,
|
||||||
@@ -780,6 +792,7 @@ function resolveSharingQuantity(
|
|||||||
export async function submitAdminWorkOrderMaterial(
|
export async function submitAdminWorkOrderMaterial(
|
||||||
workOrderId: number | string,
|
workOrderId: number | string,
|
||||||
payload: JsonObject = {},
|
payload: JsonObject = {},
|
||||||
|
actorName = '',
|
||||||
) {
|
) {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (
|
if (
|
||||||
@@ -841,7 +854,7 @@ export async function submitAdminWorkOrderMaterial(
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: actorName,
|
||||||
eventType:
|
eventType:
|
||||||
workOrder.status === WORK_ORDER_STATUS.IN_PROGRESS
|
workOrder.status === WORK_ORDER_STATUS.IN_PROGRESS
|
||||||
? 'material_updated'
|
? 'material_updated'
|
||||||
@@ -942,7 +955,7 @@ export async function createAdminMockWorkOrder(payload: JsonObject = {}) {
|
|||||||
export async function updateAdminWorkOrder(
|
export async function updateAdminWorkOrder(
|
||||||
workOrderId: number | string,
|
workOrderId: number | string,
|
||||||
payload: JsonObject = {},
|
payload: JsonObject = {},
|
||||||
options: { canEditRequirementFields?: boolean } = {},
|
options: { canEditRequirementFields?: boolean; actorName?: string } = {},
|
||||||
) {
|
) {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (
|
if (
|
||||||
@@ -1006,7 +1019,7 @@ export async function updateAdminWorkOrder(
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: options.actorName || '',
|
||||||
eventType: 'edited',
|
eventType: 'edited',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: workOrder.status,
|
toStatus: workOrder.status,
|
||||||
@@ -1061,7 +1074,7 @@ export async function deleteAdminWorkOrder(workOrderId: number | string) {
|
|||||||
return { deleted: true }
|
return { deleted: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function publishAdminWorkOrder(workOrderId: number | string) {
|
export async function publishAdminWorkOrder(workOrderId: number | string, actorName = '') {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (workOrder.status !== WORK_ORDER_STATUS.UNASSIGNED) {
|
if (workOrder.status !== WORK_ORDER_STATUS.UNASSIGNED) {
|
||||||
throw createHttpError('只有未分配订单可以发布到大厅', {
|
throw createHttpError('只有未分配订单可以发布到大厅', {
|
||||||
@@ -1082,7 +1095,7 @@ export async function publishAdminWorkOrder(workOrderId: number | string) {
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: actorName,
|
||||||
eventType: 'publish_voucher_consume_failed',
|
eventType: 'publish_voucher_consume_failed',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: workOrder.status,
|
toStatus: workOrder.status,
|
||||||
@@ -1117,7 +1130,7 @@ export async function publishAdminWorkOrder(workOrderId: number | string) {
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: actorName,
|
||||||
eventType: 'published',
|
eventType: 'published',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: WORK_ORDER_STATUS.OPEN,
|
toStatus: WORK_ORDER_STATUS.OPEN,
|
||||||
@@ -1127,7 +1140,7 @@ export async function publishAdminWorkOrder(workOrderId: number | string) {
|
|||||||
return { order: mapWorkOrderAdmin(updated), voucherConsume }
|
return { order: mapWorkOrderAdmin(updated), voucherConsume }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unpublishAdminWorkOrder(workOrderId: number | string) {
|
export async function unpublishAdminWorkOrder(workOrderId: number | string, actorName = '') {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (workOrder.status !== WORK_ORDER_STATUS.OPEN || workOrder.assigned_worker_id) {
|
if (workOrder.status !== WORK_ORDER_STATUS.OPEN || workOrder.assigned_worker_id) {
|
||||||
throw createHttpError('只有待抢单且未被接走的订单可以下架', {
|
throw createHttpError('只有待抢单且未被接走的订单可以下架', {
|
||||||
@@ -1144,7 +1157,7 @@ export async function unpublishAdminWorkOrder(workOrderId: number | string) {
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: actorName,
|
||||||
eventType: 'unpublished',
|
eventType: 'unpublished',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: WORK_ORDER_STATUS.UNASSIGNED,
|
toStatus: WORK_ORDER_STATUS.UNASSIGNED,
|
||||||
@@ -1153,7 +1166,11 @@ export async function unpublishAdminWorkOrder(workOrderId: number | string) {
|
|||||||
return { order: mapWorkOrderAdmin(updated || workOrder) }
|
return { order: mapWorkOrderAdmin(updated || workOrder) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function pinAdminWorkOrder(workOrderId: number | string, payload: JsonObject = {}) {
|
export async function pinAdminWorkOrder(
|
||||||
|
workOrderId: number | string,
|
||||||
|
payload: JsonObject = {},
|
||||||
|
actorName = '',
|
||||||
|
) {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
const pinned = payload.pinned === true
|
const pinned = payload.pinned === true
|
||||||
if (pinned && workOrder.status !== WORK_ORDER_STATUS.OPEN) {
|
if (pinned && workOrder.status !== WORK_ORDER_STATUS.OPEN) {
|
||||||
@@ -1170,7 +1187,7 @@ export async function pinAdminWorkOrder(workOrderId: number | string, payload: J
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: actorName,
|
||||||
eventType: pinned ? 'pinned' : 'unpinned',
|
eventType: pinned ? 'pinned' : 'unpinned',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: workOrder.status,
|
toStatus: workOrder.status,
|
||||||
@@ -1182,6 +1199,7 @@ export async function pinAdminWorkOrder(workOrderId: number | string, payload: J
|
|||||||
export async function markAdminWorkOrderProblem(
|
export async function markAdminWorkOrderProblem(
|
||||||
workOrderId: number | string,
|
workOrderId: number | string,
|
||||||
payload: JsonObject = {},
|
payload: JsonObject = {},
|
||||||
|
actorName = '',
|
||||||
) {
|
) {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (
|
if (
|
||||||
@@ -1204,7 +1222,7 @@ export async function markAdminWorkOrderProblem(
|
|||||||
await createWorkOrderEvent({
|
await createWorkOrderEvent({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
actorType: 'admin',
|
actorType: 'admin',
|
||||||
actorId: '',
|
actorId: actorName,
|
||||||
eventType: 'marked_problem',
|
eventType: 'marked_problem',
|
||||||
fromStatus: workOrder.status,
|
fromStatus: workOrder.status,
|
||||||
toStatus: WORK_ORDER_STATUS.PROBLEM,
|
toStatus: WORK_ORDER_STATUS.PROBLEM,
|
||||||
@@ -1217,6 +1235,7 @@ export async function markAdminWorkOrderProblem(
|
|||||||
export async function resolveAdminProblemWorkOrder(
|
export async function resolveAdminProblemWorkOrder(
|
||||||
workOrderId: number | string,
|
workOrderId: number | string,
|
||||||
payload: JsonObject = {},
|
payload: JsonObject = {},
|
||||||
|
actorName = '',
|
||||||
) {
|
) {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (workOrder.status !== WORK_ORDER_STATUS.PROBLEM) {
|
if (workOrder.status !== WORK_ORDER_STATUS.PROBLEM) {
|
||||||
@@ -1240,6 +1259,7 @@ export async function resolveAdminProblemWorkOrder(
|
|||||||
action,
|
action,
|
||||||
note,
|
note,
|
||||||
now: nowIso(),
|
now: nowIso(),
|
||||||
|
actorName,
|
||||||
})
|
})
|
||||||
if (!updated) {
|
if (!updated) {
|
||||||
throw createHttpError('问题单处置失败,订单状态可能已变化', {
|
throw createHttpError('问题单处置失败,订单状态可能已变化', {
|
||||||
@@ -1250,7 +1270,7 @@ export async function resolveAdminProblemWorkOrder(
|
|||||||
return { order: mapWorkOrderAdmin(updated) }
|
return { order: mapWorkOrderAdmin(updated) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function acceptAdminWorkOrder(workOrderId: number | string) {
|
export async function acceptAdminWorkOrder(workOrderId: number | string, actorName = '') {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (workOrder.status !== WORK_ORDER_STATUS.PENDING_ACCEPTANCE) {
|
if (workOrder.status !== WORK_ORDER_STATUS.PENDING_ACCEPTANCE) {
|
||||||
throw createHttpError('只有待验收订单可以通过验收', {
|
throw createHttpError('只有待验收订单可以通过验收', {
|
||||||
@@ -1263,6 +1283,8 @@ export async function acceptAdminWorkOrder(workOrderId: number | string) {
|
|||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
|
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
|
||||||
now,
|
now,
|
||||||
|
actorType: 'admin',
|
||||||
|
actorName,
|
||||||
})
|
})
|
||||||
if (failureReason === 'sharing_pending_submissions') {
|
if (failureReason === 'sharing_pending_submissions') {
|
||||||
throw createHttpError('还有拼单打手未提交验收资料,暂不能统一验收', {
|
throw createHttpError('还有拼单打手未提交验收资料,暂不能统一验收', {
|
||||||
@@ -1279,7 +1301,7 @@ export async function acceptAdminWorkOrder(workOrderId: number | string) {
|
|||||||
return { order: mapWorkOrderAdmin(updated || workOrder) }
|
return { order: mapWorkOrderAdmin(updated || workOrder) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function acceptAdminWorkOrders(payload: JsonObject = {}) {
|
export async function acceptAdminWorkOrders(payload: JsonObject = {}, actorName = '') {
|
||||||
const workOrderIds = [
|
const workOrderIds = [
|
||||||
...new Set(
|
...new Set(
|
||||||
(Array.isArray(payload.workOrderIds) ? payload.workOrderIds : [])
|
(Array.isArray(payload.workOrderIds) ? payload.workOrderIds : [])
|
||||||
@@ -1316,12 +1338,12 @@ export async function acceptAdminWorkOrders(payload: JsonObject = {}) {
|
|||||||
|
|
||||||
const results = []
|
const results = []
|
||||||
for (const workOrder of workOrders) {
|
for (const workOrder of workOrders) {
|
||||||
results.push(await acceptAdminWorkOrder(workOrder.id))
|
results.push(await acceptAdminWorkOrder(workOrder.id, actorName))
|
||||||
}
|
}
|
||||||
return { orders: results.map((result) => result.order) }
|
return { orders: results.map((result) => result.order) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unassignAdminWorkOrder(workOrderId: number | string) {
|
export async function unassignAdminWorkOrder(workOrderId: number | string, actorName = '') {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (workOrder.status !== WORK_ORDER_STATUS.IN_PROGRESS || !workOrder.assigned_worker_id) {
|
if (workOrder.status !== WORK_ORDER_STATUS.IN_PROGRESS || !workOrder.assigned_worker_id) {
|
||||||
throw createHttpError('只有进行中且已指派打手的订单可以取消指派', {
|
throw createHttpError('只有进行中且已指派打手的订单可以取消指派', {
|
||||||
@@ -1333,6 +1355,7 @@ export async function unassignAdminWorkOrder(workOrderId: number | string) {
|
|||||||
const { order: updated, failureReason } = await unassignWorkOrder({
|
const { order: updated, failureReason } = await unassignWorkOrder({
|
||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
now,
|
now,
|
||||||
|
actorName,
|
||||||
})
|
})
|
||||||
if (failureReason === 'work_order_not_in_progress') {
|
if (failureReason === 'work_order_not_in_progress') {
|
||||||
throw createHttpError('订单状态已变化,无法取消指派', {
|
throw createHttpError('订单状态已变化,无法取消指派', {
|
||||||
@@ -1355,7 +1378,11 @@ export async function unassignAdminWorkOrder(workOrderId: number | string) {
|
|||||||
return { order: mapWorkOrderAdmin(updated) }
|
return { order: mapWorkOrderAdmin(updated) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cancelAdminWorkOrder(workOrderId: number | string, payload: JsonObject = {}) {
|
export async function cancelAdminWorkOrder(
|
||||||
|
workOrderId: number | string,
|
||||||
|
payload: JsonObject = {},
|
||||||
|
actorName = '',
|
||||||
|
) {
|
||||||
const workOrder = await getRequiredWorkOrder(workOrderId)
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
if (workOrder.status !== WORK_ORDER_STATUS.IN_PROGRESS || !workOrder.assigned_worker_id) {
|
if (workOrder.status !== WORK_ORDER_STATUS.IN_PROGRESS || !workOrder.assigned_worker_id) {
|
||||||
throw createHttpError('只有代练中且已接单的订单可以撤单', {
|
throw createHttpError('只有代练中且已接单的订单可以撤单', {
|
||||||
@@ -1381,6 +1408,7 @@ export async function cancelAdminWorkOrder(workOrderId: number | string, payload
|
|||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
now: nowIso(),
|
now: nowIso(),
|
||||||
reason,
|
reason,
|
||||||
|
actorName,
|
||||||
}
|
}
|
||||||
const { order: updated, failureReason } =
|
const { order: updated, failureReason } =
|
||||||
action === 'return_to_hall'
|
action === 'return_to_hall'
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import crypto from 'node:crypto'
|
|||||||
import { runtimeConfig } from '../../config/runtime.js'
|
import { runtimeConfig } from '../../config/runtime.js'
|
||||||
import {
|
import {
|
||||||
addWorkerWalletCredit,
|
addWorkerWalletCredit,
|
||||||
|
getWorkerUserById,
|
||||||
type WorkCategoryRow,
|
type WorkCategoryRow,
|
||||||
type WorkerFinanceRequestRow,
|
type WorkerFinanceRequestRow,
|
||||||
type WorkOrderRow,
|
type WorkOrderRow,
|
||||||
@@ -1028,3 +1029,78 @@ export const IGNORABLE_WORKER_AUTH_ERROR_CODES = new Set([
|
|||||||
'worker_auth_user_invalid',
|
'worker_auth_user_invalid',
|
||||||
'worker_auth_stale',
|
'worker_auth_stale',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
export type WorkOrderEventView = {
|
||||||
|
id: number
|
||||||
|
time: string
|
||||||
|
actorType: string
|
||||||
|
actorName: string
|
||||||
|
eventType: string
|
||||||
|
fromStatus: string
|
||||||
|
toStatus: string
|
||||||
|
payload: JsonObject
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将工单事件原始行映射为带操作人显示名的视图。
|
||||||
|
* admin 事件的操作人来自 actor_id(管理员用户名);worker 事件按打手表解析显示名。
|
||||||
|
*/
|
||||||
|
export async function mapWorkOrderEvents(
|
||||||
|
events: Array<{
|
||||||
|
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
|
||||||
|
}>,
|
||||||
|
): Promise<WorkOrderEventView[]> {
|
||||||
|
const workerIds = [
|
||||||
|
...new Set(
|
||||||
|
events
|
||||||
|
.filter((event) => event.actor_type === 'worker')
|
||||||
|
.map((event) => Number(event.actor_id))
|
||||||
|
.filter((id) => Number.isFinite(id) && id > 0),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
const workerNameById = new Map<number, string>()
|
||||||
|
for (const workerId of workerIds) {
|
||||||
|
const worker = await getWorkerUserById(workerId)
|
||||||
|
if (worker) {
|
||||||
|
workerNameById.set(workerId, worker.display_name || worker.username || `打手#${workerId}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return events.map((event) => ({
|
||||||
|
id: Number(event.id),
|
||||||
|
time: new Date(event.created_at).toISOString(),
|
||||||
|
actorType: event.actor_type,
|
||||||
|
actorName: resolveWorkOrderEventActorName(event, workerNameById),
|
||||||
|
eventType: event.event_type,
|
||||||
|
fromStatus: event.from_status,
|
||||||
|
toStatus: event.to_status,
|
||||||
|
payload: safeParseJson(event.payload_json),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveWorkOrderEventActorName(
|
||||||
|
event: { actor_type: string; actor_id: string },
|
||||||
|
workerNameById: Map<number, string>,
|
||||||
|
) {
|
||||||
|
const actorId = String(event.actor_id || '').trim()
|
||||||
|
if (event.actor_type === 'worker') {
|
||||||
|
return workerNameById.get(Number(actorId)) || (actorId ? `打手#${actorId}` : '打手')
|
||||||
|
}
|
||||||
|
if (event.actor_type === 'admin') {
|
||||||
|
return actorId || '管理员'
|
||||||
|
}
|
||||||
|
if (event.actor_type === 'system') {
|
||||||
|
return '系统'
|
||||||
|
}
|
||||||
|
if (event.actor_type === 'collect') {
|
||||||
|
return '采集'
|
||||||
|
}
|
||||||
|
return actorId || '未知'
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
listWorkerWorkOrderNotes,
|
listWorkerWorkOrderNotes,
|
||||||
listWorkCategories,
|
listWorkCategories,
|
||||||
listWorkOrders,
|
listWorkOrders,
|
||||||
|
listWorkOrderEventsByOrderId,
|
||||||
listWorkerLevels,
|
listWorkerLevels,
|
||||||
listDueDepositUnfreezes,
|
listDueDepositUnfreezes,
|
||||||
listOverdueWorkOrders,
|
listOverdueWorkOrders,
|
||||||
@@ -79,6 +80,7 @@ import {
|
|||||||
mapWorkCategory,
|
mapWorkCategory,
|
||||||
mapWorkOrderForWorker,
|
mapWorkOrderForWorker,
|
||||||
mapWorkOrderPublic,
|
mapWorkOrderPublic,
|
||||||
|
mapWorkOrderEvents,
|
||||||
mapWorkOrderShare,
|
mapWorkOrderShare,
|
||||||
mapWorkerUser,
|
mapWorkerUser,
|
||||||
normalizeAmountFen,
|
normalizeAmountFen,
|
||||||
@@ -903,6 +905,21 @@ export async function grabWorkerHallOrder(workOrderId: number | string, session:
|
|||||||
throwGrabWorkOrderFailure(grabbed.failureReason)
|
throwGrabWorkOrderFailure(grabbed.failureReason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await createWorkOrderEvent({
|
||||||
|
workOrderId: workOrder.id,
|
||||||
|
actorType: 'worker',
|
||||||
|
actorId: String(worker.id),
|
||||||
|
eventType: 'grabbed',
|
||||||
|
fromStatus: workOrder.status,
|
||||||
|
toStatus: grabbed.order.status,
|
||||||
|
payloadJson: JSON.stringify({
|
||||||
|
workerId: worker.id,
|
||||||
|
workerName: worker.display_name || worker.username,
|
||||||
|
internal: worker.worker_type === 'internal',
|
||||||
|
}),
|
||||||
|
now: nowIso(),
|
||||||
|
})
|
||||||
|
|
||||||
return { order: mapWorkOrderForWorker(grabbed.order, permissions) }
|
return { order: mapWorkOrderForWorker(grabbed.order, permissions) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1018,6 +1035,24 @@ export async function listWorkerMyOrders(query: JsonObject = {}, session: Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getWorkerMyOrderEvents(workOrderId: number | string, session: WorkerSession) {
|
||||||
|
requireActiveWorkerSession(session)
|
||||||
|
const workOrder = await getRequiredWorkOrder(workOrderId)
|
||||||
|
const workerId = Number(session.workerId)
|
||||||
|
const sharingShare = await getWorkOrderShare(workOrder.id, workerId)
|
||||||
|
const isOrderOwner =
|
||||||
|
Number(workOrder.assigned_worker_id || 0) === workerId ||
|
||||||
|
Number(workOrder.last_assigned_worker_id || 0) === workerId
|
||||||
|
if (!sharingShare && !isOrderOwner) {
|
||||||
|
throw createHttpError('只能查看自己订单的流转记录', {
|
||||||
|
statusCode: 403,
|
||||||
|
errorCode: 'work_order_events_owner_required',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const events = await listWorkOrderEventsByOrderId(workOrderId)
|
||||||
|
return { items: await mapWorkOrderEvents(events) }
|
||||||
|
}
|
||||||
|
|
||||||
export async function saveWorkerOrderNote(
|
export async function saveWorkerOrderNote(
|
||||||
workOrderId: number | string,
|
workOrderId: number | string,
|
||||||
payload: JsonObject = {},
|
payload: JsonObject = {},
|
||||||
@@ -1190,6 +1225,8 @@ export async function submitWorkerOrderAcceptance(
|
|||||||
workOrderId: workOrder.id,
|
workOrderId: workOrder.id,
|
||||||
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
|
unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays,
|
||||||
now,
|
now,
|
||||||
|
actorType: 'worker',
|
||||||
|
actorName: worker.display_name || worker.username || '',
|
||||||
})
|
})
|
||||||
if (failureReason === 'sharing_pending_submissions') {
|
if (failureReason === 'sharing_pending_submissions') {
|
||||||
throw createHttpError('还有拼单打手未提交验收资料,暂不能统一验收', {
|
throw createHttpError('还有拼单打手未提交验收资料,暂不能统一验收', {
|
||||||
|
|||||||
@@ -0,0 +1,267 @@
|
|||||||
|
import { ClockCircleOutlined } from '@ant-design/icons'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { Empty, Skeleton, Tag, Timeline, Tooltip, Typography } from 'antd'
|
||||||
|
import dayjs from '@/lib/dayjs'
|
||||||
|
|
||||||
|
import { fetchAdminWorkOrderEvents } from '@/services/admin'
|
||||||
|
import { fetchWorkerMyOrderEvents } from '@/services/worker'
|
||||||
|
import type { WorkOrderEvent } from '@/types/worker-platform'
|
||||||
|
|
||||||
|
type EventNodeStyle = {
|
||||||
|
/** 节点标题(主文案) */
|
||||||
|
title: string
|
||||||
|
/** 补充说明(次文案) */
|
||||||
|
description?: string
|
||||||
|
/** Timeline 节点颜色 */
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 工单状态中文名 */
|
||||||
|
const STATUS_LABELS: Record<string, string> = {
|
||||||
|
pending_material: '待完善资料',
|
||||||
|
unassigned: '未分配',
|
||||||
|
open: '待抢单',
|
||||||
|
in_progress: '代练中',
|
||||||
|
pending_acceptance: '待验收',
|
||||||
|
problem: '问题单',
|
||||||
|
accepted: '已验收',
|
||||||
|
cancelled: '已取消',
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatStatusLabel(status: string) {
|
||||||
|
return STATUS_LABELS[status] || status || '未知'
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPayload(event: WorkOrderEvent, key: string): string {
|
||||||
|
const value = event.payload?.[key]
|
||||||
|
if (value === undefined || value === null) return ''
|
||||||
|
return String(value).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 事件类型 → 节点阶段文案与颜色 */
|
||||||
|
export function formatWorkOrderEvent(event: WorkOrderEvent): EventNodeStyle {
|
||||||
|
const type = event.eventType
|
||||||
|
const note = readPayload(event, 'note') || readPayload(event, 'reason')
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
// —— 创建节点 ——
|
||||||
|
case 'source_synced':
|
||||||
|
return {
|
||||||
|
title: '系统收到推送的新订单,自动生成该笔订单',
|
||||||
|
color: 'blue',
|
||||||
|
}
|
||||||
|
case 'mock_created':
|
||||||
|
return { title: '本地 Mock 生成该笔订单', color: 'blue' }
|
||||||
|
|
||||||
|
// —— 发布节点 ——
|
||||||
|
case 'published':
|
||||||
|
return { title: '将订单发单至内部接单大厅', color: 'geekblue' }
|
||||||
|
case 'unpublished':
|
||||||
|
return { title: '将订单下架,退回未分配状态', color: 'geekblue' }
|
||||||
|
case 'pinned':
|
||||||
|
return { title: '将订单置顶,抢单大厅优先展示', color: 'gold' }
|
||||||
|
case 'unpinned':
|
||||||
|
return { title: '取消订单置顶', color: 'gold' }
|
||||||
|
|
||||||
|
// —— 接手节点 ——
|
||||||
|
case 'grabbed': {
|
||||||
|
const workerName = readPayload(event, 'workerName')
|
||||||
|
return {
|
||||||
|
title: workerName
|
||||||
|
? `通过接单大厅自助接手此单,该订单当前接手人:${workerName}`
|
||||||
|
: '通过接单大厅自助接手此单',
|
||||||
|
description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel(
|
||||||
|
event.toStatus,
|
||||||
|
)}`,
|
||||||
|
color: 'green',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'assigned_by_admin': {
|
||||||
|
const workerId = readPayload(event, 'workerId')
|
||||||
|
return {
|
||||||
|
title: `指派打手接手此单${workerId ? `(打手 #${workerId})` : ''}`,
|
||||||
|
description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel(
|
||||||
|
event.toStatus,
|
||||||
|
)}`,
|
||||||
|
color: 'green',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'unassigned_by_admin':
|
||||||
|
return { title: '取消指派,订单回到未分配状态', color: 'green' }
|
||||||
|
case 'returned_to_hall_by_admin':
|
||||||
|
return {
|
||||||
|
title: note ? `撤回订单到抢单大厅(${note})` : '撤回订单到抢单大厅',
|
||||||
|
color: 'green',
|
||||||
|
}
|
||||||
|
case 'cancelled_by_worker':
|
||||||
|
return { title: '打手取消接单', color: 'red' }
|
||||||
|
case 'timeout_reopen':
|
||||||
|
return { title: '订单超时未完成,已退回抢单大厅', color: 'orange' }
|
||||||
|
|
||||||
|
// —— 资料节点 ——
|
||||||
|
case 'material_submitted':
|
||||||
|
return { title: '提交了订单资料', color: 'cyan' }
|
||||||
|
case 'material_completed':
|
||||||
|
return { title: '完善了订单资料', color: 'cyan' }
|
||||||
|
case 'material_updated':
|
||||||
|
return { title: '更新了订单资料', color: 'cyan' }
|
||||||
|
|
||||||
|
// —— 验收节点 ——
|
||||||
|
case 'acceptance_submitted': {
|
||||||
|
const imageCount = readPayload(event, 'imageCount')
|
||||||
|
return {
|
||||||
|
title: `提交验收资料${imageCount ? `(${imageCount} 张图)` : ''}`,
|
||||||
|
description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel(
|
||||||
|
event.toStatus,
|
||||||
|
)}`,
|
||||||
|
color: 'purple',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'acceptance_updated':
|
||||||
|
return { title: '补充了验收资料', color: 'purple' }
|
||||||
|
case 'acceptance_draft_saved':
|
||||||
|
return { title: '暂存了验收草稿', color: 'purple' }
|
||||||
|
case 'accepted_by_internal':
|
||||||
|
return {
|
||||||
|
title: '内部打手自助验收通过,订单完成',
|
||||||
|
description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel(
|
||||||
|
event.toStatus,
|
||||||
|
)}`,
|
||||||
|
color: 'purple',
|
||||||
|
}
|
||||||
|
case 'accepted':
|
||||||
|
case 'accepted_by_admin':
|
||||||
|
return {
|
||||||
|
title: '验收通过,订单完成',
|
||||||
|
description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel(
|
||||||
|
event.toStatus,
|
||||||
|
)}`,
|
||||||
|
color: 'purple',
|
||||||
|
}
|
||||||
|
|
||||||
|
// —— 问题节点 ——
|
||||||
|
case 'marked_problem':
|
||||||
|
return {
|
||||||
|
title: note ? `将订单标记为问题单:${note}` : '将订单标记为问题单',
|
||||||
|
color: 'red',
|
||||||
|
}
|
||||||
|
case 'problem_return_to_worker':
|
||||||
|
return { title: '问题单已处置:退回打手继续处理', color: 'orange' }
|
||||||
|
case 'problem_reopen':
|
||||||
|
return { title: '问题单已处置:重新开工', color: 'orange' }
|
||||||
|
case 'problem_cancel_release':
|
||||||
|
return { title: '问题单已处置:取消订单并释放押金', color: 'orange' }
|
||||||
|
case 'problem_cancel_deduct':
|
||||||
|
return { title: '问题单已处置:取消订单并扣除押金', color: 'orange' }
|
||||||
|
|
||||||
|
// —— 取消节点 ——
|
||||||
|
case 'cancelled_by_admin':
|
||||||
|
return {
|
||||||
|
title: note ? `取消订单(${note})` : '取消订单',
|
||||||
|
description: `订单状态:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel(
|
||||||
|
event.toStatus,
|
||||||
|
)}`,
|
||||||
|
color: 'red',
|
||||||
|
}
|
||||||
|
|
||||||
|
// —— 其他 ——
|
||||||
|
case 'edited':
|
||||||
|
return { title: '更改了订单信息', color: 'default' }
|
||||||
|
case 'sharing_config_updated':
|
||||||
|
return { title: '更新了拼单配置', color: 'default' }
|
||||||
|
case 'pending_deposit_deducted':
|
||||||
|
return { title: '扣减了待解冻押金', color: 'default' }
|
||||||
|
case 'voucher_backfilled':
|
||||||
|
return { title: '回填了电子凭证信息', color: 'default' }
|
||||||
|
case 'assign_voucher_consume_failed':
|
||||||
|
case 'publish_voucher_consume_failed':
|
||||||
|
return { title: '电子凭证核销失败,本次操作未生效', color: 'red' }
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 通用状态流转兜底
|
||||||
|
if (event.fromStatus && event.toStatus && event.fromStatus !== event.toStatus) {
|
||||||
|
return {
|
||||||
|
title: `订单状态变更:${formatStatusLabel(event.fromStatus)} → ${formatStatusLabel(
|
||||||
|
event.toStatus,
|
||||||
|
)}`,
|
||||||
|
color: 'default',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { title: type, color: 'default' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 订单详情中的流转节点时间线(管理端 / 打手端共用) */
|
||||||
|
export default function WorkOrderEventTimeline({
|
||||||
|
orderId,
|
||||||
|
viewer,
|
||||||
|
}: {
|
||||||
|
orderId: number
|
||||||
|
viewer: 'admin' | 'worker'
|
||||||
|
}) {
|
||||||
|
const eventsQuery = useQuery({
|
||||||
|
queryKey: ['work-order-events', viewer, orderId],
|
||||||
|
queryFn: () =>
|
||||||
|
viewer === 'admin' ? fetchAdminWorkOrderEvents(orderId) : fetchWorkerMyOrderEvents(orderId),
|
||||||
|
enabled: Boolean(orderId),
|
||||||
|
retry: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (eventsQuery.isLoading) {
|
||||||
|
return <Skeleton active paragraph={{ rows: 4 }} />
|
||||||
|
}
|
||||||
|
if (eventsQuery.error) {
|
||||||
|
return (
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
{eventsQuery.error instanceof Error ? eventsQuery.error.message : '读取流转记录失败'}
|
||||||
|
</Typography.Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = eventsQuery.data?.data?.items || []
|
||||||
|
if (events.length === 0) {
|
||||||
|
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无流转记录" />
|
||||||
|
}
|
||||||
|
|
||||||
|
// 倒序:最新节点在上
|
||||||
|
const items = [...events].reverse().map((event) => {
|
||||||
|
const style = formatWorkOrderEvent(event)
|
||||||
|
return {
|
||||||
|
color: style.color,
|
||||||
|
children: (
|
||||||
|
<div className="work-order-event-item">
|
||||||
|
<div className="work-order-event-head">
|
||||||
|
<Typography.Text strong>{style.title}</Typography.Text>
|
||||||
|
<Tooltip title={dayjs(event.time).format('YYYY-MM-DD HH:mm:ss')}>
|
||||||
|
<Typography.Text type="secondary" className="work-order-event-time">
|
||||||
|
<ClockCircleOutlined /> {dayjs(event.time).format('MM-DD HH:mm')}
|
||||||
|
</Typography.Text>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
{style.description ? (
|
||||||
|
<Typography.Text type="secondary" className="work-order-event-desc">
|
||||||
|
{style.description}
|
||||||
|
</Typography.Text>
|
||||||
|
) : null}
|
||||||
|
<div className="work-order-event-actor">
|
||||||
|
<Tag
|
||||||
|
color={
|
||||||
|
event.actorType === 'system'
|
||||||
|
? 'default'
|
||||||
|
: event.actorType === 'worker'
|
||||||
|
? 'blue'
|
||||||
|
: event.actorType === 'admin'
|
||||||
|
? 'green'
|
||||||
|
: 'default'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{event.actorName || event.actorType}
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return <Timeline items={items} className="work-order-event-timeline" />
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
|
|
||||||
import JsonPreview from '@/components/admin/JsonPreview'
|
import JsonPreview from '@/components/admin/JsonPreview'
|
||||||
|
import WorkOrderEventTimeline from '@/components/WorkOrderEventTimeline'
|
||||||
import ImageUpload from '@/components/files/ImageUpload'
|
import ImageUpload from '@/components/files/ImageUpload'
|
||||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||||
import {
|
import {
|
||||||
@@ -1101,6 +1102,10 @@ export default function WorkOrdersPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card title="流转记录" size="small">
|
||||||
|
<WorkOrderEventTimeline orderId={detailOrder.workOrderId} viewer="admin" />
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card title="原始数据" size="small">
|
<Card title="原始数据" size="small">
|
||||||
<Tabs
|
<Tabs
|
||||||
className="worker-order-detail-json-tabs"
|
className="worker-order-detail-json-tabs"
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import type { TableColumnsType } from 'antd'
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
import { DeadlineCountdown } from '@/components/DeadlineCountdown'
|
import { DeadlineCountdown } from '@/components/DeadlineCountdown'
|
||||||
|
import WorkOrderEventTimeline from '@/components/WorkOrderEventTimeline'
|
||||||
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
import ImagePreviewList from '@/components/files/ImagePreviewList'
|
||||||
import ImageUpload from '@/components/files/ImageUpload'
|
import ImageUpload from '@/components/files/ImageUpload'
|
||||||
import {
|
import {
|
||||||
@@ -1102,6 +1103,10 @@ export default function WorkerOrdersPage() {
|
|||||||
</span>
|
</span>
|
||||||
</Card>
|
</Card>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<Card title="流转记录" size="small">
|
||||||
|
<WorkOrderEventTimeline orderId={detailOrder.workOrderId} viewer="worker" />
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type {
|
|||||||
UploadedFile,
|
UploadedFile,
|
||||||
WorkCategory,
|
WorkCategory,
|
||||||
WorkOrder,
|
WorkOrder,
|
||||||
|
WorkOrderEvent,
|
||||||
WorkOrderShare,
|
WorkOrderShare,
|
||||||
WorkProductRule,
|
WorkProductRule,
|
||||||
WorkerFinanceConfig,
|
WorkerFinanceConfig,
|
||||||
@@ -200,6 +201,12 @@ export function fetchAdminWorkOrders(params?: Record<string, unknown>) {
|
|||||||
return apiGet<WorkerListResponse<WorkOrder>>('/api/v1/admin/worker-platform/orders', params)
|
return apiGet<WorkerListResponse<WorkOrder>>('/api/v1/admin/worker-platform/orders', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchAdminWorkOrderEvents(workOrderId: number) {
|
||||||
|
return apiGet<{ items: WorkOrderEvent[] }>(
|
||||||
|
`/api/v1/admin/worker-platform/orders/${workOrderId}/events`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function createAdminMockWorkOrder(payload: {
|
export function createAdminMockWorkOrder(payload: {
|
||||||
platformOrderId?: string
|
platformOrderId?: string
|
||||||
productName?: string
|
productName?: string
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type {
|
|||||||
CollectLookupResponse,
|
CollectLookupResponse,
|
||||||
UploadedFile,
|
UploadedFile,
|
||||||
WorkOrder,
|
WorkOrder,
|
||||||
|
WorkOrderEvent,
|
||||||
WorkOrderShare,
|
WorkOrderShare,
|
||||||
WorkerFinanceRequest,
|
WorkerFinanceRequest,
|
||||||
WorkerHallOrdersResponse,
|
WorkerHallOrdersResponse,
|
||||||
@@ -123,6 +124,10 @@ export function fetchWorkerMyOrders(params?: Record<string, unknown>) {
|
|||||||
return apiGet<WorkerListResponse<WorkOrder>>('/api/v1/worker/orders', params)
|
return apiGet<WorkerListResponse<WorkOrder>>('/api/v1/worker/orders', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchWorkerMyOrderEvents(workOrderId: number) {
|
||||||
|
return apiGet<{ items: WorkOrderEvent[] }>(`/api/v1/worker/orders/${workOrderId}/events`)
|
||||||
|
}
|
||||||
|
|
||||||
export function saveWorkerOrderNote(workOrderId: number, note: string) {
|
export function saveWorkerOrderNote(workOrderId: number, note: string) {
|
||||||
return apiPost<{ order: WorkOrder }>(`/api/v1/worker/orders/${workOrderId}/note`, { note })
|
return apiPost<{ order: WorkOrder }>(`/api/v1/worker/orders/${workOrderId}/note`, { note })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,40 @@
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== 工单流转记录时间线 ===== */
|
||||||
|
.work-order-event-timeline {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-order-event-item {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-order-event-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-order-event-head .ant-typography {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-order-event-time {
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-order-event-actor .ant-tag {
|
||||||
|
margin-inline-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.work-order-event-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.worker-order-actions {
|
.worker-order-actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-items: start;
|
justify-items: start;
|
||||||
|
|||||||
@@ -269,6 +269,18 @@ export type WorkOrder = {
|
|||||||
updatedAt: string
|
updatedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 工单流转事件(订单详情中的操作日志) */
|
||||||
|
export type WorkOrderEvent = {
|
||||||
|
id: number
|
||||||
|
time: string
|
||||||
|
actorType: string
|
||||||
|
actorName: string
|
||||||
|
eventType: string
|
||||||
|
fromStatus: string
|
||||||
|
toStatus: string
|
||||||
|
payload: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
export type Pagination = {
|
export type Pagination = {
|
||||||
page: number
|
page: number
|
||||||
pageSize: number
|
pageSize: number
|
||||||
|
|||||||
Reference in New Issue
Block a user