diff --git a/apps/backend/src/services/worker-platform/admin-mock-work-order-service.ts b/apps/backend/src/services/worker-platform/admin-mock-work-order-service.ts new file mode 100644 index 00000000..f93a6589 --- /dev/null +++ b/apps/backend/src/services/worker-platform/admin-mock-work-order-service.ts @@ -0,0 +1,184 @@ +import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js' +import { createWorkOrder, createWorkOrderEvent } from '../../repositories/worker-platform/index.js' +import type { JsonObject } from '../../types/json.js' +import { createHttpError } from '../../utils/http.js' +import { randomId } from '../../utils/random.js' +import { nowIso } from '../../utils/time.js' +import { + getKuaishouIndustrySourceConfig, + listKuaishouIndustryShopConfigs, + type KuaishouIndustryShopConfig, +} from '../platforms/kuaishou-industry/source-config-service.js' +import { + DEFAULT_DEPOSIT_THRESHOLD_AMOUNT, + mapWorkOrderAdmin, + normalizeAmountFen, + normalizeInteger, + normalizeRequirementFields, +} from './mappers.js' +import { ensureWorkerPlatformDefaults } from './worker-platform-defaults.js' +import { normalizeWorkOrderTimeoutPolicy } from './work-order-timeout-policy.js' + +export async function createAdminMockWorkOrder(payload: JsonObject = {}) { + const defaults = await ensureWorkerPlatformDefaults() + const now = nowIso() + const input = resolveAdminMockWorkOrderInput( + payload, + listKuaishouIndustryShopConfigs(getKuaishouIndustrySourceConfig()), + ) + const rewardAmount = input.rewardAmount + const depositThresholdAmount = normalizeAmountFen( + payload.depositThresholdAmount ?? payload.depositThresholdAmountYuan, + DEFAULT_DEPOSIT_THRESHOLD_AMOUNT, + ) + const requiredDepositAmount = + payload.requiredDepositAmount === undefined + ? rewardAmount > depositThresholdAmount + ? rewardAmount + : 0 + : normalizeAmountFen(payload.requiredDepositAmount, 0) + const paymentFen = input.paymentAmount + const materialComplete = payload.materialComplete === true || payload.materialComplete === 'true' + const fields = normalizeRequirementFields(payload.fields) + const { platformOrderId, productName, shop } = input + const material = { + ...(materialComplete + ? { + collect: { + complete: true, + submittedAt: now, + fields: fields.reduce>((result, field) => { + result[field.key] = String(field.mockValue || `${field.label}测试值`) + return result + }, {}), + }, + } + : {}), + source: { + orderAmountFen: paymentFen, + syncSource: 'mock', + mock: true, + provider: 'mock', + platform: 'mock', + sellerId: String(shop.sellerId || shop.shopId || '').trim(), + shopId: String(shop.shopId || shop.sellerId || '').trim(), + shopName: String( + shop.customShopName || shop.shopName || shop.shopId || shop.sellerId || '', + ).trim(), + }, + } + const workOrder = await createWorkOrder({ + workOrderNo: randomId('WO'), + platformOrderId, + productName, + categoryId: defaults.category?.id || null, + status: materialComplete ? WORK_ORDER_STATUS.UNASSIGNED : WORK_ORDER_STATUS.PENDING_MATERIAL, + rewardAmount, + requiredDepositAmount, + depositThresholdAmount, + timeoutMinutes: Math.max(0, normalizeInteger(payload.timeoutMinutes, 0)), + timeoutPolicy: normalizeWorkOrderTimeoutPolicy(payload.timeoutPolicy), + materialJson: JSON.stringify(material), + requirementJson: JSON.stringify({ fields }), + now, + }) + if (!workOrder) { + throw createHttpError('接单工单创建失败', { + statusCode: 500, + errorCode: 'work_order_create_failed', + }) + } + await createWorkOrderEvent({ + workOrderId: workOrder.id, + actorType: 'admin', + actorId: 'dev_mock', + eventType: 'mock_created', + fromStatus: '', + toStatus: workOrder.status, + payloadJson: JSON.stringify({ + platformOrderId, + productName, + rewardAmount, + sellerId: String(shop.sellerId || shop.shopId || '').trim(), + }), + now, + }) + return { order: mapWorkOrderAdmin(workOrder) } +} + +/** 本地 Mock 与真实订单保持相同的关键字段约束,避免生成无法追溯的测试工单。 */ +export function resolveAdminMockWorkOrderInput( + payload: JsonObject, + shops: KuaishouIndustryShopConfig[], +) { + const platformOrderId = String(payload.platformOrderId || '').trim() + if (!platformOrderId) { + throw createHttpError('请填写订单号', { + statusCode: 400, + errorCode: 'mock_work_order_no_required', + }) + } + + const productName = String(payload.productName || '').trim() + if (!productName) { + throw createHttpError('请填写商品名称', { + statusCode: 400, + errorCode: 'mock_work_order_product_name_required', + }) + } + + const shopId = String(payload.sellerId || payload.shopId || '').trim() + if (!shopId) { + throw createHttpError('请选择店铺', { + statusCode: 400, + errorCode: 'mock_work_order_shop_required', + }) + } + const shop = + shops.find( + (item) => + String(item.sellerId || item.shopId || '').trim() === shopId || + String(item.shopId || '').trim() === shopId, + ) || null + if (!shop) { + throw createHttpError('所选店铺不存在', { + statusCode: 400, + errorCode: 'mock_work_order_shop_invalid', + }) + } + if (shop.enabled === false) { + throw createHttpError('所选店铺已停用', { + statusCode: 409, + errorCode: 'mock_work_order_shop_disabled', + }) + } + + return { + platformOrderId, + productName, + shop, + rewardAmount: requireMockWorkOrderAmount(payload.rewardAmount, '接单金额', 'reward_amount'), + paymentAmount: requireMockWorkOrderAmount( + payload.paymentAmount, + '用户付款金额', + 'payment_amount', + ), + } +} + +function requireMockWorkOrderAmount(value: unknown, label: string, field: string): number { + if (value === undefined || value === null || String(value).trim() === '') { + throw createHttpError(`请填写${label}`, { + statusCode: 400, + errorCode: `mock_work_order_${field}_required`, + }) + } + const amount = normalizeAmountFen(value, 0) + if (amount <= 0) { + throw createHttpError(`${label}必须大于 0`, { + statusCode: 400, + errorCode: `mock_work_order_${field}_invalid`, + }) + } + return amount +} diff --git a/apps/backend/src/services/worker-platform/admin-service.ts b/apps/backend/src/services/worker-platform/admin-service.ts index bcad978b..1bb56c0d 100644 --- a/apps/backend/src/services/worker-platform/admin-service.ts +++ b/apps/backend/src/services/worker-platform/admin-service.ts @@ -51,7 +51,6 @@ import { } from './publish-voucher-service.js' import { - DEFAULT_DEPOSIT_THRESHOLD_AMOUNT, mapWorkOrderAdmin, mapWorkOrderShare, normalizeAmountFen, @@ -71,15 +70,9 @@ import { resolveWorkerPermissions, resolveWorkOrderOperationalStatus, } from './mappers.js' -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, - type KuaishouIndustryShopConfig, -} from '../platforms/kuaishou-industry/source-config-service.js' export { listAdminWorkerUsers, @@ -292,169 +285,10 @@ export { submitAdminWorkOrderMaterial, } from './admin-work-order-material-service.js' -export async function createAdminMockWorkOrder(payload: JsonObject = {}) { - const defaults = await ensureWorkerPlatformDefaults() - const now = nowIso() - const input = resolveAdminMockWorkOrderInput( - payload, - listKuaishouIndustryShopConfigs(getKuaishouIndustrySourceConfig()), - ) - const rewardAmount = input.rewardAmount - const depositThresholdAmount = normalizeAmountFen( - payload.depositThresholdAmount ?? payload.depositThresholdAmountYuan, - DEFAULT_DEPOSIT_THRESHOLD_AMOUNT, - ) - const requiredDepositAmount = - payload.requiredDepositAmount === undefined - ? rewardAmount > depositThresholdAmount - ? rewardAmount - : 0 - : normalizeAmountFen(payload.requiredDepositAmount, 0) - const paymentFen = input.paymentAmount - const materialComplete = payload.materialComplete === true || payload.materialComplete === 'true' - const fields = normalizeRequirementFields(payload.fields) - const { platformOrderId, productName, shop } = input - const material = { - ...(materialComplete - ? { - collect: { - complete: true, - submittedAt: now, - fields: fields.reduce>((result, field) => { - result[field.key] = String(field.mockValue || `${field.label}测试值`) - return result - }, {}), - }, - } - : {}), - source: { - orderAmountFen: paymentFen, - syncSource: 'mock', - mock: true, - provider: 'mock', - platform: 'mock', - sellerId: String(shop.sellerId || shop.shopId || '').trim(), - shopId: String(shop.shopId || shop.sellerId || '').trim(), - shopName: String( - shop.customShopName || shop.shopName || shop.shopId || shop.sellerId || '', - ).trim(), - }, - } - const workOrder = await createWorkOrder({ - workOrderNo: randomId('WO'), - platformOrderId, - productName, - categoryId: defaults.category?.id || null, - status: materialComplete ? WORK_ORDER_STATUS.UNASSIGNED : WORK_ORDER_STATUS.PENDING_MATERIAL, - rewardAmount, - requiredDepositAmount, - depositThresholdAmount, - timeoutMinutes: Math.max(0, normalizeInteger(payload.timeoutMinutes, 0)), - timeoutPolicy: normalizeWorkOrderTimeoutPolicy(payload.timeoutPolicy), - materialJson: JSON.stringify(material), - requirementJson: JSON.stringify({ fields }), - now, - }) - if (!workOrder) { - throw createHttpError('接单工单创建失败', { - statusCode: 500, - errorCode: 'work_order_create_failed', - }) - } - await createWorkOrderEvent({ - workOrderId: workOrder.id, - actorType: 'admin', - actorId: 'dev_mock', - eventType: 'mock_created', - fromStatus: '', - toStatus: workOrder.status, - payloadJson: JSON.stringify({ - platformOrderId, - productName, - rewardAmount, - sellerId: String(shop.sellerId || shop.shopId || '').trim(), - }), - now, - }) - return { order: mapWorkOrderAdmin(workOrder) } -} - -/** 本地 Mock 与真实订单保持相同的关键字段约束,避免生成无法追溯的测试工单。 */ -export function resolveAdminMockWorkOrderInput( - payload: JsonObject, - shops: KuaishouIndustryShopConfig[], -) { - const platformOrderId = String(payload.platformOrderId || '').trim() - if (!platformOrderId) { - throw createHttpError('请填写订单号', { - statusCode: 400, - errorCode: 'mock_work_order_no_required', - }) - } - - const productName = String(payload.productName || '').trim() - if (!productName) { - throw createHttpError('请填写商品名称', { - statusCode: 400, - errorCode: 'mock_work_order_product_name_required', - }) - } - - const shopId = String(payload.sellerId || payload.shopId || '').trim() - if (!shopId) { - throw createHttpError('请选择店铺', { - statusCode: 400, - errorCode: 'mock_work_order_shop_required', - }) - } - const shop = - shops.find( - (item) => - String(item.sellerId || item.shopId || '').trim() === shopId || - String(item.shopId || '').trim() === shopId, - ) || null - if (!shop) { - throw createHttpError('所选店铺不存在', { - statusCode: 400, - errorCode: 'mock_work_order_shop_invalid', - }) - } - if (shop.enabled === false) { - throw createHttpError('所选店铺已停用', { - statusCode: 409, - errorCode: 'mock_work_order_shop_disabled', - }) - } - - return { - platformOrderId, - productName, - shop, - rewardAmount: requireMockWorkOrderAmount(payload.rewardAmount, '接单金额', 'reward_amount'), - paymentAmount: requireMockWorkOrderAmount( - payload.paymentAmount, - '用户付款金额', - 'payment_amount', - ), - } -} - -function requireMockWorkOrderAmount(value: unknown, label: string, field: string): number { - if (value === undefined || value === null || String(value).trim() === '') { - throw createHttpError(`请填写${label}`, { - statusCode: 400, - errorCode: `mock_work_order_${field}_required`, - }) - } - const amount = normalizeAmountFen(value, 0) - if (amount <= 0) { - throw createHttpError(`${label}必须大于 0`, { - statusCode: 400, - errorCode: `mock_work_order_${field}_invalid`, - }) - } - return amount -} +export { + createAdminMockWorkOrder, + resolveAdminMockWorkOrderInput, +} from './admin-mock-work-order-service.js' export async function updateAdminWorkOrder( workOrderId: number | string, diff --git a/apps/backend/src/services/worker-platform/index.ts b/apps/backend/src/services/worker-platform/index.ts index 8d3885a3..8c66cf8e 100644 --- a/apps/backend/src/services/worker-platform/index.ts +++ b/apps/backend/src/services/worker-platform/index.ts @@ -31,6 +31,7 @@ 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 './admin-mock-work-order-service.js' export * from './worker-product-match-config-service.js' export * from './worker-announcement-config-service.js' export * from './worker-hall-config-service.js'