拆分后台工单管理服务

This commit is contained in:
yml2213
2026-08-21 17:22:13 +08:00
parent eeedb4a4da
commit 499293c7cf
3 changed files with 413 additions and 382 deletions
@@ -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)
@@ -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) }
}
@@ -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'