diff --git a/apps/backend/src/repositories/worker-platform/shared.ts b/apps/backend/src/repositories/worker-platform/shared.ts index 2e5fcec0..9af1016f 100644 --- a/apps/backend/src/repositories/worker-platform/shared.ts +++ b/apps/backend/src/repositories/worker-platform/shared.ts @@ -56,7 +56,8 @@ export function resolveWorkOrderShareCancellationStatus( workOrderStatus: string, shareStatus: string, ): 'open' | null { - if (!['open', 'pending_acceptance'].includes(workOrderStatus)) return null + // in_progress 用于解围历史问题单处置产生的"份额已提交、母单回到进行中"的卡死存量单 + if (!['open', 'pending_acceptance', 'in_progress'].includes(workOrderStatus)) return null if (!['joined', 'submitted'].includes(shareStatus)) return null return 'open' } diff --git a/apps/backend/src/repositories/worker-platform/work-order-issue-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-issue-repo.ts index 33fd4786..8712ca61 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-issue-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-issue-repo.ts @@ -6,6 +6,16 @@ import { getOutstandingDepositAmountWithClient } from './work-order-deposit-repo import { RESET_GIFT_FLOW_SQL } from './work-order-gift-repo.js' import type { ProblemWorkOrderResolutionAction, WorkOrderRow } from './types.js' +/** 问题单退回打手时,按标记前状态恢复;异常历史数据回退单人单进行中。 */ +export function resolveProblemReturnToWorkerStatus( + fromStatus: string, +): 'open' | 'in_progress' | 'pending_acceptance' { + if (['open', 'in_progress', 'pending_acceptance'].includes(fromStatus)) { + return fromStatus as 'open' | 'in_progress' | 'pending_acceptance' + } + return 'in_progress' +} + export async function resolveProblemWorkOrder(input: { workOrderId: number action: ProblemWorkOrderResolutionAction @@ -27,6 +37,26 @@ export async function resolveProblemWorkOrder(input: { const workOrder = currentResult.rows[0] || null if (!workOrder) return null + // return_to_worker 需恢复到标记问题单前的真实状态:拼单份额已全部提交时 + // 标记前是 pending_acceptance,若一律回到 in_progress 会陷入"份额已提交、 + // 母单却不可验收也不可重新提交"的卡死状态,因此按最近一次 marked_problem + // 事件的 from_status 恢复。 + let restoreStatus = '' + if (input.action === 'return_to_worker') { + const eventResult = await client.query<{ from_status: string }>( + ` + SELECT from_status + FROM work_order_events + WHERE work_order_id = $1 AND event_type = 'marked_problem' + ORDER BY id DESC + LIMIT 1 + `, + [input.workOrderId], + ) + const fromStatus = String(eventResult.rows[0]?.from_status || '').trim() + restoreStatus = resolveProblemReturnToWorkerStatus(fromStatus) + } + const shouldReleaseDeposit = ['reopen', 'cancel_release'].includes(input.action) const shouldDeductDeposit = input.action === 'cancel_deduct' const workerId = Number(workOrder.assigned_worker_id || 0) @@ -83,12 +113,16 @@ export async function resolveProblemWorkOrder(input: { const targetStatus = input.action === 'return_to_worker' - ? 'in_progress' + ? restoreStatus || 'in_progress' : input.action === 'reopen' ? 'open' : 'cancelled' const shouldClearAssignment = input.action === 'reopen' - const shouldClearSubmission = ['return_to_worker', 'reopen'].includes(input.action) + // 仅在真正退回打手重做(回到进行中)时清验收提交;份额已提交待验收的拼单 + // 需保留母单待验收状态,否则份额验收链路断裂。 + const shouldClearSubmission = + input.action === 'reopen' || + (input.action === 'return_to_worker' && targetStatus === 'in_progress') await client.query( ` diff --git a/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts index 3f2e6d4a..66cc0ff7 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-settlement-repo.ts @@ -392,8 +392,10 @@ export async function acceptWorkOrderShareAndSettle(input: { failureReason: 'work_order_not_accepting' | 'share_not_submitted' | null }> { return withTransaction(async (client) => { + // in_progress 仅用于解围历史问题单处置产生的"份额已提交、母单却回到进行中" + // 的卡死存量单;正常拼单流程母单只会是 open / pending_acceptance。 const orderResult = await client.query( - `SELECT * FROM work_orders WHERE id = $1 AND status IN ('open', 'pending_acceptance') FOR UPDATE`, + `SELECT * FROM work_orders WHERE id = $1 AND status IN ('open', 'pending_acceptance', 'in_progress') FOR UPDATE`, [input.workOrderId], ) const workOrder = orderResult.rows[0] || null @@ -592,7 +594,7 @@ export async function cancelWorkOrderShare(input: { [input.workOrderId], ) const workOrder = orderResult.rows[0] || null - if (!workOrder || !['open', 'pending_acceptance'].includes(workOrder.status)) { + if (!workOrder || !['open', 'pending_acceptance', 'in_progress'].includes(workOrder.status)) { return { order: workOrder, share: null, diff --git a/apps/backend/src/repositories/worker-platform/work-order-share-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-share-repo.ts index 596727d3..2959d675 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-share-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-share-repo.ts @@ -295,7 +295,7 @@ export async function submitWorkOrderShareAcceptance(input: { now: string }): Promise<{ share: WorkOrderShareRow | null - failureReason: 'share_not_found' | 'share_status_invalid' | null + failureReason: 'share_not_found' | 'share_status_invalid' | 'work_order_status_invalid' | null }> { return withTransaction(async (client) => { const orderResult = await client.query<{ @@ -309,9 +309,12 @@ export async function submitWorkOrderShareAcceptance(input: { [input.workOrderId], ) const workOrder = orderResult.rows[0] || null - if (!workOrder || !['open', 'pending_acceptance'].includes(workOrder.status)) { + if (!workOrder) { return { share: null, failureReason: 'share_not_found' } } + if (!['open', 'pending_acceptance'].includes(workOrder.status)) { + return { share: null, failureReason: 'work_order_status_invalid' } + } const lockResult = await client.query<{ id: number }>( ` diff --git a/apps/backend/src/services/worker-platform/worker-acceptance-service.ts b/apps/backend/src/services/worker-platform/worker-acceptance-service.ts index 1b94df64..4d3c4ea4 100644 --- a/apps/backend/src/services/worker-platform/worker-acceptance-service.ts +++ b/apps/backend/src/services/worker-platform/worker-acceptance-service.ts @@ -435,6 +435,12 @@ async function submitWorkerSharingAcceptance( errorCode: 'work_order_sharing_share_submitted', }) } + if (result.failureReason === 'work_order_status_invalid') { + throw createHttpError('当前拼单状态不能提交验收资料,请联系管理员', { + statusCode: 409, + errorCode: 'work_order_sharing_submit_status_invalid', + }) + } if (!result.share) { throw createHttpError('未找到拼单份额', { statusCode: 404, diff --git a/apps/backend/src/services/worker-platform/worker-platform-service.test.ts b/apps/backend/src/services/worker-platform/worker-platform-service.test.ts index f15cf913..402265f4 100644 --- a/apps/backend/src/services/worker-platform/worker-platform-service.test.ts +++ b/apps/backend/src/services/worker-platform/worker-platform-service.test.ts @@ -16,6 +16,7 @@ import { resolveWorkOrderShareJoinQuantity, resolveOpenSharingSyncReward, } from '../../repositories/worker-platform/index.js' +import { resolveProblemReturnToWorkerStatus } from '../../repositories/worker-platform/work-order-issue-repo.js' import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js' import { assertWithdrawChannelAllowedForAmount, @@ -1045,6 +1046,13 @@ test('拼单份额撤单只允许未验收份额并让母工单回到大厅状 assert.equal(resolveWorkOrderShareCancellationStatus('open', 'cancelled'), null) }) +test('问题单退回打手按标记前状态恢复', () => { + assert.equal(resolveProblemReturnToWorkerStatus('pending_acceptance'), 'pending_acceptance') + assert.equal(resolveProblemReturnToWorkerStatus('open'), 'open') + assert.equal(resolveProblemReturnToWorkerStatus('in_progress'), 'in_progress') + assert.equal(resolveProblemReturnToWorkerStatus(''), 'in_progress') +}) + test('后台更新资料时普通接单可以修改接单价格', () => { assert.equal( resolveAdminMaterialRewardAmount({