From 499293c7cf52f69377d0b09270c092467abf5ec3 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Fri, 21 Aug 2026 17:22:13 +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=E7=AE=A1=E7=90=86=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 | 394 +---------------- .../admin-work-order-management-service.ts | 400 ++++++++++++++++++ .../src/services/worker-platform/index.ts | 1 + 3 files changed, 413 insertions(+), 382 deletions(-) create mode 100644 apps/backend/src/services/worker-platform/admin-work-order-management-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 1bb56c0d..6ae2eac2 100644 --- a/apps/backend/src/services/worker-platform/admin-service.ts +++ b/apps/backend/src/services/worker-platform/admin-service.ts @@ -13,18 +13,13 @@ import { createWorkOrder, createWorkOrderEvent, deductPendingDepositUnfreeze, - deleteWorkOrder, getWorkerUserById, getWorkOrderByOrderItemId, listWorkOrders, listWorkProductRules, listWorkProductRuleMappings, listWorkerUsers, - listWorkOrderShares, - resolveProblemWorkOrder, - reopenCancelledWorkOrder, updateWorkOrder, - updateWorkOrderBasic, type WorkOrderRow, } from '../../repositories/worker-platform/index.js' import type { JsonObject } from '../../types/json.js' @@ -45,10 +40,7 @@ import { publishWorkerWalletRealtimeChange, publishWorkOrderRealtimeChange, } from '../realtime/realtime-event-service.js' -import { - consumeIndustryVouchersBeforeWorkOrderAssign, - consumeIndustryVouchersBeforeWorkOrderPublish, -} from './publish-voucher-service.js' +import { consumeIndustryVouchersBeforeWorkOrderAssign } from './publish-voucher-service.js' import { mapWorkOrderAdmin, @@ -56,14 +48,11 @@ import { normalizeAmountFen, normalizeInteger, normalizeOptionalId, - normalizePositiveInteger, - normalizeProblemResolutionAction, normalizeRequirementFields, - normalizeRequirementFieldsFromPayload, + normalizePositiveInteger, resolveFreezeDepositAmount, resolveMatchingProductRuleDecision, resolveKuaishouWorkProductMatchContext, - resolveRequirementFields, resolveSkuNameQuantity, resolveWorkOrderRulePricing, resolveWorkOrderSharingEnabled, @@ -290,375 +279,16 @@ export { resolveAdminMockWorkOrderInput, } from './admin-mock-work-order-service.js' -export async function updateAdminWorkOrder( - workOrderId: number | string, - payload: JsonObject = {}, - options: { canEditRequirementFields?: boolean; actorName?: string } = {}, -) { - const workOrder = await getRequiredWorkOrder(workOrderId) - if (!mapWorkOrderAdmin(workOrder).capabilities.canEdit) { - throw createHttpError('只有待完善、未分配或待抢单的工单可以编辑', { - statusCode: 409, - errorCode: 'work_order_edit_status_invalid', - }) - } - if (workOrder.assigned_worker_id) { - throw createHttpError('工单已被打手接走,不能编辑', { - statusCode: 409, - errorCode: 'work_order_edit_assigned_invalid', - }) - } - - const fields = - options.canEditRequirementFields === false - ? resolveRequirementFields(workOrder) - : normalizeRequirementFieldsFromPayload(payload) - const rewardAmount = - payload.rewardAmount === undefined - ? workOrder.reward_amount - : normalizeAmountFen(payload.rewardAmount, workOrder.reward_amount) - const requiredDepositAmount = - payload.requiredDepositAmount === undefined - ? workOrder.required_deposit_amount - : normalizeAmountFen(payload.requiredDepositAmount, workOrder.required_deposit_amount) - const depositThresholdAmount = - payload.depositThresholdAmount === undefined - ? workOrder.deposit_threshold_amount - : normalizeAmountFen(payload.depositThresholdAmount, workOrder.deposit_threshold_amount) - const categoryId = - payload.categoryId === undefined || payload.categoryId === null || payload.categoryId === '' - ? workOrder.category_id - : (normalizeOptionalId(payload.categoryId) ?? workOrder.category_id) - - const now = nowIso() - const updated = await updateWorkOrderBasic(workOrder.id, { - productName: String(payload.productName ?? workOrder.product_name).trim(), - platformOrderId: String(payload.platformOrderId ?? workOrder.platform_order_id ?? '').trim(), - categoryId, - rewardAmount, - requiredDepositAmount, - depositThresholdAmount, - timeoutMinutes: - payload.timeoutMinutes === undefined - ? workOrder.timeout_minutes - : Math.max(0, normalizeInteger(payload.timeoutMinutes, workOrder.timeout_minutes)), - timeoutPolicy: normalizeWorkOrderTimeoutPolicy( - payload.timeoutPolicy ?? workOrder.timeout_policy, - ), - requirementJson: JSON.stringify({ fields }), - updatedAt: now, - }) - await createWorkOrderEvent({ - workOrderId: workOrder.id, - actorType: 'admin', - actorId: options.actorName || '', - eventType: 'edited', - fromStatus: workOrder.status, - toStatus: workOrder.status, - payloadJson: JSON.stringify({ - productName: updated?.product_name, - rewardAmount, - requiredDepositAmount, - timeoutMinutes: updated?.timeout_minutes, - timeoutPolicy: updated?.timeout_policy, - fields, - }), - now, - }) - publishWorkOrderRealtimeChange({ - workOrderId: Number(workOrder.id), - hallChanged: workOrder.status === WORK_ORDER_STATUS.OPEN, - }) - return { order: mapWorkOrderAdmin(updated || workOrder) } -} - -export async function deleteAdminWorkOrder(workOrderId: number | string) { - const workOrder = await getRequiredWorkOrder(workOrderId) - if (!mapWorkOrderAdmin(workOrder).capabilities.canDelete) { - throw createHttpError('只有待完善、未分配或已取消的工单可以删除', { - statusCode: 409, - errorCode: 'work_order_delete_status_invalid', - }) - } - if (workOrder.assigned_worker_id) { - throw createHttpError('工单已被打手接走,不能删除', { - statusCode: 409, - errorCode: 'work_order_delete_assigned_invalid', - }) - } - const shares = await listWorkOrderShares(workOrder.id) - const activeShares = shares.filter((share) => share.status !== 'cancelled') - if (activeShares.length > 0) { - throw createHttpError('工单存在参与中的拼单,不能删除', { - statusCode: 409, - errorCode: 'work_order_delete_has_shares', - }) - } - const deleted = await deleteWorkOrder(workOrder.id) - if (!deleted) { - throw createHttpError('工单删除失败', { - statusCode: 500, - errorCode: 'work_order_delete_failed', - }) - } - publishWorkOrderRealtimeChange({ - workOrderId: Number(workOrder.id), - hallChanged: workOrder.status === WORK_ORDER_STATUS.OPEN, - }) - return { deleted: true } -} - -export async function reopenAdminCancelledWorkOrder(workOrderId: number | string, actorName = '') { - const workOrder = await getRequiredWorkOrder(workOrderId) - if (!mapWorkOrderAdmin(workOrder).capabilities.canReopen) { - throw createHttpError('只有没有有效拼单份额的已取消订单可以重新启用', { - statusCode: 409, - errorCode: 'work_order_reopen_status_invalid', - }) - } - - const { order: updated, failureReason } = await reopenCancelledWorkOrder({ - workOrderId: Number(workOrder.id), - now: nowIso(), - actorName, - }) - if (failureReason === 'work_order_has_active_shares') { - throw createHttpError('该订单仍有有效拼单份额,不能重新启用', { - statusCode: 409, - errorCode: 'work_order_reopen_has_active_shares', - }) - } - if (!updated) { - throw createHttpError('订单状态已变化,无法重新启用', { - statusCode: 409, - errorCode: 'work_order_reopen_conflict', - }) - } - - publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true }) - return { order: mapWorkOrderAdmin(updated) } -} - -export async function publishAdminWorkOrder(workOrderId: number | string, actorName = '') { - const workOrder = await getRequiredWorkOrder(workOrderId) - if (workOrder.status !== WORK_ORDER_STATUS.UNASSIGNED) { - throw createHttpError('只有未分配订单可以发布到大厅', { - statusCode: 409, - errorCode: 'work_order_publish_status_invalid', - }) - } - if (Number(workOrder.reward_amount || 0) <= 0) { - throw createHttpError('接单金额为 0,无法发布到大厅', { - statusCode: 409, - errorCode: 'work_order_publish_reward_invalid', - }) - } - - const voucherConsume = await consumeIndustryVouchersBeforeWorkOrderPublish(workOrder) - if (!voucherConsume.ok) { - const failedAt = nowIso() - await createWorkOrderEvent({ - workOrderId: workOrder.id, - actorType: 'admin', - actorId: actorName, - eventType: 'publish_voucher_consume_failed', - fromStatus: workOrder.status, - toStatus: workOrder.status, - payloadJson: JSON.stringify(voucherConsume), - now: failedAt, - }) - throw createHttpError(voucherConsume.errorMessage || '电子凭证核销失败,订单未发布', { - statusCode: 409, - errorCode: 'work_order_publish_voucher_consume_failed', - context: voucherConsume, - }) - } - - const now = nowIso() - const updated = await updateWorkOrder(workOrder.id, { - status: WORK_ORDER_STATUS.OPEN, - published_at: now, - hall_queued_at: now, - updated_at: now, - }) - if (!updated) { - throw createHttpError( - voucherConsume.voucherCount > 0 - ? '电子凭证已核销,但订单发布失败,请重试发布' - : '订单发布失败,请重试', - { - statusCode: 409, - errorCode: 'work_order_publish_update_failed', - context: voucherConsume, - }, - ) - } - await createWorkOrderEvent({ - workOrderId: workOrder.id, - actorType: 'admin', - actorId: actorName, - eventType: 'published', - fromStatus: workOrder.status, - toStatus: WORK_ORDER_STATUS.OPEN, - payloadJson: JSON.stringify({ voucherConsume }), - now, - }) - publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true }) - const hallConfig = getWorkerHallConfig() - const hallEntry = await listWorkOrders({ - page: 1, - pageSize: 1, - workOrderId: Number(workOrder.id), - statuses: [WORK_ORDER_STATUS.OPEN], - hallCandidateLimit: hallConfig.maxVisibleOrders, - excludeFilledSharing: true, - }) - return { - order: mapWorkOrderAdmin(updated), - voucherConsume, - hallVisible: hallEntry.total > 0, - } -} - -export async function unpublishAdminWorkOrder(workOrderId: number | string, actorName = '') { - const workOrder = await getRequiredWorkOrder(workOrderId) - if (!mapWorkOrderAdmin(workOrder).capabilities.canUnpublish) { - throw createHttpError('只有待抢单且未被接走的订单可以下架', { - statusCode: 409, - errorCode: 'work_order_unpublish_status_invalid', - }) - } - const now = nowIso() - const updated = await updateWorkOrder(workOrder.id, { - status: WORK_ORDER_STATUS.UNASSIGNED, - published_at: null, - updated_at: now, - }) - await createWorkOrderEvent({ - workOrderId: workOrder.id, - actorType: 'admin', - actorId: actorName, - eventType: 'unpublished', - fromStatus: workOrder.status, - toStatus: WORK_ORDER_STATUS.UNASSIGNED, - now, - }) - publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true }) - return { order: mapWorkOrderAdmin(updated || workOrder) } -} - -export async function pinAdminWorkOrder( - workOrderId: number | string, - payload: JsonObject = {}, - actorName = '', -) { - const workOrder = await getRequiredWorkOrder(workOrderId) - const pinned = payload.pinned === true - const capabilities = mapWorkOrderAdmin(workOrder).capabilities - if (pinned && (!capabilities.canPin || !mapWorkOrderAdmin(workOrder).hallAvailable)) { - throw createHttpError('只有待抢单订单可以置顶', { - statusCode: 409, - errorCode: 'work_order_pin_status_invalid', - }) - } - const now = nowIso() - const updated = await updateWorkOrder(workOrder.id, { - pinned_at: pinned ? now : null, - updated_at: now, - }) - await createWorkOrderEvent({ - workOrderId: workOrder.id, - actorType: 'admin', - actorId: actorName, - eventType: pinned ? 'pinned' : 'unpinned', - fromStatus: workOrder.status, - toStatus: workOrder.status, - now, - }) - publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true }) - return { order: mapWorkOrderAdmin(updated || workOrder) } -} - -export async function markAdminWorkOrderProblem( - workOrderId: number | string, - payload: JsonObject = {}, - actorName = '', -) { - const workOrder = await getRequiredWorkOrder(workOrderId) - if (!mapWorkOrderAdmin(workOrder).capabilities.canMarkProblem) { - throw createHttpError('当前状态不能标记问题单', { - statusCode: 409, - errorCode: 'work_order_problem_status_invalid', - }) - } - const now = nowIso() - const note = String(payload.note || payload.problemNote || '').trim() - const updated = await updateWorkOrder(workOrder.id, { - status: WORK_ORDER_STATUS.PROBLEM, - problem_note: note, - updated_at: now, - }) - await createWorkOrderEvent({ - workOrderId: workOrder.id, - actorType: 'admin', - actorId: actorName, - eventType: 'marked_problem', - fromStatus: workOrder.status, - toStatus: WORK_ORDER_STATUS.PROBLEM, - payloadJson: JSON.stringify({ note }), - now, - }) - if (workOrder.status === WORK_ORDER_STATUS.PENDING_ACCEPTANCE) { - await resolveAdminNotificationEntity('work_order', Number(workOrder.id), 'marked_problem') - } - publishWorkOrderRealtimeChange({ - workOrderId: Number(workOrder.id), - workerIds: await resolveWorkOrderRealtimeWorkerIds(workOrder), - }) - return { order: mapWorkOrderAdmin(updated || workOrder) } -} - -export async function resolveAdminProblemWorkOrder( - workOrderId: number | string, - payload: JsonObject = {}, - actorName = '', -) { - const workOrder = await getRequiredWorkOrder(workOrderId) - if (workOrder.status !== WORK_ORDER_STATUS.PROBLEM) { - throw createHttpError('只有问题单可以执行处置', { - statusCode: 409, - errorCode: 'work_order_problem_resolution_status_invalid', - }) - } - - const action = normalizeProblemResolutionAction(payload.action) - const note = String(payload.note || '').trim() - if (!note) { - throw createHttpError('请填写处置备注', { - statusCode: 400, - errorCode: 'work_order_problem_resolution_note_required', - }) - } - - const updated = await resolveProblemWorkOrder({ - workOrderId: workOrder.id, - action, - note, - now: nowIso(), - actorName, - }) - if (!updated) { - throw createHttpError('问题单处置失败,订单状态可能已变化', { - statusCode: 409, - errorCode: 'work_order_problem_resolution_conflict', - }) - } - publishWorkOrderRealtimeChange({ - workOrderId: Number(workOrder.id), - workerIds: await resolveWorkOrderRealtimeWorkerIds(workOrder), - }) - return { order: mapWorkOrderAdmin(updated) } -} +export { + deleteAdminWorkOrder, + markAdminWorkOrderProblem, + pinAdminWorkOrder, + publishAdminWorkOrder, + reopenAdminCancelledWorkOrder, + resolveAdminProblemWorkOrder, + unpublishAdminWorkOrder, + updateAdminWorkOrder, +} from './admin-work-order-management-service.js' export async function acceptAdminWorkOrder(workOrderId: number | string, actorName = '') { let workOrder = await getRequiredWorkOrder(workOrderId) diff --git a/apps/backend/src/services/worker-platform/admin-work-order-management-service.ts b/apps/backend/src/services/worker-platform/admin-work-order-management-service.ts new file mode 100644 index 00000000..3cffee3a --- /dev/null +++ b/apps/backend/src/services/worker-platform/admin-work-order-management-service.ts @@ -0,0 +1,400 @@ +import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js' +import { + createWorkOrderEvent, + deleteWorkOrder, + listWorkOrderShares, + listWorkOrders, + reopenCancelledWorkOrder, + resolveProblemWorkOrder, + updateWorkOrder, + updateWorkOrderBasic, +} 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 { publishWorkOrderRealtimeChange } from '../realtime/realtime-event-service.js' +import { consumeIndustryVouchersBeforeWorkOrderPublish } from './publish-voucher-service.js' +import { + mapWorkOrderAdmin, + normalizeAmountFen, + normalizeInteger, + normalizeOptionalId, + normalizeProblemResolutionAction, + normalizeRequirementFieldsFromPayload, + resolveRequirementFields, +} from './mappers.js' +import { resolveWorkOrderRealtimeWorkerIds } from './admin-work-order-realtime-service.js' +import { getWorkerHallConfig } from './worker-hall-config-service.js' +import { getRequiredWorkOrder } from './worker-session-context-service.js' +import { normalizeWorkOrderTimeoutPolicy } from './work-order-timeout-policy.js' + +export async function updateAdminWorkOrder( + workOrderId: number | string, + payload: JsonObject = {}, + options: { canEditRequirementFields?: boolean; actorName?: string } = {}, +) { + const workOrder = await getRequiredWorkOrder(workOrderId) + if (!mapWorkOrderAdmin(workOrder).capabilities.canEdit) { + throw createHttpError('只有待完善、未分配或待抢单的工单可以编辑', { + statusCode: 409, + errorCode: 'work_order_edit_status_invalid', + }) + } + if (workOrder.assigned_worker_id) { + throw createHttpError('工单已被打手接走,不能编辑', { + statusCode: 409, + errorCode: 'work_order_edit_assigned_invalid', + }) + } + + const fields = + options.canEditRequirementFields === false + ? resolveRequirementFields(workOrder) + : normalizeRequirementFieldsFromPayload(payload) + const rewardAmount = + payload.rewardAmount === undefined + ? workOrder.reward_amount + : normalizeAmountFen(payload.rewardAmount, workOrder.reward_amount) + const requiredDepositAmount = + payload.requiredDepositAmount === undefined + ? workOrder.required_deposit_amount + : normalizeAmountFen(payload.requiredDepositAmount, workOrder.required_deposit_amount) + const depositThresholdAmount = + payload.depositThresholdAmount === undefined + ? workOrder.deposit_threshold_amount + : normalizeAmountFen(payload.depositThresholdAmount, workOrder.deposit_threshold_amount) + const categoryId = + payload.categoryId === undefined || payload.categoryId === null || payload.categoryId === '' + ? workOrder.category_id + : (normalizeOptionalId(payload.categoryId) ?? workOrder.category_id) + + const now = nowIso() + const updated = await updateWorkOrderBasic(workOrder.id, { + productName: String(payload.productName ?? workOrder.product_name).trim(), + platformOrderId: String(payload.platformOrderId ?? workOrder.platform_order_id ?? '').trim(), + categoryId, + rewardAmount, + requiredDepositAmount, + depositThresholdAmount, + timeoutMinutes: + payload.timeoutMinutes === undefined + ? workOrder.timeout_minutes + : Math.max(0, normalizeInteger(payload.timeoutMinutes, workOrder.timeout_minutes)), + timeoutPolicy: normalizeWorkOrderTimeoutPolicy( + payload.timeoutPolicy ?? workOrder.timeout_policy, + ), + requirementJson: JSON.stringify({ fields }), + updatedAt: now, + }) + await createWorkOrderEvent({ + workOrderId: workOrder.id, + actorType: 'admin', + actorId: options.actorName || '', + eventType: 'edited', + fromStatus: workOrder.status, + toStatus: workOrder.status, + payloadJson: JSON.stringify({ + productName: updated?.product_name, + rewardAmount, + requiredDepositAmount, + timeoutMinutes: updated?.timeout_minutes, + timeoutPolicy: updated?.timeout_policy, + fields, + }), + now, + }) + publishWorkOrderRealtimeChange({ + workOrderId: Number(workOrder.id), + hallChanged: workOrder.status === WORK_ORDER_STATUS.OPEN, + }) + return { order: mapWorkOrderAdmin(updated || workOrder) } +} + +export async function deleteAdminWorkOrder(workOrderId: number | string) { + const workOrder = await getRequiredWorkOrder(workOrderId) + if (!mapWorkOrderAdmin(workOrder).capabilities.canDelete) { + throw createHttpError('只有待完善、未分配或已取消的工单可以删除', { + statusCode: 409, + errorCode: 'work_order_delete_status_invalid', + }) + } + if (workOrder.assigned_worker_id) { + throw createHttpError('工单已被打手接走,不能删除', { + statusCode: 409, + errorCode: 'work_order_delete_assigned_invalid', + }) + } + const shares = await listWorkOrderShares(workOrder.id) + const activeShares = shares.filter((share) => share.status !== 'cancelled') + if (activeShares.length > 0) { + throw createHttpError('工单存在参与中的拼单,不能删除', { + statusCode: 409, + errorCode: 'work_order_delete_has_shares', + }) + } + const deleted = await deleteWorkOrder(workOrder.id) + if (!deleted) { + throw createHttpError('工单删除失败', { + statusCode: 500, + errorCode: 'work_order_delete_failed', + }) + } + publishWorkOrderRealtimeChange({ + workOrderId: Number(workOrder.id), + hallChanged: workOrder.status === WORK_ORDER_STATUS.OPEN, + }) + return { deleted: true } +} + +export async function reopenAdminCancelledWorkOrder(workOrderId: number | string, actorName = '') { + const workOrder = await getRequiredWorkOrder(workOrderId) + if (!mapWorkOrderAdmin(workOrder).capabilities.canReopen) { + throw createHttpError('只有没有有效拼单份额的已取消订单可以重新启用', { + statusCode: 409, + errorCode: 'work_order_reopen_status_invalid', + }) + } + + const { order: updated, failureReason } = await reopenCancelledWorkOrder({ + workOrderId: Number(workOrder.id), + now: nowIso(), + actorName, + }) + if (failureReason === 'work_order_has_active_shares') { + throw createHttpError('该订单仍有有效拼单份额,不能重新启用', { + statusCode: 409, + errorCode: 'work_order_reopen_has_active_shares', + }) + } + if (!updated) { + throw createHttpError('订单状态已变化,无法重新启用', { + statusCode: 409, + errorCode: 'work_order_reopen_conflict', + }) + } + + publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true }) + return { order: mapWorkOrderAdmin(updated) } +} + +export async function publishAdminWorkOrder(workOrderId: number | string, actorName = '') { + const workOrder = await getRequiredWorkOrder(workOrderId) + if (workOrder.status !== WORK_ORDER_STATUS.UNASSIGNED) { + throw createHttpError('只有未分配订单可以发布到大厅', { + statusCode: 409, + errorCode: 'work_order_publish_status_invalid', + }) + } + if (Number(workOrder.reward_amount || 0) <= 0) { + throw createHttpError('接单金额为 0,无法发布到大厅', { + statusCode: 409, + errorCode: 'work_order_publish_reward_invalid', + }) + } + + const voucherConsume = await consumeIndustryVouchersBeforeWorkOrderPublish(workOrder) + if (!voucherConsume.ok) { + const failedAt = nowIso() + await createWorkOrderEvent({ + workOrderId: workOrder.id, + actorType: 'admin', + actorId: actorName, + eventType: 'publish_voucher_consume_failed', + fromStatus: workOrder.status, + toStatus: workOrder.status, + payloadJson: JSON.stringify(voucherConsume), + now: failedAt, + }) + throw createHttpError(voucherConsume.errorMessage || '电子凭证核销失败,订单未发布', { + statusCode: 409, + errorCode: 'work_order_publish_voucher_consume_failed', + context: voucherConsume, + }) + } + + const now = nowIso() + const updated = await updateWorkOrder(workOrder.id, { + status: WORK_ORDER_STATUS.OPEN, + published_at: now, + hall_queued_at: now, + updated_at: now, + }) + if (!updated) { + throw createHttpError( + voucherConsume.voucherCount > 0 + ? '电子凭证已核销,但订单发布失败,请重试发布' + : '订单发布失败,请重试', + { + statusCode: 409, + errorCode: 'work_order_publish_update_failed', + context: voucherConsume, + }, + ) + } + await createWorkOrderEvent({ + workOrderId: workOrder.id, + actorType: 'admin', + actorId: actorName, + eventType: 'published', + fromStatus: workOrder.status, + toStatus: WORK_ORDER_STATUS.OPEN, + payloadJson: JSON.stringify({ voucherConsume }), + now, + }) + publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true }) + const hallConfig = getWorkerHallConfig() + const hallEntry = await listWorkOrders({ + page: 1, + pageSize: 1, + workOrderId: Number(workOrder.id), + statuses: [WORK_ORDER_STATUS.OPEN], + hallCandidateLimit: hallConfig.maxVisibleOrders, + excludeFilledSharing: true, + }) + return { + order: mapWorkOrderAdmin(updated), + voucherConsume, + hallVisible: hallEntry.total > 0, + } +} + +export async function unpublishAdminWorkOrder(workOrderId: number | string, actorName = '') { + const workOrder = await getRequiredWorkOrder(workOrderId) + if (!mapWorkOrderAdmin(workOrder).capabilities.canUnpublish) { + throw createHttpError('只有待抢单且未被接走的订单可以下架', { + statusCode: 409, + errorCode: 'work_order_unpublish_status_invalid', + }) + } + const now = nowIso() + const updated = await updateWorkOrder(workOrder.id, { + status: WORK_ORDER_STATUS.UNASSIGNED, + published_at: null, + updated_at: now, + }) + await createWorkOrderEvent({ + workOrderId: workOrder.id, + actorType: 'admin', + actorId: actorName, + eventType: 'unpublished', + fromStatus: workOrder.status, + toStatus: WORK_ORDER_STATUS.UNASSIGNED, + now, + }) + publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true }) + return { order: mapWorkOrderAdmin(updated || workOrder) } +} + +export async function pinAdminWorkOrder( + workOrderId: number | string, + payload: JsonObject = {}, + actorName = '', +) { + const workOrder = await getRequiredWorkOrder(workOrderId) + const pinned = payload.pinned === true + const capabilities = mapWorkOrderAdmin(workOrder).capabilities + if (pinned && (!capabilities.canPin || !mapWorkOrderAdmin(workOrder).hallAvailable)) { + throw createHttpError('只有待抢单订单可以置顶', { + statusCode: 409, + errorCode: 'work_order_pin_status_invalid', + }) + } + const now = nowIso() + const updated = await updateWorkOrder(workOrder.id, { + pinned_at: pinned ? now : null, + updated_at: now, + }) + await createWorkOrderEvent({ + workOrderId: workOrder.id, + actorType: 'admin', + actorId: actorName, + eventType: pinned ? 'pinned' : 'unpinned', + fromStatus: workOrder.status, + toStatus: workOrder.status, + now, + }) + publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), hallChanged: true }) + return { order: mapWorkOrderAdmin(updated || workOrder) } +} + +export async function markAdminWorkOrderProblem( + workOrderId: number | string, + payload: JsonObject = {}, + actorName = '', +) { + const workOrder = await getRequiredWorkOrder(workOrderId) + if (!mapWorkOrderAdmin(workOrder).capabilities.canMarkProblem) { + throw createHttpError('当前状态不能标记问题单', { + statusCode: 409, + errorCode: 'work_order_problem_status_invalid', + }) + } + const now = nowIso() + const note = String(payload.note || payload.problemNote || '').trim() + const updated = await updateWorkOrder(workOrder.id, { + status: WORK_ORDER_STATUS.PROBLEM, + problem_note: note, + updated_at: now, + }) + await createWorkOrderEvent({ + workOrderId: workOrder.id, + actorType: 'admin', + actorId: actorName, + eventType: 'marked_problem', + fromStatus: workOrder.status, + toStatus: WORK_ORDER_STATUS.PROBLEM, + payloadJson: JSON.stringify({ note }), + now, + }) + if (workOrder.status === WORK_ORDER_STATUS.PENDING_ACCEPTANCE) { + await resolveAdminNotificationEntity('work_order', Number(workOrder.id), 'marked_problem') + } + publishWorkOrderRealtimeChange({ + workOrderId: Number(workOrder.id), + workerIds: await resolveWorkOrderRealtimeWorkerIds(workOrder), + }) + return { order: mapWorkOrderAdmin(updated || workOrder) } +} + +export async function resolveAdminProblemWorkOrder( + workOrderId: number | string, + payload: JsonObject = {}, + actorName = '', +) { + const workOrder = await getRequiredWorkOrder(workOrderId) + if (workOrder.status !== WORK_ORDER_STATUS.PROBLEM) { + throw createHttpError('只有问题单可以执行处置', { + statusCode: 409, + errorCode: 'work_order_problem_resolution_status_invalid', + }) + } + + const action = normalizeProblemResolutionAction(payload.action) + const note = String(payload.note || '').trim() + if (!note) { + throw createHttpError('请填写处置备注', { + statusCode: 400, + errorCode: 'work_order_problem_resolution_note_required', + }) + } + + const updated = await resolveProblemWorkOrder({ + workOrderId: workOrder.id, + action, + note, + now: nowIso(), + actorName, + }) + if (!updated) { + throw createHttpError('问题单处置失败,订单状态可能已变化', { + statusCode: 409, + errorCode: 'work_order_problem_resolution_conflict', + }) + } + publishWorkOrderRealtimeChange({ + workOrderId: Number(workOrder.id), + workerIds: await resolveWorkOrderRealtimeWorkerIds(workOrder), + }) + return { order: mapWorkOrderAdmin(updated) } +} diff --git a/apps/backend/src/services/worker-platform/index.ts b/apps/backend/src/services/worker-platform/index.ts index 8c66cf8e..af8f0558 100644 --- a/apps/backend/src/services/worker-platform/index.ts +++ b/apps/backend/src/services/worker-platform/index.ts @@ -32,6 +32,7 @@ export * from './admin-work-order-sharing-service.js' 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 * from './worker-product-match-config-service.js' export * from './worker-announcement-config-service.js' export * from './worker-hall-config-service.js'