超时订单按打手统计,抢单大厅标注限时任务
This commit is contained in:
@@ -1425,6 +1425,7 @@ export async function settleOverdueWorkOrder(input: {
|
|||||||
payloadJson: JSON.stringify({
|
payloadJson: JSON.stringify({
|
||||||
policy: input.policy,
|
policy: input.policy,
|
||||||
depositAmount: resolvedDepositAmount,
|
depositAmount: resolvedDepositAmount,
|
||||||
|
workerId: workerId > 0 ? workerId : null,
|
||||||
}),
|
}),
|
||||||
now: input.now,
|
now: input.now,
|
||||||
})
|
})
|
||||||
@@ -1449,6 +1450,36 @@ export async function countWorkerActiveOrders(workerId: number | string): Promis
|
|||||||
return Number(result.rows[0]?.total || 0)
|
return Number(result.rows[0]?.total || 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function countTimeoutEventsByWorkerIds(workerIds: number[]): Promise<Map<number, number>> {
|
||||||
|
const counts = new Map<number, number>()
|
||||||
|
const uniqueIds = [...new Set(workerIds.map((id) => Number(id)).filter((id) => id > 0))]
|
||||||
|
if (uniqueIds.length === 0) return counts
|
||||||
|
const result = await query<{ worker_id: string; total: number }>(
|
||||||
|
`
|
||||||
|
SELECT payload_json->>'workerId' AS worker_id, COUNT(*)::int AS total
|
||||||
|
FROM work_order_events
|
||||||
|
WHERE event_type LIKE 'timeout_%'
|
||||||
|
AND payload_json->>'workerId' IS NOT NULL
|
||||||
|
AND payload_json->>'workerId' != ''
|
||||||
|
AND payload_json->>'workerId' = ANY($1::text[])
|
||||||
|
GROUP BY payload_json->>'workerId'
|
||||||
|
`,
|
||||||
|
[uniqueIds.map(String)],
|
||||||
|
)
|
||||||
|
for (const row of result.rows) {
|
||||||
|
const workerId = Number(row.worker_id)
|
||||||
|
if (Number.isFinite(workerId) && workerId > 0) {
|
||||||
|
counts.set(workerId, Number(row.total || 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return counts
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function countWorkerTimeoutEvents(workerId: number | string): Promise<number> {
|
||||||
|
const counts = await countTimeoutEventsByWorkerIds([Number(workerId)])
|
||||||
|
return counts.get(Number(workerId)) || 0
|
||||||
|
}
|
||||||
|
|
||||||
export async function createWorkOrderEvent(input: {
|
export async function createWorkOrderEvent(input: {
|
||||||
workOrderId: number
|
workOrderId: number
|
||||||
actorType: string
|
actorType: string
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
countWorkCategoryUsages,
|
countWorkCategoryUsages,
|
||||||
countWorkerLevelUsages,
|
countWorkerLevelUsages,
|
||||||
countWorkOrderPendingSharingSubmissions,
|
countWorkOrderPendingSharingSubmissions,
|
||||||
|
countTimeoutEventsByWorkerIds,
|
||||||
cancelWorkerWorkOrder,
|
cancelWorkerWorkOrder,
|
||||||
countWorkerCancellationsSince,
|
countWorkerCancellationsSince,
|
||||||
createWorkOrder,
|
createWorkOrder,
|
||||||
@@ -299,8 +300,17 @@ export async function listAdminWorkerUsers(query: JsonObject = {}) {
|
|||||||
status: String(query.status || '').trim(),
|
status: String(query.status || '').trim(),
|
||||||
keyword: String(query.keyword || '').trim(),
|
keyword: String(query.keyword || '').trim(),
|
||||||
})
|
})
|
||||||
|
const timeoutCounts = await countTimeoutEventsByWorkerIds(
|
||||||
|
items.map((item) => Number(item.id)),
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
items: items.map(mapWorkerUser),
|
items: items.map((item) => {
|
||||||
|
const worker = mapWorkerUser(item)
|
||||||
|
return {
|
||||||
|
...worker,
|
||||||
|
timeoutOrderCount: timeoutCounts.get(Number(item.id)) || 0,
|
||||||
|
}
|
||||||
|
}),
|
||||||
pagination: { page, pageSize, total },
|
pagination: { page, pageSize, total },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
addWorkerWalletCredit,
|
addWorkerWalletCredit,
|
||||||
countWorkerAcceptedOrders,
|
countWorkerAcceptedOrders,
|
||||||
countWorkerActiveOrders,
|
countWorkerActiveOrders,
|
||||||
|
countWorkerTimeoutEvents,
|
||||||
countWorkCategoryUsages,
|
countWorkCategoryUsages,
|
||||||
countWorkerLevelUsages,
|
countWorkerLevelUsages,
|
||||||
countWorkOrderPendingSharingSubmissions,
|
countWorkOrderPendingSharingSubmissions,
|
||||||
@@ -347,9 +348,10 @@ export function requireActiveWorkerSession(session: WorkerSession | null | undef
|
|||||||
|
|
||||||
export async function getWorkerProfile(session: WorkerSession) {
|
export async function getWorkerProfile(session: WorkerSession) {
|
||||||
const worker = await getRequiredWorker(session.workerId)
|
const worker = await getRequiredWorker(session.workerId)
|
||||||
const [financeSummary, acceptedOrderCount, financeConfig] = await Promise.all([
|
const [financeSummary, acceptedOrderCount, timeoutOrderCount, financeConfig] = await Promise.all([
|
||||||
getWorkerFinanceRequestSummary(session.workerId),
|
getWorkerFinanceRequestSummary(session.workerId),
|
||||||
countWorkerAcceptedOrders(session.workerId),
|
countWorkerAcceptedOrders(session.workerId),
|
||||||
|
countWorkerTimeoutEvents(session.workerId),
|
||||||
Promise.resolve(getWorkerFinanceConfig()),
|
Promise.resolve(getWorkerFinanceConfig()),
|
||||||
])
|
])
|
||||||
const permissions = resolveWorkerPermissions(worker)
|
const permissions = resolveWorkerPermissions(worker)
|
||||||
@@ -359,6 +361,7 @@ export async function getWorkerProfile(session: WorkerSession) {
|
|||||||
permissions,
|
permissions,
|
||||||
summary: {
|
summary: {
|
||||||
acceptedOrderCount,
|
acceptedOrderCount,
|
||||||
|
timeoutOrderCount,
|
||||||
pendingWithdrawAmount: financeSummary.pendingWithdrawAmount,
|
pendingWithdrawAmount: financeSummary.pendingWithdrawAmount,
|
||||||
approvedWithdrawAmount: financeSummary.approvedWithdrawAmount,
|
approvedWithdrawAmount: financeSummary.approvedWithdrawAmount,
|
||||||
pendingRechargeAmount: financeSummary.pendingRechargeAmount,
|
pendingRechargeAmount: financeSummary.pendingRechargeAmount,
|
||||||
|
|||||||
@@ -196,6 +196,18 @@ export default function WorkersPanel() {
|
|||||||
width: 120,
|
width: 120,
|
||||||
render: (_, row) => formatMoney(row.wallet.frozenDepositAmount),
|
render: (_, row) => formatMoney(row.wallet.frozenDepositAmount),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '超时订单',
|
||||||
|
width: 110,
|
||||||
|
render: (_, row) => {
|
||||||
|
const count = Number(row.timeoutOrderCount || 0)
|
||||||
|
return count > 0 ? (
|
||||||
|
<Tag color="red">{count} 单</Tag>
|
||||||
|
) : (
|
||||||
|
<Typography.Text type="secondary">0</Typography.Text>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
width: 130,
|
width: 130,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EyeOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
|
import { ClockCircleOutlined, EyeOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
@@ -378,6 +378,14 @@ export default function WorkerHallPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{Number(order.timeoutMinutes || 0) > 0 ? (
|
||||||
|
<div className="worker-hall-card-sharing">
|
||||||
|
<Tag color="orange" icon={<ClockCircleOutlined />}>
|
||||||
|
限时 {order.timeoutMinutes} 分钟
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className="worker-hall-card-amount-row">
|
<div className="worker-hall-card-amount-row">
|
||||||
<div className="worker-hall-card-amount">
|
<div className="worker-hall-card-amount">
|
||||||
{formatMoney(order.rewardAmount)}
|
{formatMoney(order.rewardAmount)}
|
||||||
@@ -511,6 +519,15 @@ export default function WorkerHallPage() {
|
|||||||
<Descriptions.Item label="冻结押金">
|
<Descriptions.Item label="冻结押金">
|
||||||
{formatMoney(detailOrder.freezeDepositAmount)}
|
{formatMoney(detailOrder.freezeDepositAmount)}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="任务时限">
|
||||||
|
{Number(detailOrder.timeoutMinutes || 0) > 0 ? (
|
||||||
|
<Tag color="orange" icon={<ClockCircleOutlined />}>
|
||||||
|
限时 {detailOrder.timeoutMinutes} 分钟
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
'不限时'
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="发布时间">
|
<Descriptions.Item label="发布时间">
|
||||||
{formatDateTime(resolvePublishedTime(detailOrder))}
|
{formatDateTime(resolvePublishedTime(detailOrder))}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ type MetricCardItem = {
|
|||||||
prefix?: string
|
prefix?: string
|
||||||
suffix?: string
|
suffix?: string
|
||||||
precision?: number
|
precision?: number
|
||||||
|
danger?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function WorkerProfilePage() {
|
export default function WorkerProfilePage() {
|
||||||
@@ -519,6 +520,7 @@ export default function WorkerProfilePage() {
|
|||||||
prefix={item.prefix}
|
prefix={item.prefix}
|
||||||
suffix={item.suffix}
|
suffix={item.suffix}
|
||||||
precision={item.precision}
|
precision={item.precision}
|
||||||
|
valueStyle={item.danger ? { color: '#cf1322' } : undefined}
|
||||||
/>
|
/>
|
||||||
<Typography.Text type="secondary">{item.note}</Typography.Text>
|
<Typography.Text type="secondary">{item.note}</Typography.Text>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -1086,6 +1088,13 @@ function buildMetricCards(worker: WorkerUser, summary?: WorkerProfileSummary): M
|
|||||||
note: '已完成并验收通过的工单数量',
|
note: '已完成并验收通过的工单数量',
|
||||||
suffix: '单',
|
suffix: '单',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '超时订单',
|
||||||
|
value: Number(summary?.timeoutOrderCount || 0),
|
||||||
|
note: '因任务超时被系统判定失败的工单数量',
|
||||||
|
suffix: '单',
|
||||||
|
danger: Number(summary?.timeoutOrderCount || 0) > 0,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export type WorkerUser = {
|
|||||||
totalCreditedAmount: number
|
totalCreditedAmount: number
|
||||||
totalSettledAmount: number
|
totalSettledAmount: number
|
||||||
}
|
}
|
||||||
|
timeoutOrderCount?: number
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
reviewedAt: string | null
|
reviewedAt: string | null
|
||||||
@@ -35,6 +36,7 @@ export type WorkerUser = {
|
|||||||
|
|
||||||
export type WorkerProfileSummary = {
|
export type WorkerProfileSummary = {
|
||||||
acceptedOrderCount: number
|
acceptedOrderCount: number
|
||||||
|
timeoutOrderCount: number
|
||||||
pendingWithdrawAmount: number
|
pendingWithdrawAmount: number
|
||||||
approvedWithdrawAmount: number
|
approvedWithdrawAmount: number
|
||||||
pendingRechargeAmount: number
|
pendingRechargeAmount: number
|
||||||
|
|||||||
Reference in New Issue
Block a user