fix: align audit and dispatch workflows and release failed requests

This commit is contained in:
2026-10-22 23:10:09 +08:00
parent 9cdcb9c077
commit 4a3456756f
124 changed files with 1296 additions and 2120 deletions
@@ -22,13 +22,12 @@ type ReceiptInput = ExceptionReturnSource & { sourcePalletNo: string; quantity:
const receipts = ref<ReceiptInput[]>([]), targets = ref<PalletDto[]>([]), notes = ref(''), evidence = ref<{ fileId: string; fileName: string }[]>([])
type Attempt = { action: ExceptionAction; payload: ExceptionCommand }
const pendingByScope = reactive(new Map<string, Attempt>())
const unknownUploads = reactive(new Set<string>())
const savedEvidence = new Map<string, { fileId: string; fileName: string }[]>()
const selectedHistory = ref<string[]>([])
const inFlight = reactive(new Map<string, string>())
const uploadingScopes = reactive(new Set<string>())
const pending = computed(() => pendingByScope.get(identity.value))
const locked = computed(() => busy.value || inFlight.has(identity.value) || uploadingScopes.has(identity.value) || !!pending.value || unknownUploads.has(identity.value))
const locked = computed(() => busy.value || inFlight.has(identity.value) || uploadingScopes.has(identity.value) || !!pending.value)
const warehouseReturn = computed(() => detail.value?.order.exceptionType === 'RETURN' && detail.value.order.returnDirection === 'OVERSEAS_WAREHOUSE')
const allowed = (action: string) => canProcess.value && detail.value?.order.allowedActions?.includes(action)
let sequence = 0
@@ -91,13 +90,12 @@ async function upload(event: Event) {
savedEvidence.set(scope, [...(savedEvidence.get(scope) || []), fileResult])
if (current(ticket)) evidence.value.push(fileResult)
} catch (cause) {
if (!definite(cause)) unknownUploads.add(scope)
if (current(ticket)) error.value = definite(cause) ? getApiErrorDetails(cause, '证据上传失败').message : '证据上传结果未知,请先人工核对;不会自动重复上传'
if (current(ticket)) error.value = getApiErrorDetails(cause, '证据上传失败,请重新选择文件').message
} finally { uploadingScopes.delete(scope); if (current(ticket)) busy.value = false; input.value = '' }
}
async function execute(action: ExceptionAction) {
const actionName = { 'start-processing': 'START_PROCESSING', complete: 'COMPLETE_EXCEPTION', cancel: 'CANCEL_EXCEPTION' }[action]
if (busy.value || inFlight.has(identity.value) || uploadingScopes.has(identity.value) || loading.value || !canProcess.value || (!pending.value && !allowed(actionName)) || unknownUploads.has(identity.value)) return
if (busy.value || inFlight.has(identity.value) || uploadingScopes.has(identity.value) || loading.value || !canProcess.value || (!pending.value && !allowed(actionName))) return
if (pending.value && pending.value.action !== action) return
if (!pending.value) {
const selectedFiles = [...new Set([...evidence.value.map(row => row.fileId), ...selectedHistory.value])]
@@ -128,7 +126,7 @@ async function execute(action: ExceptionAction) {
const result = await commandException(customer, number, attempt.action, attempt.payload)
const statuses = attempt.action === 'complete' ? ['PROCESSING', 'COMPLETED'] : [attempt.action === 'cancel' ? 'CANCELLED' : 'PROCESSING']
if (!result || result.exceptionNo !== number || Number(result.version) !== attempt.payload.expectedVersion + 1 || !statuses.includes(result.status))
throw new Error('处理结果无法核对,请保留原请求重试')
throw new Error('处理结果无法核对,请刷新当前记录')
if (sameAttempt()) { pendingByScope.delete(scope); savedEvidence.delete(scope) }
if (!current(ticket)) return
message.value = action === 'complete' && result.status === 'PROCESSING' ? `本批已实收 ${result.restoredPackages} 箱,仍有待收货物` : '操作已保存'
@@ -137,9 +135,9 @@ async function execute(action: ExceptionAction) {
if (definite(cause) && sameAttempt()) pendingByScope.delete(scope)
if (!current(ticket)) return
const text = getApiErrorDetails(cause, '处理请求失败').message
if (definite(cause)) { responseSequence = sequence + 1; await load(); if (current(responseSequence)) error.value = `${text};已重新读取当前版本,请核对后再操作` }
else error.value = `${text};结果未确认,已保留原请求,请仅重试原操作`
error.value = text
} finally {
if (sameAttempt()) pendingByScope.delete(scope)
if (inFlight.get(scope) === requestKey) inFlight.delete(scope)
if (current(responseSequence)) busy.value = false
}
@@ -221,12 +219,9 @@ watch(() => [props.customerId, props.exceptionNo, props.active], () => { ++seque
<p v-else-if="detail.order.exceptionType === 'RETURN'">关闭此异常只记录处理结果,不代表货物交接完成,也不会释放车辆或司机。</p>
</template>
</el-form>
<p v-if="pending">存在结果未确认的操作,表单已锁定;切换后仍保留该单原请求。</p>
<p v-if="unknownUploads.has(identity)">证据上传结果未知,需人工核对后继续。</p>
<el-button v-if="pending && !detail.order.allowedActions?.includes({ 'start-processing': 'START_PROCESSING', complete: 'COMPLETE_EXCEPTION', cancel: 'CANCEL_EXCEPTION' }[pending.action])" data-test="retry" :disabled="busy" @click="execute(pending.action)">重试原操作</el-button>
<el-button v-if="allowed('START_PROCESSING')" data-test="start" :disabled="busy || (!!pending && pending.action !== 'start-processing')" @click="execute('start-processing')">{{ pending ? '重试原开始请求' : '开始处理' }}</el-button>
<el-button v-if="allowed('CANCEL_EXCEPTION')" data-test="cancel" :disabled="busy || (!!pending && pending.action !== 'cancel')" @click="execute('cancel')">{{ pending ? '重试原取消请求' : '取消异常' }}</el-button>
<el-button v-if="allowed('COMPLETE_EXCEPTION')" data-test="complete" :disabled="busy || (!!pending && pending.action !== 'complete')" @click="execute('complete')">{{ pending ? '重试原处理请求' : warehouseReturn ? '提交本批实收' : '完成处理' }}</el-button>
<el-button v-if="allowed('START_PROCESSING')" data-test="start" :disabled="busy || (!!pending && pending.action !== 'start-processing')" @click="execute('start-processing')">{{ '开始处理' }}</el-button>
<el-button v-if="allowed('CANCEL_EXCEPTION')" data-test="cancel" :disabled="busy || (!!pending && pending.action !== 'cancel')" @click="execute('cancel')">{{ '取消异常' }}</el-button>
<el-button v-if="allowed('COMPLETE_EXCEPTION')" data-test="complete" :disabled="busy || (!!pending && pending.action !== 'complete')" @click="execute('complete')">{{ warehouseReturn ? '提交本批实收' : '完成处理' }}</el-button>
</template>
</template>
</section>