diff --git a/apps/backend/src/services/worker-platform/admin-service.ts b/apps/backend/src/services/worker-platform/admin-service.ts index b3f10430..bcad978b 100644 --- a/apps/backend/src/services/worker-platform/admin-service.ts +++ b/apps/backend/src/services/worker-platform/admin-service.ts @@ -61,8 +61,6 @@ import { normalizeProblemResolutionAction, normalizeRequirementFields, normalizeRequirementFieldsFromPayload, - normalizeSubmittedFields, - normalizeUploadedFiles, resolveFreezeDepositAmount, resolveMatchingProductRuleDecision, resolveKuaishouWorkProductMatchContext, @@ -76,6 +74,7 @@ import { import { ensureWorkerPlatformDefaults } from './worker-platform-defaults.js' import { normalizeWorkOrderTimeoutPolicy } from './work-order-timeout-policy.js' import { getRequiredWorkOrder, getRequiredWorker } from './worker-session-context-service.js' +import { resolveWorkOrderRealtimeWorkerIds } from './admin-work-order-realtime-service.js' import { getKuaishouIndustrySourceConfig, listKuaishouIndustryShopConfigs, @@ -288,104 +287,10 @@ export { updateAdminWorkOrderSharing, } from './admin-work-order-sharing-service.js' -export async function submitAdminWorkOrderMaterial( - workOrderId: number | string, - payload: JsonObject = {}, - actorName = '', -) { - const workOrder = await getRequiredWorkOrder(workOrderId) - const materialCapabilities = mapWorkOrderAdmin(workOrder).capabilities - if (!materialCapabilities.canUpdateMaterial) { - throw createHttpError('当前状态不能更新订单资料', { - statusCode: 409, - errorCode: 'work_order_material_status_invalid', - }) - } - - const fields = resolveRequirementFields(workOrder) - const submittedFields = normalizeSubmittedFields(payload.fields || payload.material || payload) - const complete = fields.every( - (field) => !field.required || String(submittedFields[field.key] || '').trim(), - ) - const now = nowIso() - const screenshots = normalizeUploadedFiles(payload.screenshots || payload.files) - const hasActiveShares = Number(workOrder.active_share_quantity || 0) > 0 - const rewardAmount = resolveAdminMaterialRewardAmount({ - currentRewardAmount: Number(workOrder.reward_amount || 0), - requestedRewardAmount: payload.rewardAmount, - sharingEnabled: workOrder.sharing_enabled === true, - activeShareQuantity: Number(workOrder.active_share_quantity || 0), - }) - const material = { - ...safeParseJson(workOrder.material_json), - collect: { - fields: submittedFields, - screenshots, - submittedAt: now, - complete, - source: 'admin', - }, - } - const nextStatus = - hasActiveShares || - workOrder.status === WORK_ORDER_STATUS.OPEN || - workOrder.status === WORK_ORDER_STATUS.IN_PROGRESS - ? workOrder.status - : complete - ? WORK_ORDER_STATUS.UNASSIGNED - : WORK_ORDER_STATUS.PENDING_MATERIAL - const updated = await updateWorkOrder(workOrder.id, { - status: nextStatus, - material_json: material, - reward_amount: rewardAmount, - updated_at: now, - }) - await createWorkOrderEvent({ - workOrderId: workOrder.id, - actorType: 'admin', - actorId: actorName, - eventType: - hasActiveShares || - workOrder.status === WORK_ORDER_STATUS.OPEN || - workOrder.status === WORK_ORDER_STATUS.IN_PROGRESS - ? 'material_updated' - : 'material_completed', - fromStatus: workOrder.status, - toStatus: nextStatus, - payloadJson: JSON.stringify({ - fields: submittedFields, - complete, - previousRewardAmount: Number(workOrder.reward_amount || 0), - rewardAmount, - }), - now, - }) - if (nextStatus !== WORK_ORDER_STATUS.PENDING_MATERIAL) { - await resolveAdminNotificationEntity('work_order', Number(workOrder.id), 'material_completed') - } - publishWorkOrderRealtimeChange({ - workOrderId: Number(workOrder.id), - workerIds: await resolveWorkOrderRealtimeWorkerIds(workOrder), - }) - return { order: mapWorkOrderAdmin(updated || workOrder), complete } -} - -/** 更新资料时仅普通接单可改价;已有参与者的拼单必须保留份额价格快照。 */ -export function resolveAdminMaterialRewardAmount(input: { - currentRewardAmount: number - requestedRewardAmount: unknown - sharingEnabled: boolean - activeShareQuantity: number -}): number { - if (input.requestedRewardAmount === undefined) return input.currentRewardAmount - if (input.sharingEnabled && input.activeShareQuantity > 0) { - throw createHttpError('拼单订单的份额奖励已锁定,不能在更新资料时修改价格', { - statusCode: 409, - errorCode: 'work_order_material_reward_sharing_invalid', - }) - } - return normalizeAmountFen(input.requestedRewardAmount, input.currentRewardAmount) -} +export { + resolveAdminMaterialRewardAmount, + submitAdminWorkOrderMaterial, +} from './admin-work-order-material-service.js' export async function createAdminMockWorkOrder(payload: JsonObject = {}) { const defaults = await ensureWorkerPlatformDefaults() @@ -1208,13 +1113,6 @@ export async function cancelAdminWorkOrder( return { order: mapWorkOrderAdmin(updated), action } } -async function resolveWorkOrderRealtimeWorkerIds(workOrder: WorkOrderRow) { - const shares = await listWorkOrderShares(workOrder.id) - return [Number(workOrder.assigned_worker_id), ...shares.map((share) => Number(share.worker_id))] - .filter((workerId) => Number.isInteger(workerId) && workerId > 0) - .filter((workerId, index, values) => values.indexOf(workerId) === index) -} - export async function getAdminWorkerPlatformSummary() { const hallConfig = getWorkerHallConfig() const [pendingWorkers, pendingMaterial, openOrders, inProgressOrders] = await Promise.all([ diff --git a/apps/backend/src/services/worker-platform/admin-work-order-material-service.ts b/apps/backend/src/services/worker-platform/admin-work-order-material-service.ts new file mode 100644 index 00000000..e21565aa --- /dev/null +++ b/apps/backend/src/services/worker-platform/admin-work-order-material-service.ts @@ -0,0 +1,116 @@ +import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js' +import { createWorkOrderEvent, 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 { safeParseJson } from '../admin/admin-query-utils.js' +import { resolveAdminNotificationEntity } from '../admin/admin-notification-service.js' +import { publishWorkOrderRealtimeChange } from '../realtime/realtime-event-service.js' +import { + mapWorkOrderAdmin, + normalizeAmountFen, + normalizeSubmittedFields, + normalizeUploadedFiles, + resolveRequirementFields, +} from './mappers.js' +import { resolveWorkOrderRealtimeWorkerIds } from './admin-work-order-realtime-service.js' +import { getRequiredWorkOrder } from './worker-session-context-service.js' + +export async function submitAdminWorkOrderMaterial( + workOrderId: number | string, + payload: JsonObject = {}, + actorName = '', +) { + const workOrder = await getRequiredWorkOrder(workOrderId) + const materialCapabilities = mapWorkOrderAdmin(workOrder).capabilities + if (!materialCapabilities.canUpdateMaterial) { + throw createHttpError('当前状态不能更新订单资料', { + statusCode: 409, + errorCode: 'work_order_material_status_invalid', + }) + } + + const fields = resolveRequirementFields(workOrder) + const submittedFields = normalizeSubmittedFields(payload.fields || payload.material || payload) + const complete = fields.every( + (field) => !field.required || String(submittedFields[field.key] || '').trim(), + ) + const now = nowIso() + const screenshots = normalizeUploadedFiles(payload.screenshots || payload.files) + const hasActiveShares = Number(workOrder.active_share_quantity || 0) > 0 + const rewardAmount = resolveAdminMaterialRewardAmount({ + currentRewardAmount: Number(workOrder.reward_amount || 0), + requestedRewardAmount: payload.rewardAmount, + sharingEnabled: workOrder.sharing_enabled === true, + activeShareQuantity: Number(workOrder.active_share_quantity || 0), + }) + const material = { + ...safeParseJson(workOrder.material_json), + collect: { + fields: submittedFields, + screenshots, + submittedAt: now, + complete, + source: 'admin', + }, + } + const nextStatus = + hasActiveShares || + workOrder.status === WORK_ORDER_STATUS.OPEN || + workOrder.status === WORK_ORDER_STATUS.IN_PROGRESS + ? workOrder.status + : complete + ? WORK_ORDER_STATUS.UNASSIGNED + : WORK_ORDER_STATUS.PENDING_MATERIAL + const updated = await updateWorkOrder(workOrder.id, { + status: nextStatus, + material_json: material, + reward_amount: rewardAmount, + updated_at: now, + }) + await createWorkOrderEvent({ + workOrderId: workOrder.id, + actorType: 'admin', + actorId: actorName, + eventType: + hasActiveShares || + workOrder.status === WORK_ORDER_STATUS.OPEN || + workOrder.status === WORK_ORDER_STATUS.IN_PROGRESS + ? 'material_updated' + : 'material_completed', + fromStatus: workOrder.status, + toStatus: nextStatus, + payloadJson: JSON.stringify({ + fields: submittedFields, + complete, + previousRewardAmount: Number(workOrder.reward_amount || 0), + rewardAmount, + }), + now, + }) + if (nextStatus !== WORK_ORDER_STATUS.PENDING_MATERIAL) { + await resolveAdminNotificationEntity('work_order', Number(workOrder.id), 'material_completed') + } + publishWorkOrderRealtimeChange({ + workOrderId: Number(workOrder.id), + workerIds: await resolveWorkOrderRealtimeWorkerIds(workOrder), + }) + return { order: mapWorkOrderAdmin(updated || workOrder), complete } +} + +/** 更新资料时仅普通接单可改价;已有参与者的拼单必须保留份额价格快照。 */ +export function resolveAdminMaterialRewardAmount(input: { + currentRewardAmount: number + requestedRewardAmount: unknown + sharingEnabled: boolean + activeShareQuantity: number +}): number { + if (input.requestedRewardAmount === undefined) return input.currentRewardAmount + if (input.sharingEnabled && input.activeShareQuantity > 0) { + throw createHttpError('拼单订单的份额奖励已锁定,不能在更新资料时修改价格', { + statusCode: 409, + errorCode: 'work_order_material_reward_sharing_invalid', + }) + } + return normalizeAmountFen(input.requestedRewardAmount, input.currentRewardAmount) +} diff --git a/apps/backend/src/services/worker-platform/admin-work-order-realtime-service.ts b/apps/backend/src/services/worker-platform/admin-work-order-realtime-service.ts new file mode 100644 index 00000000..e97dadfd --- /dev/null +++ b/apps/backend/src/services/worker-platform/admin-work-order-realtime-service.ts @@ -0,0 +1,8 @@ +import { listWorkOrderShares, type WorkOrderRow } from '../../repositories/worker-platform/index.js' + +export async function resolveWorkOrderRealtimeWorkerIds(workOrder: WorkOrderRow) { + const shares = await listWorkOrderShares(workOrder.id) + return [Number(workOrder.assigned_worker_id), ...shares.map((share) => Number(share.worker_id))] + .filter((workerId) => Number.isInteger(workerId) && workerId > 0) + .filter((workerId, index, values) => values.indexOf(workerId) === index) +} diff --git a/apps/backend/src/services/worker-platform/index.ts b/apps/backend/src/services/worker-platform/index.ts index c6302346..8d3885a3 100644 --- a/apps/backend/src/services/worker-platform/index.ts +++ b/apps/backend/src/services/worker-platform/index.ts @@ -29,6 +29,8 @@ export * from './admin-work-order-query-service.js' export * from './admin-worker-feedback-service.js' export * from './admin-worker-cancel-service.js' 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 './worker-product-match-config-service.js' export * from './worker-announcement-config-service.js' export * from './worker-hall-config-service.js'