From 49f99e52f2126001ffa09eb117450e20884e84c9 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Fri, 21 Aug 2026 17:23:51 +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=E9=AA=8C=E6=94=B6=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin-work-order-acceptance-service.ts | 193 ++++++++++++++++++ .../src/services/worker-platform/index.ts | 6 + 2 files changed, 199 insertions(+) create mode 100644 apps/backend/src/services/worker-platform/admin-work-order-acceptance-service.ts diff --git a/apps/backend/src/services/worker-platform/admin-work-order-acceptance-service.ts b/apps/backend/src/services/worker-platform/admin-work-order-acceptance-service.ts new file mode 100644 index 00000000..93c0060d --- /dev/null +++ b/apps/backend/src/services/worker-platform/admin-work-order-acceptance-service.ts @@ -0,0 +1,193 @@ +import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js' +import { + acceptWorkOrderAndSettle, + acceptWorkOrderShareAndSettle, + cancelWorkOrderShare, + countWorkOrderPendingSharingSubmissions, + updateWorkOrder, +} 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 { resolveAdminNotificationEntity } from '../admin/admin-notification-service.js' +import { + publishWorkerWalletRealtimeChange, + publishWorkOrderRealtimeChange, +} from '../realtime/realtime-event-service.js' +import { + mapWorkOrderAdmin, + mapWorkOrderShare, + normalizeOptionalId, + resolveWorkOrderOperationalStatus, +} from './mappers.js' +import { resolveWorkOrderRealtimeWorkerIds } from './admin-work-order-realtime-service.js' +import { getWorkerFinanceConfig } from './worker-finance-config-service.js' +import { getRequiredWorkOrder } from './worker-session-context-service.js' + +export async function acceptAdminWorkOrder(workOrderId: number | string, actorName = '') { + let workOrder = await getRequiredWorkOrder(workOrderId) + if (!mapWorkOrderAdmin(workOrder).capabilities.canAccept) { + throw createHttpError('只有待验收订单可以通过验收', { + statusCode: 409, + errorCode: 'work_order_accept_status_invalid', + }) + } + // 历史拼单订单可能仍保留 open,验收前先补齐母工单生命周期状态。 + if (workOrder.status === WORK_ORDER_STATUS.OPEN) { + const promoted = await updateWorkOrder(workOrder.id, { + status: WORK_ORDER_STATUS.PENDING_ACCEPTANCE, + updated_at: nowIso(), + }) + if (promoted) workOrder = promoted + } + const now = nowIso() + const { order: updated, failureReason } = await acceptWorkOrderAndSettle({ + workOrderId: workOrder.id, + unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays, + now, + actorType: 'admin', + actorName, + }) + if (failureReason === 'sharing_pending_submissions') { + throw createHttpError('还有拼单打手未提交验收资料,暂不能统一验收', { + statusCode: 409, + errorCode: 'work_order_sharing_pending_submissions', + }) + } + if (!updated) { + throw createHttpError('验收失败,订单状态可能已变化', { + statusCode: 409, + errorCode: 'work_order_accept_conflict', + }) + } + await resolveAdminNotificationEntity('work_order', Number(workOrder.id), 'accepted') + const workerIds = await resolveWorkOrderRealtimeWorkerIds(workOrder) + publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), workerIds }) + for (const workerId of workerIds) publishWorkerWalletRealtimeChange(workerId) + return { order: mapWorkOrderAdmin(updated || workOrder) } +} + +export async function acceptAdminWorkOrderShare( + workOrderId: number | string, + shareId: number | string, + actorName = '', +) { + const result = await acceptWorkOrderShareAndSettle({ + workOrderId: Number(workOrderId), + shareId: Number(shareId), + unfreezeDays: getWorkerFinanceConfig().depositUnfreezeDays, + now: nowIso(), + actorType: 'admin', + actorName, + }) + if (result.failureReason === 'work_order_not_accepting') { + throw createHttpError('当前母工单不能验收拼单份额', { + statusCode: 409, + errorCode: 'work_order_sharing_accept_status_invalid', + }) + } + if (result.failureReason === 'share_not_submitted' || !result.share || !result.order) { + throw createHttpError('该拼单参与者尚未提交验收资料或已被验收', { + statusCode: 409, + errorCode: 'work_order_sharing_share_accept_status_invalid', + }) + } + const workerId = Number(result.share.worker_id || 0) + publishWorkOrderRealtimeChange({ + workOrderId: Number(workOrderId), + workerIds: workerId > 0 ? [workerId] : [], + }) + if (workerId > 0) publishWorkerWalletRealtimeChange(workerId) + if (result.order.status === WORK_ORDER_STATUS.ACCEPTED) { + await resolveAdminNotificationEntity('work_order', Number(workOrderId), 'accepted') + } + return { order: mapWorkOrderAdmin(result.order), share: mapWorkOrderShare(result.share) } +} + +export async function cancelAdminWorkOrderShare( + workOrderId: number | string, + shareId: number | string, + payload: JsonObject = {}, + actorName = '', +) { + const result = await cancelWorkOrderShare({ + workOrderId: Number(workOrderId), + shareId: Number(shareId), + now: nowIso(), + reason: String(payload.reason || '').trim(), + actorName, + }) + if (result.failureReason === 'work_order_not_cancellable') { + throw createHttpError('当前拼单状态不能撤销参与者', { + statusCode: 409, + errorCode: 'work_order_sharing_cancel_status_invalid', + }) + } + if (result.failureReason === 'share_not_found') { + throw createHttpError('未找到该拼单参与记录', { + statusCode: 404, + errorCode: 'work_order_sharing_share_not_found', + }) + } + if (result.failureReason === 'share_status_not_cancellable' || !result.share || !result.order) { + throw createHttpError('只有已参与或已提交验收的拼单份额可以撤单', { + statusCode: 409, + errorCode: 'work_order_sharing_share_cancel_status_invalid', + }) + } + const workerId = Number(result.share.worker_id || 0) + publishWorkOrderRealtimeChange({ + workOrderId: Number(workOrderId), + workerIds: await resolveWorkOrderRealtimeWorkerIds(result.order), + hallChanged: true, + }) + if (workerId > 0) publishWorkerWalletRealtimeChange(workerId) + return { + order: mapWorkOrderAdmin(result.order), + share: mapWorkOrderShare(result.share), + releasedDepositAmount: result.releasedDepositAmount, + } +} + +export async function acceptAdminWorkOrders(payload: JsonObject = {}, actorName = '') { + const workOrderIds = [ + ...new Set( + (Array.isArray(payload.workOrderIds) ? payload.workOrderIds : []) + .map((item) => normalizeOptionalId(item)) + .filter((item): item is number => item !== null), + ), + ] + if (workOrderIds.length === 0) { + throw createHttpError('请至少选择一个待验收订单', { + statusCode: 400, + errorCode: 'work_order_batch_accept_required', + }) + } + const workOrders = await Promise.all( + workOrderIds.map((workOrderId) => getRequiredWorkOrder(workOrderId)), + ) + if ( + workOrders.some( + (workOrder) => + resolveWorkOrderOperationalStatus(workOrder) !== WORK_ORDER_STATUS.PENDING_ACCEPTANCE, + ) + ) { + throw createHttpError('选中的订单包含非待验收状态,请刷新后重试', { + statusCode: 409, + errorCode: 'work_order_batch_accept_status_invalid', + }) + } + const pendingSharingCounts = await Promise.all( + workOrders.map((workOrder) => countWorkOrderPendingSharingSubmissions(workOrder.id)), + ) + if (pendingSharingCounts.some((count) => count > 0)) { + throw createHttpError('选中的订单存在拼单打手未提交验收资料,暂不能统一验收', { + statusCode: 409, + errorCode: 'work_order_batch_accept_sharing_pending_submissions', + }) + } + const results = [] + for (const workOrder of workOrders) + results.push(await acceptAdminWorkOrder(workOrder.id, actorName)) + return { orders: results.map((result) => result.order) } +} diff --git a/apps/backend/src/services/worker-platform/index.ts b/apps/backend/src/services/worker-platform/index.ts index af8f0558..a49de7c8 100644 --- a/apps/backend/src/services/worker-platform/index.ts +++ b/apps/backend/src/services/worker-platform/index.ts @@ -33,6 +33,12 @@ export * from './admin-work-order-realtime-service.js' 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 { + acceptAdminWorkOrder, + acceptAdminWorkOrderShare, + acceptAdminWorkOrders, + cancelAdminWorkOrderShare, +} from './admin-work-order-acceptance-service.js' export * from './worker-product-match-config-service.js' export * from './worker-announcement-config-service.js' export * from './worker-hall-config-service.js'