fix(worker): 多份额验收优先唯一待提交份额并补充 shareId 防呆
This commit is contained in:
@@ -801,22 +801,29 @@ async function supplementWorkerAcceptedSharingEvidence(
|
||||
return { share: mapWorkOrderShare(updated || sharingShare) }
|
||||
}
|
||||
|
||||
type WorkerTargetShareLike = { id: number; status: string }
|
||||
|
||||
export type WorkerTargetShareSelection<T> = {
|
||||
share: T | null
|
||||
error: 'required' | 'not_found' | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 定位本次操作要处理的份额:
|
||||
* - 传了 shareId:必须是本人该工单下的有效份额(否则 not_found)
|
||||
* - 没传且只有 1 条:直接用
|
||||
* - 没传且有多条:要求前端明确指定(required)
|
||||
* - 没传且有多条:优先唯一一条「进行中(joined)」份额;仍有多条才要求前端指定(required)
|
||||
* - 没有任何份额:返回空(走整单逻辑)
|
||||
*
|
||||
* 多条时优先 joined 是因为 submitted / accepted 的份额已经不能再提交验收:
|
||||
* 第一份提交后只剩第二份待提交时,前端不传 shareId 也应该能直接定位到它。
|
||||
*/
|
||||
async function resolveWorkerTargetShare(
|
||||
workOrderId: number,
|
||||
workerId: number | string,
|
||||
payload: JsonObject,
|
||||
): Promise<{ share: WorkOrderShareRow | null; error: 'required' | 'not_found' | null }> {
|
||||
const requested = Number(payload.shareId ?? payload.workOrderShareId ?? 0)
|
||||
const shares = await listWorkOrderSharesByWorkerInOrder(workOrderId, workerId)
|
||||
if (Number.isFinite(requested) && requested > 0) {
|
||||
const found = shares.find((share) => Number(share.id) === requested) || null
|
||||
export function chooseWorkerTargetShare<T extends WorkerTargetShareLike>(
|
||||
shares: T[],
|
||||
requestedShareId: number,
|
||||
): WorkerTargetShareSelection<T> {
|
||||
if (Number.isFinite(requestedShareId) && requestedShareId > 0) {
|
||||
const found = shares.find((share) => Number(share.id) === requestedShareId) || null
|
||||
return { share: found, error: found ? null : 'not_found' }
|
||||
}
|
||||
if (shares.length === 0) {
|
||||
@@ -825,9 +832,23 @@ async function resolveWorkerTargetShare(
|
||||
if (shares.length === 1) {
|
||||
return { share: shares[0] ?? null, error: null }
|
||||
}
|
||||
const pendingShares = shares.filter((share) => String(share.status) === 'joined')
|
||||
if (pendingShares.length === 1) {
|
||||
return { share: pendingShares[0] ?? null, error: null }
|
||||
}
|
||||
return { share: null, error: 'required' }
|
||||
}
|
||||
|
||||
async function resolveWorkerTargetShare(
|
||||
workOrderId: number,
|
||||
workerId: number | string,
|
||||
payload: JsonObject,
|
||||
): Promise<WorkerTargetShareSelection<WorkOrderShareRow>> {
|
||||
const requested = Number(payload.shareId ?? payload.workOrderShareId ?? 0)
|
||||
const shares = await listWorkOrderSharesByWorkerInOrder(workOrderId, workerId)
|
||||
return chooseWorkerTargetShare(shares, requested)
|
||||
}
|
||||
|
||||
const ACCEPTANCE_VIDEO_SCENE = 'worker-acceptance-video'
|
||||
const ACCEPTANCE_VIDEO_CONTENT_TYPES = new Set(['video/mp4', 'video/webm'])
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { chooseWorkerTargetShare } from './worker-acceptance-service.js'
|
||||
|
||||
const submitted = { id: 11, status: 'submitted' }
|
||||
const joined = { id: 12, status: 'joined' }
|
||||
const accepted = { id: 13, status: 'accepted' }
|
||||
|
||||
test('份额选择:指定 shareId 命中本人份额', () => {
|
||||
const result = chooseWorkerTargetShare([submitted, joined], 12)
|
||||
assert.equal(result.error, null)
|
||||
assert.equal(result.share?.id, 12)
|
||||
})
|
||||
|
||||
test('份额选择:指定了不属于自己的 shareId 返回 not_found', () => {
|
||||
const result = chooseWorkerTargetShare([submitted, joined], 99)
|
||||
assert.equal(result.error, 'not_found')
|
||||
assert.equal(result.share, null)
|
||||
})
|
||||
|
||||
test('份额选择:没有任何份额时走整单逻辑', () => {
|
||||
const result = chooseWorkerTargetShare([], 0)
|
||||
assert.equal(result.error, null)
|
||||
assert.equal(result.share, null)
|
||||
})
|
||||
|
||||
test('份额选择:只有一条份额时直接使用,不要求 shareId', () => {
|
||||
const result = chooseWorkerTargetShare([joined], 0)
|
||||
assert.equal(result.error, null)
|
||||
assert.equal(result.share?.id, joined.id)
|
||||
})
|
||||
|
||||
test('份额选择:多条中只剩一条 joined 时自动选中,不再要求 shareId', () => {
|
||||
const result = chooseWorkerTargetShare([submitted, joined, accepted], 0)
|
||||
assert.equal(result.error, null)
|
||||
assert.equal(result.share?.id, joined.id)
|
||||
})
|
||||
|
||||
test('份额选择:多条 joined 时仍要求显式指定', () => {
|
||||
const secondJoined = { id: 14, status: 'joined' }
|
||||
const result = chooseWorkerTargetShare([joined, secondJoined, submitted], 0)
|
||||
assert.equal(result.error, 'required')
|
||||
assert.equal(result.share, null)
|
||||
})
|
||||
|
||||
test('份额选择:全部已提交/已验收时不能自动选中', () => {
|
||||
const result = chooseWorkerTargetShare([submitted, accepted], 0)
|
||||
assert.equal(result.error, 'required')
|
||||
assert.equal(result.share, null)
|
||||
})
|
||||
@@ -323,8 +323,12 @@ export default function WorkerOrdersPage() {
|
||||
}
|
||||
|
||||
function openAcceptanceModal(order: WorkOrder) {
|
||||
markOrderAsRead(order)
|
||||
const shares = getOrderShares(order)
|
||||
if (shares.length === 0) {
|
||||
message.warning('未找到可验收的拼单份额,请刷新后重试')
|
||||
return
|
||||
}
|
||||
markOrderAsRead(order)
|
||||
if (shares.length > 1) {
|
||||
setPickedShareId(shares[0]?.shareId || 0)
|
||||
setSharePicker({ order, action: 'accept' })
|
||||
@@ -487,6 +491,10 @@ export default function WorkerOrdersPage() {
|
||||
|
||||
async function submitAcceptance(values: AcceptanceFormValues) {
|
||||
if (!editingOrder) return
|
||||
if (getOrderShares(editingOrder).length > 1 && !editingShareId) {
|
||||
message.warning('请先选择要验收的拼单份额')
|
||||
return
|
||||
}
|
||||
const isAcceptedEvidenceSupplement = canSupplementAcceptedEvidence(editingOrder)
|
||||
if (isAcceptedEvidenceSupplement) {
|
||||
if (acceptanceFiles.length === 0 && !acceptanceVideoDirty) {
|
||||
@@ -552,6 +560,10 @@ export default function WorkerOrdersPage() {
|
||||
|
||||
async function saveAcceptanceImages(values: AcceptanceFormValues) {
|
||||
if (!editingOrder) return
|
||||
if (getOrderShares(editingOrder).length > 1 && !editingShareId) {
|
||||
message.warning('请先选择要操作的拼单份额')
|
||||
return
|
||||
}
|
||||
if (hasSubmittedAcceptance(editingOrder) && acceptanceFiles.length === 0 && !acceptanceVideo) {
|
||||
message.error('请至少保留一张验收图片或一段验收视频')
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user