feat: 打手分内部/外部,内部免审核;支持后台指派打手
- worker_users 新增 worker_type(internal/external,迁移 017) - 内部打手提交验收后自动通过进入待打款(accepted+结算),外部打手保持后台审核 - 打手管理页支持设为内部/外部并显示类型标签 - 新增后台指派打手:未分配/待抢单工单可指定已注册打手,进入其我的订单并冻结押金 - 指派带审计与事件记录,校验打手状态/接单上限/余额押金
This commit is contained in:
@@ -19,6 +19,7 @@ export type WorkerUserRow = {
|
||||
phone: string
|
||||
level_id: number | null
|
||||
status: string
|
||||
worker_type: string
|
||||
review_note: string
|
||||
session_version: number
|
||||
created_at: string
|
||||
@@ -241,6 +242,7 @@ export type CreateWorkerInput = {
|
||||
phone: string
|
||||
levelId: number | null
|
||||
status: string
|
||||
workerType?: string
|
||||
inviterId?: number | null
|
||||
inviteCode: string
|
||||
now: string
|
||||
|
||||
@@ -859,6 +859,115 @@ export async function grabWorkOrder(input: {
|
||||
})
|
||||
}
|
||||
|
||||
export async function assignWorkOrderToWorker(input: {
|
||||
workOrderId: number
|
||||
workerId: number
|
||||
depositAmount: number
|
||||
maxActiveOrders: number
|
||||
deadlineAt: string | null
|
||||
now: string
|
||||
}): Promise<{
|
||||
order: WorkOrderRow | null
|
||||
failureReason:
|
||||
| 'work_order_not_assignable'
|
||||
| 'worker_active_order_limit'
|
||||
| 'worker_deposit_insufficient'
|
||||
| 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 ||
|
||||
!['unassigned', 'open'].includes(workOrder.status) ||
|
||||
workOrder.assigned_worker_id
|
||||
) {
|
||||
return { order: null, failureReason: 'work_order_not_assignable' }
|
||||
}
|
||||
const fromStatus = workOrder.status
|
||||
|
||||
await ensureWorkerWalletWithClient(client, input.workerId, input.now)
|
||||
const wallet = await getWorkerWalletWithClient(client, input.workerId)
|
||||
const activeOrderCount = await countWorkerActiveOrdersWithClient(client, input.workerId)
|
||||
if (activeOrderCount >= input.maxActiveOrders) {
|
||||
return { order: null, failureReason: 'worker_active_order_limit' }
|
||||
}
|
||||
|
||||
const available = Number(wallet?.available_amount || 0)
|
||||
if (available < input.depositAmount) {
|
||||
return { order: null, failureReason: 'worker_deposit_insufficient' }
|
||||
}
|
||||
|
||||
await client.query(
|
||||
`
|
||||
UPDATE work_orders
|
||||
SET
|
||||
status = 'in_progress',
|
||||
assigned_worker_id = $1,
|
||||
last_assigned_worker_id = $1,
|
||||
assigned_at = $2,
|
||||
deadline_at = $3,
|
||||
updated_at = $2
|
||||
WHERE id = $4
|
||||
`,
|
||||
[input.workerId, input.now, input.deadlineAt, input.workOrderId],
|
||||
)
|
||||
|
||||
if (input.depositAmount > 0) {
|
||||
const nextAvailable = available - input.depositAmount
|
||||
const nextFrozen = Number(wallet?.frozen_deposit_amount || 0) + input.depositAmount
|
||||
await client.query(
|
||||
`
|
||||
UPDATE worker_wallets
|
||||
SET available_amount = $1, frozen_deposit_amount = $2, updated_at = $3
|
||||
WHERE worker_id = $4
|
||||
`,
|
||||
[nextAvailable, nextFrozen, input.now, input.workerId],
|
||||
)
|
||||
await client.query(
|
||||
`
|
||||
INSERT INTO worker_wallet_ledgers (
|
||||
worker_id, ledger_type, amount, balance_after, frozen_after,
|
||||
related_work_order_id, note, payload_json, created_at
|
||||
) VALUES ($1, 'deposit_freeze', $2, $3, $4, $5, '后台指派冻结押金', '{}'::jsonb, $6)
|
||||
`,
|
||||
[
|
||||
input.workerId,
|
||||
-input.depositAmount,
|
||||
nextAvailable,
|
||||
nextFrozen,
|
||||
input.workOrderId,
|
||||
input.now,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
await createWorkOrderEventWithClient(client, {
|
||||
workOrderId: input.workOrderId,
|
||||
actorType: 'admin',
|
||||
actorId: '',
|
||||
eventType: 'assigned_by_admin',
|
||||
fromStatus,
|
||||
toStatus: 'in_progress',
|
||||
payloadJson: JSON.stringify({ workerId: input.workerId, depositAmount: input.depositAmount }),
|
||||
now: input.now,
|
||||
})
|
||||
|
||||
return {
|
||||
order: await getWorkOrderByIdWithClient(client, input.workOrderId),
|
||||
failureReason: null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function cancelWorkerWorkOrder(input: {
|
||||
workOrderId: number
|
||||
workerId: number
|
||||
|
||||
@@ -159,9 +159,9 @@ export async function createWorkerUser(input: CreateWorkerInput): Promise<Worker
|
||||
`
|
||||
INSERT INTO worker_users (
|
||||
username, password_hash, display_name, phone, level_id, status,
|
||||
inviter_id, invite_code,
|
||||
worker_type, inviter_id, invite_code,
|
||||
created_at, updated_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING id
|
||||
`,
|
||||
[
|
||||
@@ -171,6 +171,7 @@ export async function createWorkerUser(input: CreateWorkerInput): Promise<Worker
|
||||
input.phone,
|
||||
input.levelId,
|
||||
input.status,
|
||||
input.workerType || 'external',
|
||||
input.inviterId || null,
|
||||
input.inviteCode,
|
||||
input.now,
|
||||
@@ -188,7 +189,13 @@ export async function updateWorkerUser(
|
||||
patch: Partial<
|
||||
Pick<
|
||||
WorkerUserRow,
|
||||
'status' | 'review_note' | 'level_id' | 'session_version' | 'updated_at' | 'reviewed_at'
|
||||
| 'status'
|
||||
| 'worker_type'
|
||||
| 'review_note'
|
||||
| 'level_id'
|
||||
| 'session_version'
|
||||
| 'updated_at'
|
||||
| 'reviewed_at'
|
||||
>
|
||||
>,
|
||||
): Promise<WorkerUserRow | null> {
|
||||
@@ -201,15 +208,17 @@ export async function updateWorkerUser(
|
||||
UPDATE worker_users
|
||||
SET
|
||||
status = $1,
|
||||
review_note = $2,
|
||||
level_id = $3,
|
||||
session_version = $4,
|
||||
reviewed_at = $5,
|
||||
updated_at = $6
|
||||
WHERE id = $7
|
||||
worker_type = $2,
|
||||
review_note = $3,
|
||||
level_id = $4,
|
||||
session_version = $5,
|
||||
reviewed_at = $6,
|
||||
updated_at = $7
|
||||
WHERE id = $8
|
||||
`,
|
||||
[
|
||||
next.status,
|
||||
next.worker_type || 'external',
|
||||
next.review_note || '',
|
||||
next.level_id || null,
|
||||
Number(next.session_version || 1),
|
||||
|
||||
Reference in New Issue
Block a user