From 5af2dd08e0568c7ad312b44670d72e929dd1d952 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Fri, 21 Aug 2026 17:31:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E5=88=86=E5=90=8E=E5=8F=B0=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E6=8C=87=E6=B4=BE=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../services/worker-platform/admin-service.ts | 133 +---------------- .../admin-work-order-assignment-service.ts | 137 ++++++++++++++++++ .../src/services/worker-platform/index.ts | 1 + 3 files changed, 139 insertions(+), 132 deletions(-) create mode 100644 apps/backend/src/services/worker-platform/admin-work-order-assignment-service.ts diff --git a/apps/backend/src/services/worker-platform/admin-service.ts b/apps/backend/src/services/worker-platform/admin-service.ts index ab74e8a8..1378ebf8 100644 --- a/apps/backend/src/services/worker-platform/admin-service.ts +++ b/apps/backend/src/services/worker-platform/admin-service.ts @@ -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' diff --git a/apps/backend/src/services/worker-platform/admin-work-order-assignment-service.ts b/apps/backend/src/services/worker-platform/admin-work-order-assignment-service.ts new file mode 100644 index 00000000..13190bcf --- /dev/null +++ b/apps/backend/src/services/worker-platform/admin-work-order-assignment-service.ts @@ -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() +} diff --git a/apps/backend/src/services/worker-platform/index.ts b/apps/backend/src/services/worker-platform/index.ts index ee0a2f76..b4db2c6f 100644 --- a/apps/backend/src/services/worker-platform/index.ts +++ b/apps/backend/src/services/worker-platform/index.ts @@ -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,