fix(worker): 验收重复提交幂等,修复卡待验收与手动验收 500

This commit is contained in:
yml2213
2026-10-30 21:33:56 +08:00
parent a6ccb2e2d8
commit 96ae744dbb
6 changed files with 453 additions and 239 deletions
@@ -14,10 +14,10 @@ import {
countWorkOrderPendingSharingSubmissions,
createWorkOrderEvent,
listWorkOrderSharesByWorkerInOrder,
markWorkOrderAcceptanceSubmitted,
settleOverdueWorkOrder,
settleOverdueWorkOrderShare,
submitWorkOrderShareAcceptance,
updateWorkOrder,
updateWorkOrderAcceptanceRecord,
updateWorkOrderDraftAcceptance,
updateWorkOrderShareAcceptanceRecord,
@@ -82,7 +82,7 @@ export async function submitWorkerOrderAcceptance(
} = {},
) {
requireActiveWorkerSession(session)
const workOrder = await getRequiredWorkOrder(workOrderId)
let workOrder = await getRequiredWorkOrder(workOrderId)
const workerId = Number(session.workerId)
const worker = await getRequiredWorker(session.workerId)
if (!options.asConsignmentSupplier) {
@@ -184,28 +184,54 @@ export async function submitWorkerOrderAcceptance(
}
: {}),
}
const updated = await updateWorkOrder(workOrder.id, {
status: WORK_ORDER_STATUS.PENDING_ACCEPTANCE,
acceptance_json: acceptance,
draft_acceptance_json: {},
submitted_at: now,
updated_at: now,
})
await createWorkOrderEvent({
const transitioned = await markWorkOrderAcceptanceSubmitted({
workOrderId: workOrder.id,
actorType: 'worker',
actorId: String(session.workerId),
eventType: 'acceptance_submitted',
fromStatus: workOrder.status,
toStatus: WORK_ORDER_STATUS.PENDING_ACCEPTANCE,
payloadJson: JSON.stringify({
imageCount: imageUrls.length,
hasVideo: Boolean(video),
autoAccepted: canAutoAcceptWithoutEvidence,
evidenceDueAt: acceptance.evidenceDueAt || null,
}),
acceptanceJson: acceptance,
now,
})
let updated = transitioned
if (transitioned) {
await createWorkOrderEvent({
workOrderId: workOrder.id,
actorType: 'worker',
actorId: String(session.workerId),
eventType: 'acceptance_submitted',
fromStatus: workOrder.status,
toStatus: WORK_ORDER_STATUS.PENDING_ACCEPTANCE,
payloadJson: JSON.stringify({
imageCount: imageUrls.length,
hasVideo: Boolean(video),
autoAccepted: canAutoAcceptWithoutEvidence,
evidenceDueAt: acceptance.evidenceDueAt || null,
}),
now,
})
} else {
// 重复提交(双击/前端重试):第一个请求可能已经验收或正在结算。
// 绝不能把 accepted 再写回待验收,只按当前状态幂等处理。
const latest = await getRequiredWorkOrder(workOrder.id).catch(() => null)
const ownerMatched =
options.asConsignmentSupplier || Number(latest?.assigned_worker_id || 0) === workerId
if (
!latest ||
!ownerMatched ||
![WORK_ORDER_STATUS.PENDING_ACCEPTANCE, WORK_ORDER_STATUS.ACCEPTED].includes(
latest.status as never,
)
) {
throw createHttpError('当前状态不能提交验收', {
statusCode: 409,
errorCode: 'work_order_submit_status_invalid',
})
}
if (latest.status === WORK_ORDER_STATUS.ACCEPTED) {
publishWorkOrderRealtimeChange({ workOrderId: Number(latest.id), workerIds: [workerId] })
return { order: mapWorkOrderForWorker(latest, resolveWorkerPermissions(worker)) }
}
// 上一请求已推进到待验收但结算未完成:用已有验收资料重试结算,不重复写资料。
workOrder = latest
updated = latest
}
// 好友赠送模式必须由后台人工验收赠送凭证,内部打手与 VIP 免审核直通均不适用。
if (
!isFriendGiftAcceptanceMode(workOrder.acceptance_mode) &&