拆分后台工单指派服务

This commit is contained in:
yml2213
2026-08-21 17:31:39 +08:00
parent 2b79034a98
commit 5af2dd08e0
3 changed files with 139 additions and 132 deletions
@@ -2,18 +2,15 @@ import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
import {
acceptWorkOrderAndSettle,
acceptWorkOrderShareAndSettle,
assignWorkOrderToWorker,
cancelAssignedWorkOrder,
cancelWorkOrderShare,
returnAssignedWorkOrderToHall,
unassignWorkOrder,
countWorkerActiveOrders,
countWorkOrderPendingSharingSubmissions,
createWorkProductMatchLog,
createWorkOrder,
createWorkOrderEvent,
deductPendingDepositUnfreeze,
getWorkerUserById,
getWorkOrderByOrderItemId,
listWorkOrders,
listWorkProductRules,
@@ -37,7 +34,6 @@ import {
publishWorkerWalletRealtimeChange,
publishWorkOrderRealtimeChange,
} from '../realtime/realtime-event-service.js'
import { consumeIndustryVouchersBeforeWorkOrderAssign } from './publish-voucher-service.js'
import {
mapWorkOrderAdmin,
@@ -47,13 +43,11 @@ import {
normalizeOptionalId,
normalizeRequirementFields,
normalizePositiveInteger,
resolveFreezeDepositAmount,
resolveMatchingProductRuleDecision,
resolveKuaishouWorkProductMatchContext,
resolveSkuNameQuantity,
resolveWorkOrderRulePricing,
resolveWorkOrderSharingEnabled,
resolveWorkerPermissions,
resolveWorkOrderOperationalStatus,
} from './mappers.js'
import { normalizeWorkOrderTimeoutPolicy } from './work-order-timeout-policy.js'
@@ -66,132 +60,7 @@ export {
reviewAdminWorkerUser,
} from './admin-worker-user-service.js'
export async function assignAdminWorkOrderToWorker(
workOrderId: number | string,
payload: JsonObject = {},
actorName = '',
) {
const workOrder = await getRequiredWorkOrder(workOrderId)
const capabilities = mapWorkOrderAdmin(workOrder).capabilities
if (!capabilities.canAssign) {
throw createHttpError('只有未分配或待抢单且未指派打手的订单才能指派', {
statusCode: 409,
errorCode: 'work_order_not_assignable',
})
}
const workerId = normalizeOptionalId(payload.workerId ?? payload.worker_id)
if (!workerId) {
throw createHttpError('请选择要指派的打手', {
statusCode: 400,
errorCode: 'work_order_assign_worker_required',
})
}
const worker = await getWorkerUserById(workerId)
if (!worker || worker.status !== 'active') {
throw createHttpError('打手账号不存在或不可用', {
statusCode: 409,
errorCode: 'work_order_assign_worker_invalid',
})
}
const permissions = resolveWorkerPermissions(worker)
const activeCount = await countWorkerActiveOrders(worker.id)
if (activeCount >= permissions.maxActiveOrders) {
throw createHttpError('该打手已达到最大同时接单量', {
statusCode: 409,
errorCode: 'worker_active_order_limit',
})
}
const freezeAmount = resolveFreezeDepositAmount(workOrder, permissions)
if (Number(worker.available_amount || 0) < freezeAmount) {
throw createHttpError('该打手余额不足,无法冻结所需押金', {
statusCode: 409,
errorCode: 'worker_deposit_insufficient',
})
}
const voucherConsume = await consumeIndustryVouchersBeforeWorkOrderAssign(workOrder)
if (!voucherConsume.ok) {
const failedAt = nowIso()
await createWorkOrderEvent({
workOrderId: workOrder.id,
actorType: 'admin',
actorId: actorName,
eventType: 'assign_voucher_consume_failed',
fromStatus: workOrder.status,
toStatus: workOrder.status,
payloadJson: JSON.stringify({ workerId: worker.id, voucherConsume }),
now: failedAt,
})
throw createHttpError(voucherConsume.errorMessage || '电子凭证核销失败,订单未指派', {
statusCode: 409,
errorCode: 'work_order_assign_voucher_consume_failed',
context: voucherConsume,
})
}
const now = nowIso()
const { order: updated, failureReason } = await assignWorkOrderToWorker({
workOrderId: workOrder.id,
workerId: worker.id,
depositAmount: freezeAmount,
maxActiveOrders: permissions.maxActiveOrders,
deadlineAt: resolveAssignedDeadlineAt(Number(workOrder.timeout_minutes || 0), now),
now,
actorName,
})
if (failureReason === 'work_order_not_assignable') {
throw createHttpError(
resolveAssignFailureMessage(voucherConsume.voucherCount, '订单状态已变化,无法指派'),
{
statusCode: 409,
errorCode: 'work_order_not_assignable',
},
)
}
if (failureReason === 'worker_active_order_limit') {
throw createHttpError(
resolveAssignFailureMessage(voucherConsume.voucherCount, '该打手已达到最大同时接单量'),
{
statusCode: 409,
errorCode: 'worker_active_order_limit',
},
)
}
if (failureReason === 'worker_deposit_insufficient') {
throw createHttpError(
resolveAssignFailureMessage(voucherConsume.voucherCount, '该打手余额不足,无法冻结所需押金'),
{
statusCode: 409,
errorCode: 'worker_deposit_insufficient',
},
)
}
if (!updated) {
throw createHttpError(
resolveAssignFailureMessage(voucherConsume.voucherCount, '指派失败,订单状态可能已变化'),
{
statusCode: 409,
errorCode: 'work_order_assign_conflict',
},
)
}
publishWorkOrderRealtimeChange({
workOrderId: Number(workOrder.id),
workerIds: [Number(worker.id)],
hallChanged: workOrder.status === WORK_ORDER_STATUS.OPEN,
})
publishWorkerWalletRealtimeChange(Number(worker.id))
return { order: mapWorkOrderAdmin(updated), voucherConsume }
}
function resolveAssignFailureMessage(voucherCount: number, fallback: string) {
return voucherCount > 0 ? `电子凭证已核销,但${fallback},请重试指派` : fallback
}
function resolveAssignedDeadlineAt(timeoutMinutes: number, now: string): string | null {
if (timeoutMinutes <= 0) return null
return new Date(new Date(now).getTime() + timeoutMinutes * 60 * 1000).toISOString()
}
export { assignAdminWorkOrderToWorker } from './admin-work-order-assignment-service.js'
export { getAdminWorkOrderEvents, listAdminWorkOrders } from './admin-work-order-query-service.js'
@@ -0,0 +1,137 @@
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
import {
assignWorkOrderToWorker,
countWorkerActiveOrders,
createWorkOrderEvent,
getWorkerUserById,
} from '../../repositories/worker-platform/index.js'
import type { JsonObject } from '../../types/json.js'
import { createHttpError } from '../../utils/http.js'
import { nowIso } from '../../utils/time.js'
import {
publishWorkerWalletRealtimeChange,
publishWorkOrderRealtimeChange,
} from '../realtime/realtime-event-service.js'
import { consumeIndustryVouchersBeforeWorkOrderAssign } from './publish-voucher-service.js'
import {
mapWorkOrderAdmin,
normalizeOptionalId,
resolveFreezeDepositAmount,
resolveWorkerPermissions,
} from './mappers.js'
import { getRequiredWorkOrder } from './worker-session-context-service.js'
export async function assignAdminWorkOrderToWorker(
workOrderId: number | string,
payload: JsonObject = {},
actorName = '',
) {
const workOrder = await getRequiredWorkOrder(workOrderId)
const capabilities = mapWorkOrderAdmin(workOrder).capabilities
if (!capabilities.canAssign) {
throw createHttpError('只有未分配或待抢单且未指派打手的订单才能指派', {
statusCode: 409,
errorCode: 'work_order_not_assignable',
})
}
const workerId = normalizeOptionalId(payload.workerId ?? payload.worker_id)
if (!workerId) {
throw createHttpError('请选择要指派的打手', {
statusCode: 400,
errorCode: 'work_order_assign_worker_required',
})
}
const worker = await getWorkerUserById(workerId)
if (!worker || worker.status !== 'active') {
throw createHttpError('打手账号不存在或不可用', {
statusCode: 409,
errorCode: 'work_order_assign_worker_invalid',
})
}
const permissions = resolveWorkerPermissions(worker)
const activeCount = await countWorkerActiveOrders(worker.id)
if (activeCount >= permissions.maxActiveOrders) {
throw createHttpError('该打手已达到最大同时接单量', {
statusCode: 409,
errorCode: 'worker_active_order_limit',
})
}
const freezeAmount = resolveFreezeDepositAmount(workOrder, permissions)
if (Number(worker.available_amount || 0) < freezeAmount) {
throw createHttpError('该打手余额不足,无法冻结所需押金', {
statusCode: 409,
errorCode: 'worker_deposit_insufficient',
})
}
const voucherConsume = await consumeIndustryVouchersBeforeWorkOrderAssign(workOrder)
if (!voucherConsume.ok) {
const failedAt = nowIso()
await createWorkOrderEvent({
workOrderId: workOrder.id,
actorType: 'admin',
actorId: actorName,
eventType: 'assign_voucher_consume_failed',
fromStatus: workOrder.status,
toStatus: workOrder.status,
payloadJson: JSON.stringify({ workerId: worker.id, voucherConsume }),
now: failedAt,
})
throw createHttpError(voucherConsume.errorMessage || '电子凭证核销失败,订单未指派', {
statusCode: 409,
errorCode: 'work_order_assign_voucher_consume_failed',
context: voucherConsume,
})
}
const now = nowIso()
const { order: updated, failureReason } = await assignWorkOrderToWorker({
workOrderId: workOrder.id,
workerId: worker.id,
depositAmount: freezeAmount,
maxActiveOrders: permissions.maxActiveOrders,
deadlineAt: resolveAssignedDeadlineAt(Number(workOrder.timeout_minutes || 0), now),
now,
actorName,
})
if (failureReason === 'work_order_not_assignable') {
throw createHttpError(
resolveAssignFailureMessage(voucherConsume.voucherCount, '订单状态已变化,无法指派'),
{ statusCode: 409, errorCode: 'work_order_not_assignable' },
)
}
if (failureReason === 'worker_active_order_limit') {
throw createHttpError(
resolveAssignFailureMessage(voucherConsume.voucherCount, '该打手已达到最大同时接单量'),
{ statusCode: 409, errorCode: 'worker_active_order_limit' },
)
}
if (failureReason === 'worker_deposit_insufficient') {
throw createHttpError(
resolveAssignFailureMessage(voucherConsume.voucherCount, '该打手余额不足,无法冻结所需押金'),
{ statusCode: 409, errorCode: 'worker_deposit_insufficient' },
)
}
if (!updated) {
throw createHttpError(
resolveAssignFailureMessage(voucherConsume.voucherCount, '指派失败,订单状态可能已变化'),
{ statusCode: 409, errorCode: 'work_order_assign_conflict' },
)
}
publishWorkOrderRealtimeChange({
workOrderId: Number(workOrder.id),
workerIds: [Number(worker.id)],
hallChanged: workOrder.status === WORK_ORDER_STATUS.OPEN,
})
publishWorkerWalletRealtimeChange(Number(worker.id))
return { order: mapWorkOrderAdmin(updated), voucherConsume }
}
function resolveAssignFailureMessage(voucherCount: number, fallback: string) {
return voucherCount > 0 ? `电子凭证已核销,但${fallback},请重试指派` : fallback
}
function resolveAssignedDeadlineAt(timeoutMinutes: number, now: string): string | null {
if (timeoutMinutes <= 0) return null
return new Date(new Date(now).getTime() + timeoutMinutes * 60 * 1000).toISOString()
}
@@ -34,6 +34,7 @@ export * from './admin-work-order-material-service.js'
export * from './admin-mock-work-order-service.js'
export * from './admin-work-order-management-service.js'
export * from './admin-worker-platform-summary-service.js'
export * from './admin-work-order-assignment-service.js'
export {
acceptAdminWorkOrder,
acceptAdminWorkOrderShare,