fix:打手端/我的订单/验收:验收视频和验收图片至少上传一个就好了,其他逻辑按照上传图片走

This commit is contained in:
2026-10-25 16:45:38 +08:00
parent 6993c36efb
commit 60fe00b28c
4 changed files with 55 additions and 34 deletions
@@ -77,6 +77,8 @@ export async function createWorkerAcceptanceAdminNotification(input: {
productName: string
workerName: string
imageCount: number
/** 是否提交了验收视频:视频与图片至少一个,通知里要体现,避免显示成“0 张图片”。 */
hasVideo?: boolean
/** 拼单份额:每份提交各生成一条待办(按 shareId 去重) */
shareId?: number | null
}) {
@@ -97,6 +99,7 @@ export async function createWorkerAcceptanceAdminNotification(input: {
`打手:${input.workerName || '-'}`,
...(shareId > 0 ? [`份额:#${shareId}`] : []),
`验收图片:${input.imageCount} 张`,
...(input.hasVideo ? ['验收视频:已上传'] : []),
].join(';'),
actionPath: `${ADMIN_WORKER_PLATFORM_PATH}?tab=orders`,
now: new Date().toISOString(),
@@ -1,3 +1,4 @@
import { hasAcceptanceEvidence } from '../../domain/work-order-acceptance-evidence.js'
import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js'
import {
isFriendGiftAcceptanceMode,
@@ -155,8 +156,10 @@ export async function submitWorkerOrderAcceptance(
workOrder.draft_acceptance_json,
)
const video = resolveAcceptanceVideo(payload, workOrder.draft_acceptance_json)
if (imageUrls.length === 0 && !canAutoAcceptWithoutEvidence) {
throw createHttpError('请上传验收图片', {
// 验收图片与验收视频至少上传一个;有视频时视同有图片,后续口径完全一致。
const hasEvidence = hasAcceptanceEvidence({ imageUrls, video })
if (!hasEvidence && !canAutoAcceptWithoutEvidence) {
throw createHttpError('请上传验收图片或验收视频', {
statusCode: 400,
errorCode: 'work_order_acceptance_image_required',
})
@@ -170,9 +173,8 @@ export async function submitWorkerOrderAcceptance(
...(canAutoAcceptWithoutEvidence
? {
reviewMode: 'vip_auto',
evidenceStatus: imageUrls.length > 0 ? 'complete' : 'pending',
evidenceDueAt:
imageUrls.length > 0 ? null : addHours(now, VIP_AUTO_ACCEPT_EVIDENCE_WINDOW_HOURS),
evidenceStatus: hasEvidence ? 'complete' : 'pending',
evidenceDueAt: hasEvidence ? null : addHours(now, VIP_AUTO_ACCEPT_EVIDENCE_WINDOW_HOURS),
}
: {}),
}
@@ -252,6 +254,7 @@ export async function submitWorkerOrderAcceptance(
productName: workOrder.product_name,
workerName: worker.display_name || worker.username,
imageCount: imageUrls.length,
hasVideo: Boolean(video),
})
publishWorkOrderRealtimeChange({
workOrderId: Number(workOrder.id),
@@ -303,6 +306,7 @@ export async function remindWorkerOrderAcceptance(
productName: workOrder.product_name,
workerName: worker.display_name || worker.username,
imageCount: normalizeStringArray(safeParseJson(workOrder.acceptance_json).imageUrls).length,
hasVideo: Boolean(safeParseJson(workOrder.acceptance_json).video),
})
notification = await getWorkerAcceptanceReminder(Number(workOrder.id))
}
@@ -396,7 +400,7 @@ export async function supplementWorkerAcceptedOrderEvidence(
const payloadVideo = resolveAcceptanceVideoFromPayload(payload)
const video = payloadVideo || readAcceptanceVideoLenient(previous)
if (!mergedEvidence.changed && !payloadVideo) {
throw createHttpError('请上传新的验收图片', {
throw createHttpError('请上传新的验收图片或新的验收视频', {
statusCode: 400,
errorCode: 'work_order_evidence_supplement_image_required',
})
@@ -485,8 +489,10 @@ async function submitWorkerSharingAcceptance(
sharingShare.draft_acceptance_json,
)
const video = resolveAcceptanceVideo(payload, sharingShare.draft_acceptance_json)
if (imageUrls.length === 0 && !canAutoAcceptWithoutEvidence) {
throw createHttpError('请上传验收图片', {
// 验收图片与验收视频至少上传一个;有视频时视同有图片,后续口径完全一致。
const hasEvidence = hasAcceptanceEvidence({ imageUrls, video })
if (!hasEvidence && !canAutoAcceptWithoutEvidence) {
throw createHttpError('请上传验收图片或验收视频', {
statusCode: 400,
errorCode: 'work_order_acceptance_image_required',
})
@@ -500,9 +506,8 @@ async function submitWorkerSharingAcceptance(
...(canAutoAcceptWithoutEvidence
? {
reviewMode: 'vip_auto',
evidenceStatus: imageUrls.length > 0 ? 'complete' : 'pending',
evidenceDueAt:
imageUrls.length > 0 ? null : addHours(now, VIP_AUTO_ACCEPT_EVIDENCE_WINDOW_HOURS),
evidenceStatus: hasEvidence ? 'complete' : 'pending',
evidenceDueAt: hasEvidence ? null : addHours(now, VIP_AUTO_ACCEPT_EVIDENCE_WINDOW_HOURS),
}
: {}),
}
@@ -562,6 +567,7 @@ async function submitWorkerSharingAcceptance(
productName: workOrder.product_name,
workerName: worker.display_name || worker.username,
imageCount: acceptance.imageUrls.length,
hasVideo: Boolean(acceptance.video),
shareId: Number(sharingShare.id),
})
await publishWorkOrderParticipantChange(Number(workOrder.id), {
@@ -593,7 +599,7 @@ async function supplementWorkerAcceptedSharingEvidence(
const payloadVideo = resolveAcceptanceVideoFromPayload(payload)
const video = payloadVideo || readAcceptanceVideoLenient(previous)
if (!mergedEvidence.changed && !payloadVideo) {
throw createHttpError('请上传新的验收图片', {
throw createHttpError('请上传新的验收图片或新的验收视频', {
statusCode: 400,
errorCode: 'work_order_sharing_evidence_supplement_image_required',
})
@@ -846,15 +852,16 @@ export async function saveWorkerOrderAcceptanceDraft(
const worker = await getRequiredWorker(session.workerId)
if (workOrder.status === WORK_ORDER_STATUS.PENDING_ACCEPTANCE) {
if (imageUrls.length === 0) {
throw createHttpError('请至少保留一张验收图片', {
const previous = safeParseJson(workOrder.acceptance_json)
// 已提交后只允许替换视频,不允许删除证据。
const video = resolveAcceptanceVideo(payload, previous)
// 验收图片与验收视频至少保留一个。
if (!hasAcceptanceEvidence({ imageUrls, video })) {
throw createHttpError('请至少保留一张验收图片或一段验收视频', {
statusCode: 400,
errorCode: 'work_order_acceptance_image_required',
})
}
const previous = safeParseJson(workOrder.acceptance_json)
// 已提交后只允许替换视频,不允许删除证据。
const video = resolveAcceptanceVideo(payload, previous)
const acceptance = {
note: String(payload.note ?? previous.note ?? '').trim(),
files,
@@ -944,15 +951,16 @@ async function saveWorkerSharingAcceptanceDraft(
const { files, imageUrls } = resolveAcceptanceImages(payload, {})
if (sharingShare.status === 'submitted') {
if (imageUrls.length === 0) {
throw createHttpError('请至少保留一张验收图片', {
const previous = safeParseJson(sharingShare.acceptance_json)
// 已提交后只允许替换视频,不允许删除证据。
const video = resolveAcceptanceVideo(payload, previous)
// 验收图片与验收视频至少保留一个。
if (!hasAcceptanceEvidence({ imageUrls, video })) {
throw createHttpError('请至少保留一张验收图片或一段验收视频', {
statusCode: 400,
errorCode: 'work_order_acceptance_image_required',
})
}
const previous = safeParseJson(sharingShare.acceptance_json)
// 已提交后只允许替换视频,不允许删除证据。
const video = resolveAcceptanceVideo(payload, previous)
const acceptance = {
note: String(payload.note ?? previous.note ?? '').trim(),
files,
@@ -267,17 +267,18 @@ export function WorkerOrderModals({
<Form.Item
label="验收图片"
required={
!editingOrder ||
canSupplementAcceptedEvidence(editingOrder) ||
!canAutoAcceptOrder(editingOrder, workerCanAutoAcceptWithoutEvidence)
!acceptanceVideo &&
(!editingOrder ||
canSupplementAcceptedEvidence(editingOrder) ||
!canAutoAcceptOrder(editingOrder, workerCanAutoAcceptWithoutEvidence))
}
extra={
editingOrder && canSupplementAcceptedEvidence(editingOrder)
? '仅可追加新图片,原有验收图片会保留。'
: editingOrder &&
canAutoAcceptOrder(editingOrder, workerCanAutoAcceptWithoutEvidence)
? 'VIP 免审核可先提交,验收后需在 24 小时内补充图片。'
: undefined
? 'VIP 免审核可先提交,验收后需在 24 小时内补充图片或视频。'
: '验收图片与验收视频至少上传一个,已上传视频时图片可不填。'
}
>
<ImageUpload
@@ -290,8 +291,8 @@ export function WorkerOrderModals({
/>
</Form.Item>
<Form.Item
label="验收视频(选填)"
extra="选片后会在本机压到 720p 再上传;已提交的验收只能替换视频,不能删除。"
label="验收视频"
extra="与验收图片至少上传一个;选片后会在本机压到 720p 再上传;已提交的验收只能替换视频,不能删除。"
>
<VideoUpload value={acceptanceVideo} onChange={onAcceptanceVideoChange} />
</Form.Item>
@@ -518,8 +518,10 @@ export default function WorkerOrdersPage() {
}
const canAutoAccept = canAutoAcceptOrder(editingOrder, workerCanAutoAcceptWithoutEvidence)
if (acceptanceFiles.length === 0 && !canAutoAccept) {
message.error('请上传验收图片')
// 验收图片与验收视频至少上传一个;有视频时视同有图片,后续口径完全一致。
const hasEvidence = acceptanceFiles.length > 0 || Boolean(acceptanceVideo)
if (!hasEvidence && !canAutoAccept) {
message.error('请上传验收图片或验收视频')
return
}
try {
@@ -529,7 +531,14 @@ export default function WorkerOrdersPage() {
video: acceptanceVideo,
...(editingShareId ? { shareId: editingShareId } : {}),
})
message.success(canAutoAccept ? '已自动验收,可在 24 小时内补充图片' : '已提交验收')
// 已有图片或视频时证据即算齐备,不再提示补图。
message.success(
canAutoAccept
? hasEvidence
? '已自动验收'
: '已自动验收,可在 24 小时内补充图片或视频'
: '已提交验收',
)
setEditingOrder(null)
setAcceptanceFiles([])
setAcceptanceVideo(null)
@@ -543,8 +552,8 @@ export default function WorkerOrdersPage() {
async function saveAcceptanceImages(values: AcceptanceFormValues) {
if (!editingOrder) return
if (hasSubmittedAcceptance(editingOrder) && acceptanceFiles.length === 0) {
message.error('请至少保留一张验收图片')
if (hasSubmittedAcceptance(editingOrder) && acceptanceFiles.length === 0 && !acceptanceVideo) {
message.error('请至少保留一张验收图片或一段验收视频')
return
}
try {