优化后端-接单平台部分逻辑

This commit is contained in:
yml2213
2026-07-28 14:06:31 +08:00
parent b505b6b85e
commit 6e635b2499
9 changed files with 1389 additions and 20 deletions
@@ -2,6 +2,7 @@ export const APP_CONFIG_KEYS = {
notification: 'notification',
scheduledJobs: 'scheduled_jobs',
fulfillmentRouting: 'fulfillment_routing',
workerFinance: 'worker_finance',
cloudtentaclesSources: 'cloudtentacles_sources',
cloudtentaclesSession: 'cloudtentacles_session',
cloudtentaclesOverrideRules: 'cloudtentacles_override_rules',
@@ -74,6 +74,9 @@ export type WorkerFinanceRequestRow = {
created_at: string
updated_at: string
reviewed_at: string | null
worker_username?: string
worker_display_name?: string
worker_phone?: string
}
export type WorkCategoryRow = {
@@ -179,6 +182,7 @@ type FinanceRequestListInput = {
workerId?: number
status?: string
requestType?: string
keyword?: string
}
type CreateWorkerInput = {
@@ -242,6 +246,16 @@ const WORK_PRODUCT_RULE_SELECT = `
LEFT JOIN work_categories wc ON wc.id = wpr.category_id
`
const WORKER_FINANCE_REQUEST_SELECT = `
SELECT
wfr.*,
wu.username AS worker_username,
wu.display_name AS worker_display_name,
wu.phone AS worker_phone
FROM worker_finance_requests wfr
LEFT JOIN worker_users wu ON wu.id = wfr.worker_id
`
export async function getWorkerLevelByKey(levelKey: string): Promise<WorkerLevelRow | null> {
const result = await query<WorkerLevelRow>(
'SELECT * FROM worker_levels WHERE level_key = $1 LIMIT 1',
@@ -580,32 +594,43 @@ export async function createWorkerFinanceRequest(input: {
return result.rows[0] || null
}
export async function getWorkerFinanceRequestById(
requestId: number | string,
): Promise<WorkerFinanceRequestRow | null> {
const result = await query<WorkerFinanceRequestRow>(
`${WORKER_FINANCE_REQUEST_SELECT} WHERE wfr.id = $1 LIMIT 1`,
[Number(requestId)],
)
return result.rows[0] || null
}
export async function listWorkerFinanceRequests({
page = 1,
pageSize = 20,
workerId = 0,
status = '',
requestType = '',
keyword = '',
}: FinanceRequestListInput = {}): Promise<{ items: WorkerFinanceRequestRow[]; total: number }> {
const { whereClause, params } = buildWorkerFinanceRequestWhere({
workerId,
status,
requestType,
keyword,
})
const totalResult = await query<{ total: number }>(
`SELECT COUNT(*)::int AS total FROM worker_finance_requests wfr ${whereClause}`,
`SELECT COUNT(*)::int AS total FROM worker_finance_requests wfr
LEFT JOIN worker_users wu ON wu.id = wfr.worker_id
${whereClause}`,
params,
)
const offset = (page - 1) * pageSize
params.push(pageSize, offset)
const itemsResult = await query<WorkerFinanceRequestRow>(
`
SELECT *
FROM worker_finance_requests wfr
${whereClause}
ORDER BY wfr.created_at DESC, wfr.id DESC
LIMIT $${params.length - 1} OFFSET $${params.length}
`,
`${WORKER_FINANCE_REQUEST_SELECT}
${whereClause}
ORDER BY wfr.created_at DESC, wfr.id DESC
LIMIT $${params.length - 1} OFFSET $${params.length}`,
params,
)
return {
@@ -614,6 +639,146 @@ export async function listWorkerFinanceRequests({
}
}
export async function reviewWorkerFinanceRequest(input: {
requestId: number
status: string
reviewedNote: string
now: string
}): Promise<{
request: WorkerFinanceRequestRow | null
failureReason: 'request_not_pending' | 'withdraw_insufficient' | null
}> {
return withTransaction(async (client) => {
const currentResult = await client.query<WorkerFinanceRequestRow>(
`
SELECT *
FROM worker_finance_requests
WHERE id = $1
LIMIT 1
FOR UPDATE
`,
[input.requestId],
)
const current = currentResult.rows[0] || null
if (!current) {
return {
request: null,
failureReason: null,
}
}
if (current.status !== 'pending') {
return {
request: await getWorkerFinanceRequestByIdWithClient(client, input.requestId),
failureReason: 'request_not_pending',
}
}
if (input.status === 'approved') {
await ensureWorkerWalletWithClient(client, Number(current.worker_id), input.now)
const wallet = await getWorkerWalletWithClient(client, Number(current.worker_id))
const availableAmount = Number(wallet?.available_amount || 0)
const frozenAmount = Number(wallet?.frozen_deposit_amount || 0)
const amount = Number(current.amount || 0)
const note =
input.reviewedNote ||
(current.request_type === 'withdraw' ? '提现申请审核通过' : '充值申请审核通过')
if (current.request_type === 'withdraw') {
if (availableAmount < amount) {
return {
request: await getWorkerFinanceRequestByIdWithClient(client, input.requestId),
failureReason: 'withdraw_insufficient',
}
}
const nextAvailable = availableAmount - amount
await client.query(
`
UPDATE worker_wallets
SET available_amount = $1, updated_at = $2
WHERE worker_id = $3
`,
[nextAvailable, input.now, Number(current.worker_id)],
)
await client.query(
`
INSERT INTO worker_wallet_ledgers (
worker_id, ledger_type, amount, balance_after, frozen_after,
audit_status, note, payload_json, created_at
) VALUES ($1, 'withdraw_paid', $2, $3, $4, 'approved', $5, $6::jsonb, $7)
`,
[
Number(current.worker_id),
-amount,
nextAvailable,
frozenAmount,
note,
JSON.stringify({
requestId: Number(current.id),
requestType: current.request_type,
source: 'admin_finance_review',
}),
input.now,
],
)
} else {
const nextAvailable = availableAmount + amount
await client.query(
`
UPDATE worker_wallets
SET
available_amount = $1,
total_credited_amount = total_credited_amount + $2,
updated_at = $3
WHERE worker_id = $4
`,
[nextAvailable, amount, input.now, Number(current.worker_id)],
)
await client.query(
`
INSERT INTO worker_wallet_ledgers (
worker_id, ledger_type, amount, balance_after, frozen_after,
audit_status, note, payload_json, created_at
) VALUES ($1, 'manual_credit', $2, $3, $4, 'approved', $5, $6::jsonb, $7)
`,
[
Number(current.worker_id),
amount,
nextAvailable,
frozenAmount,
note,
JSON.stringify({
requestId: Number(current.id),
requestType: current.request_type,
source: 'admin_finance_review',
}),
input.now,
],
)
}
}
await client.query(
`
UPDATE worker_finance_requests
SET
status = $1,
reviewed_note = $2,
reviewed_at = $3,
updated_at = $3
WHERE id = $4
`,
[input.status, input.reviewedNote, input.now, input.requestId],
)
return {
request: await getWorkerFinanceRequestByIdWithClient(client, input.requestId),
failureReason: null,
}
})
}
export async function getWorkerFinanceRequestSummary(workerId: number | string) {
const result = await query<{
pending_withdraw_amount: number
@@ -1421,6 +1586,17 @@ async function getWorkerUserByIdWithClient(
return result.rows[0] || null
}
async function getWorkerFinanceRequestByIdWithClient(
client: PoolClient,
requestId: number | string,
): Promise<WorkerFinanceRequestRow | null> {
const result = await client.query<WorkerFinanceRequestRow>(
`${WORKER_FINANCE_REQUEST_SELECT} WHERE wfr.id = $1 LIMIT 1`,
[Number(requestId)],
)
return result.rows[0] || null
}
async function getWorkOrderByIdWithClient(
client: PoolClient,
workOrderId: number | string,
@@ -1500,6 +1676,7 @@ function buildWorkerFinanceRequestWhere({
workerId = 0,
status = '',
requestType = '',
keyword = '',
}: FinanceRequestListInput) {
const filters: string[] = []
const params: unknown[] = []
@@ -1515,6 +1692,16 @@ function buildWorkerFinanceRequestWhere({
params.push(requestType)
filters.push(`wfr.request_type = $${params.length}`)
}
if (keyword) {
params.push(`%${keyword}%`)
filters.push(
`(wu.username ILIKE $${params.length}
OR wu.display_name ILIKE $${params.length}
OR wu.phone ILIKE $${params.length}
OR wfr.account_name ILIKE $${params.length}
OR wfr.account_no ILIKE $${params.length})`,
)
}
return {
whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '',
params,
@@ -4,7 +4,9 @@ import {
acceptAdminWorkOrder,
creditAdminWorkerWallet,
createAdminMockWorkOrder,
getAdminWorkerFinanceConfig,
getAdminWorkerPlatformSummary,
listAdminWorkerFinanceRequests,
listAdminWorkCategories,
listAdminWorkProductRules,
listAdminWorkerLevels,
@@ -12,13 +14,16 @@ import {
listAdminWorkOrders,
markAdminWorkOrderProblem,
publishAdminWorkOrder,
reviewAdminWorkerFinanceRequest,
resolveAdminProblemWorkOrder,
reviewAdminWorkerUser,
saveAdminWorkerFinanceConfig,
saveAdminWorkCategory,
saveAdminWorkProductRule,
saveAdminWorkerLevel,
submitAdminWorkOrderMaterial,
syncAdminWorkerOrdersFromSource,
unpublishAdminWorkOrder,
} from '../../services/worker-platform/worker-platform-service.js'
import { createJsonHandler, requireAdminRoles } from './session.js'
@@ -161,6 +166,62 @@ router.post(
),
)
router.get(
'/worker-platform/finance-config',
requireAdminRoles(['admin', 'operator', 'support']),
createJsonHandler(() => getAdminWorkerFinanceConfig(), {
successMessage: 'ok',
errorMessage: '读取资金配置失败',
scope: '[admin/worker-platform/finance-config]',
}),
)
router.post(
'/worker-platform/finance-config',
requireAdminRoles(['admin', 'operator']),
createJsonHandler((req) => saveAdminWorkerFinanceConfig(req.body || {}), {
successMessage: '资金配置已保存',
errorMessage: '保存资金配置失败',
scope: '[admin/worker-platform/finance-config]',
audit: (_req, data) => ({
action: 'worker_finance_config_saved',
targetType: 'worker_finance_config',
targetId: 'worker_finance',
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
}),
}),
)
router.get(
'/worker-platform/finance-requests',
requireAdminRoles(['admin', 'operator', 'support']),
createJsonHandler((req) => listAdminWorkerFinanceRequests(req.query), {
successMessage: 'ok',
errorMessage: '读取资金申请失败',
scope: '[admin/worker-platform/finance-requests]',
}),
)
router.post(
'/worker-platform/finance-requests/:requestId/review',
requireAdminRoles(['admin', 'operator']),
createJsonHandler(
(req) =>
reviewAdminWorkerFinanceRequest(String(req.params.requestId || ''), req.body || {}),
{
successMessage: '资金申请已处理',
errorMessage: '处理资金申请失败',
scope: '[admin/worker-platform/finance-requests/:requestId/review]',
audit: (req, data) => ({
action: 'worker_finance_request_reviewed',
targetType: 'worker_finance_request',
targetId: String(req.params.requestId || ''),
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
}),
},
),
)
router.get(
'/worker-platform/orders',
requireAdminRoles(['admin', 'operator', 'support']),
@@ -238,6 +299,22 @@ router.post(
}),
)
router.post(
'/worker-platform/orders/:workOrderId/unpublish',
requireAdminRoles(['admin', 'operator']),
createJsonHandler((req) => unpublishAdminWorkOrder(String(req.params.workOrderId || '')), {
successMessage: '订单已撤回到未分配',
errorMessage: '下架接单订单失败',
scope: '[admin/worker-platform/orders/:workOrderId/unpublish]',
audit: (req, data) => ({
action: 'work_order_unpublished',
targetType: 'work_order',
targetId: String(req.params.workOrderId || ''),
data: data && typeof data === 'object' ? (data as Record<string, unknown>) : {},
}),
}),
)
router.post(
'/worker-platform/orders/:workOrderId/problem',
requireAdminRoles(['admin', 'operator', 'support']),
@@ -0,0 +1,123 @@
import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js'
import type { JsonObject } from '../../types/json.js'
import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-store.js'
import {
normalizeStoredFileUrl,
refreshUploadedFileUrls,
type UploadedFileDto,
} from '../file-storage/file-storage-service.js'
export type WorkerFinanceConfig = {
recharge: {
enabled: boolean
channelName: string
accountName: string
accountNo: string
qrCodeImage: UploadedFileDto | null
instructions: string
}
withdraw: {
enabled: boolean
instructions: string
}
}
export function getWorkerFinanceConfig(): WorkerFinanceConfig {
const config = readAppConfigEntry({
configKey: APP_CONFIG_KEYS.workerFinance,
fallback: createDefaultWorkerFinanceConfig,
normalize: normalizeWorkerFinanceConfig,
})
return refreshWorkerFinanceConfig(config)
}
export async function saveWorkerFinanceConfig(rawValue: unknown): Promise<WorkerFinanceConfig> {
const saved = await saveAppConfigEntry({
configKey: APP_CONFIG_KEYS.workerFinance,
value: rawValue,
normalize: normalizeWorkerFinanceConfig,
})
return refreshWorkerFinanceConfig(saved)
}
export function normalizeWorkerFinanceConfig(rawValue: unknown): WorkerFinanceConfig {
const source = isPlainObject(rawValue) ? rawValue : {}
const recharge = isPlainObject(source.recharge) ? source.recharge : {}
const withdraw = isPlainObject(source.withdraw) ? source.withdraw : {}
return {
recharge: {
enabled: typeof recharge.enabled === 'boolean' ? recharge.enabled : true,
channelName: String(recharge.channelName || recharge.channel_name || '').trim(),
accountName: String(recharge.accountName || recharge.account_name || '').trim(),
accountNo: String(recharge.accountNo || recharge.account_no || '').trim(),
qrCodeImage: normalizeUploadedFileForStorage(
recharge.qrCodeImage || recharge.qr_code_image,
),
instructions: String(recharge.instructions || '').trim(),
},
withdraw: {
enabled: typeof withdraw.enabled === 'boolean' ? withdraw.enabled : true,
instructions: String(withdraw.instructions || '').trim(),
},
}
}
export function createDefaultWorkerFinanceConfig(): WorkerFinanceConfig {
return {
recharge: {
enabled: true,
channelName: '',
accountName: '',
accountNo: '',
qrCodeImage: null,
instructions: '',
},
withdraw: {
enabled: true,
instructions: '',
},
}
}
function refreshWorkerFinanceConfig(config: WorkerFinanceConfig): WorkerFinanceConfig {
return {
recharge: {
...config.recharge,
qrCodeImage: config.recharge.qrCodeImage
? (refreshUploadedFileUrls(config.recharge.qrCodeImage) as UploadedFileDto)
: null,
},
withdraw: {
...config.withdraw,
},
}
}
function normalizeUploadedFileForStorage(value: unknown): UploadedFileDto | null {
if (!isPlainObject(value)) {
return null
}
const objectKey = String(value.objectKey || value.object_key || '').trim()
if (!objectKey) {
return null
}
return {
objectKey,
url: normalizeStoredFileUrl(value.url, objectKey),
thumbnailUrl: normalizeStoredFileUrl(
value.thumbnailUrl || value.thumbnail_url,
objectKey,
),
mediumUrl: normalizeStoredFileUrl(value.mediumUrl || value.medium_url, objectKey),
filename: String(value.filename || '').trim() || '收款二维码',
contentType: String(value.contentType || value.content_type || '').trim(),
size: Math.max(0, Number(value.size || 0) || 0),
}
}
function isPlainObject(value: unknown): value is JsonObject {
return Object.prototype.toString.call(value) === '[object Object]'
}
@@ -13,7 +13,9 @@ import {
createWorkOrderEvent,
createWorkerFinanceRequest,
createWorkerUser,
getWorkerFinanceRequestById,
getWorkerFinanceRequestSummary,
getWorkerLevelById,
incrementWorkerSessionVersion,
getWorkOrderById,
getWorkOrderByOrderItemId,
@@ -29,6 +31,7 @@ import {
listWorkerLevels,
listWorkerUsers,
resolveProblemWorkOrder,
reviewWorkerFinanceRequest,
updateWorkOrder,
updateWorkerPassword,
updateWorkerUser,
@@ -56,6 +59,10 @@ import {
refreshStoredFileUrl,
refreshUploadedFileUrls,
} from '../file-storage/file-storage-service.js'
import {
getWorkerFinanceConfig,
saveWorkerFinanceConfig,
} from './worker-finance-config-service.js'
export type WorkerSession = {
sessionId: string
@@ -286,9 +293,10 @@ export function requireActiveWorkerSession(session: WorkerSession | null | undef
export async function getWorkerProfile(session: WorkerSession) {
const worker = await getRequiredWorker(session.workerId)
const [financeSummary, acceptedOrderCount] = await Promise.all([
const [financeSummary, acceptedOrderCount, financeConfig] = await Promise.all([
getWorkerFinanceRequestSummary(session.workerId),
countWorkerAcceptedOrders(session.workerId),
Promise.resolve(getWorkerFinanceConfig()),
])
return {
worker: mapWorkerUser(worker),
@@ -299,6 +307,7 @@ export async function getWorkerProfile(session: WorkerSession) {
approvedWithdrawAmount: financeSummary.approvedWithdrawAmount,
pendingRechargeAmount: financeSummary.pendingRechargeAmount,
},
financeConfig,
}
}
@@ -348,6 +357,13 @@ export async function createWorkerRechargeRequest(
) {
requireActiveWorkerSession(session)
const worker = await getRequiredWorker(session.workerId)
const financeConfig = getWorkerFinanceConfig()
if (financeConfig.recharge.enabled === false) {
throw createHttpError('当前充值入口暂未开放,请联系管理员', {
statusCode: 409,
errorCode: 'worker_recharge_disabled',
})
}
const amount = normalizeAmountFen(payload.amount ?? payload.amountYuan, 0)
if (amount <= 0) {
throw createHttpError('充值金额必须大于 0', {
@@ -369,6 +385,12 @@ export async function createWorkerRechargeRequest(
source: 'worker_profile_recharge',
username: worker.username,
phone: worker.phone || '',
financeConfig: {
channelName: financeConfig.recharge.channelName,
accountName: financeConfig.recharge.accountName,
accountNo: financeConfig.recharge.accountNo,
qrCodeImage: financeConfig.recharge.qrCodeImage,
},
}),
now: nowIso(),
})
@@ -389,6 +411,13 @@ export async function createWorkerWithdrawRequest(
) {
requireActiveWorkerSession(session)
const worker = await getRequiredWorker(session.workerId)
const financeConfig = getWorkerFinanceConfig()
if (financeConfig.withdraw.enabled === false) {
throw createHttpError('当前提现入口暂未开放,请联系管理员', {
statusCode: 409,
errorCode: 'worker_withdraw_disabled',
})
}
const amount = normalizeAmountFen(payload.amount ?? payload.amountYuan, 0)
if (amount <= 0) {
throw createHttpError('提现金额必须大于 0', {
@@ -878,11 +907,21 @@ export async function listAdminWorkerUsers(query: JsonObject = {}) {
export async function reviewAdminWorkerUser(workerId: number | string, payload: JsonObject = {}) {
const worker = await getRequiredWorker(workerId)
const status = normalizeReviewStatus(payload.status)
const levelId =
normalizeOptionalId(payload.levelId) ||
worker.level_id ||
(await ensureWorkerPlatformDefaults()).level?.id ||
null
const requestedLevelId = normalizeOptionalId(payload.levelId)
let levelId = worker.level_id || null
if (requestedLevelId) {
const level = await getWorkerLevelById(requestedLevelId)
if (!level) {
throw createHttpError('接单等级不存在', {
statusCode: 404,
errorCode: 'worker_level_not_found',
})
}
levelId = Number(level.id)
}
if (!levelId) {
levelId = (await ensureWorkerPlatformDefaults()).level?.id || null
}
const now = nowIso()
const updated = await updateWorkerUser(worker.id, {
status,
@@ -914,6 +953,81 @@ export async function creditAdminWorkerWallet(workerId: number | string, payload
return { wallet: mapWallet(wallet) }
}
export async function getAdminWorkerFinanceConfig() {
return getWorkerFinanceConfig()
}
export async function saveAdminWorkerFinanceConfig(payload: JsonObject = {}) {
const config = await saveWorkerFinanceConfig(payload)
return config
}
export async function listAdminWorkerFinanceRequests(query: JsonObject = {}) {
const page = normalizePage(query.page)
const pageSize = normalizePageSize(query.pageSize)
const status = normalizeFinanceRequestStatus(query.status)
const requestType = normalizeFinanceRequestType(query.requestType)
const keyword = String(query.keyword || '').trim()
const { items, total } = await listWorkerFinanceRequests({
page,
pageSize,
status,
requestType,
keyword,
})
return {
items: items.map(mapFinanceRequest),
pagination: { page, pageSize, total },
}
}
export async function reviewAdminWorkerFinanceRequest(
requestId: number | string,
payload: JsonObject = {},
) {
const current = await getWorkerFinanceRequestById(requestId)
if (!current) {
throw createHttpError('资金申请不存在', {
statusCode: 404,
errorCode: 'worker_finance_request_not_found',
})
}
const status = normalizeAdminFinanceReviewStatus(payload.status)
const reviewedNote = String(payload.reviewedNote || payload.note || '').trim()
const reviewed = await reviewWorkerFinanceRequest({
requestId: Number(current.id),
status,
reviewedNote,
now: nowIso(),
})
if (reviewed.failureReason === 'request_not_pending') {
throw createHttpError('该申请已处理,请刷新后重试', {
statusCode: 409,
errorCode: 'worker_finance_request_reviewed',
})
}
if (reviewed.failureReason === 'withdraw_insufficient') {
throw createHttpError('打手余额不足,暂时无法通过该提现申请', {
statusCode: 409,
errorCode: 'worker_finance_request_withdraw_insufficient',
})
}
if (!reviewed.request) {
throw createHttpError('资金申请不存在', {
statusCode: 404,
errorCode: 'worker_finance_request_not_found',
})
}
return {
request: mapFinanceRequest(reviewed.request),
}
}
export async function listAdminWorkOrders(query: JsonObject = {}) {
const page = normalizePage(query.page)
const pageSize = normalizePageSize(query.pageSize)
@@ -1079,6 +1193,32 @@ export async function publishAdminWorkOrder(workOrderId: number | string) {
return { order: mapWorkOrderAdmin(updated || workOrder) }
}
export async function unpublishAdminWorkOrder(workOrderId: number | string) {
const workOrder = await getRequiredWorkOrder(workOrderId)
if (workOrder.status !== WORK_ORDER_STATUS.OPEN || workOrder.assigned_worker_id) {
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: '',
eventType: 'unpublished',
fromStatus: workOrder.status,
toStatus: WORK_ORDER_STATUS.UNASSIGNED,
now,
})
return { order: mapWorkOrderAdmin(updated || workOrder) }
}
export async function markAdminWorkOrderProblem(
workOrderId: number | string,
payload: JsonObject = {},
@@ -1559,6 +1699,14 @@ function mapFinanceRequest(request: WorkerFinanceRequestRow | null | undefined)
createdAt: request.created_at,
updatedAt: request.updated_at,
reviewedAt: request.reviewed_at,
worker: request.worker_id
? {
workerId: Number(request.worker_id),
username: request.worker_username || '',
displayName: request.worker_display_name || request.worker_username || '',
phone: request.worker_phone || '',
}
: null,
}
}
@@ -1902,6 +2050,7 @@ function normalizeWalletLedgerType(value: unknown) {
if (
[
'manual_credit',
'withdraw_paid',
'deposit_freeze',
'deposit_release',
'deposit_deduction',
@@ -1940,6 +2089,17 @@ function normalizeFinanceRequestStatus(value: unknown) {
})
}
function normalizeAdminFinanceReviewStatus(value: unknown) {
const status = String(value || '').trim()
if (['approved', 'rejected', 'cancelled'].includes(status)) {
return status
}
throw createHttpError('审核状态不正确', {
statusCode: 400,
errorCode: 'worker_finance_review_status_invalid',
})
}
function normalizeWithdrawChannel(value: unknown) {
const channel = String(value || '').trim()
if (['alipay', 'wechat', 'bank'].includes(channel)) {