fix(dispatch): submit and preserve independent loading groups

This commit is contained in:
2026-10-05 03:30:06 +08:00
parent 058b0778a2
commit 28eb43893a
4 changed files with 267 additions and 13 deletions
@@ -10,7 +10,7 @@ import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import AppDialog from '@/shared/components/AppDialog.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
import { createDispatchDraftContainer, deleteDispatchDraftContainer, listDispatchCandidates, listDispatchDrafts, lockDispatchDraftPallet, preArrangeDispatch, unlockDispatchDraftPallet, type DispatchPalletCandidateDto } from '../api/dispatches'
import { createDispatchDraftContainer, deleteDispatchDraftContainer, listDispatchCandidates, listDispatchDrafts, lockDispatchDraftPallet, preArrangeDispatch, preArrangeDraftDispatch, unlockDispatchDraftPallet, type DispatchPalletCandidateDto } from '../api/dispatches'
import { formatOverseasDateTime } from '../lastMilePresentation'
import type { InboundOrderListItem } from '../types'
import { dispatchVehicleSpecs } from '../dispatchVehicleSpecs'
@@ -198,10 +198,12 @@ function reset() {
}
function addContainer() {
if (saving.value) return
selectedVehicle.value = ''
vehicleDialogOpen.value = true
}
async function confirmAddContainer() {
if (saving.value) return
const spec = selectedVehicleSpec.value
if (!spec) {
ElMessage.warning('请选择车型')
@@ -224,6 +226,7 @@ async function confirmAddContainer() {
async function movePallet(item: DispatchPalletCandidateDto, containerId: string) {
const id = palletId(item)
if (saving.value || busyPalletIds.value.includes(id)) return
const target = containers.value.find((container) => container.id === containerId)
if (!target || target.palletIds.includes(id)) return
if (!String(item.overseasWarehouseId || '').trim()) {
@@ -267,6 +270,7 @@ async function movePallet(item: DispatchPalletCandidateDto, containerId: string)
}
async function removePallet(id: string) {
if (saving.value || busyPalletIds.value.includes(id)) return
const owner = containerFor(id)
if (!owner) return
busyPalletIds.value.push(id)
@@ -282,6 +286,7 @@ async function removePallet(id: string) {
}
async function deleteContainer(container: LoadingContainer) {
if (saving.value) return
for (const id of [...container.palletIds]) {
await removePallet(id)
}
@@ -300,10 +305,16 @@ function dropInto(event: DragEvent, containerId: string) {
}
async function close(value = false) {
if (saving.value) return
emit('update:modelValue', value)
}
async function submit() {
if (saving.value) return
if (busyPalletIds.value.length) {
ElMessage.warning('托盘来源正在锁定,请稍后再预排车')
return
}
if (loading.value || unresolvedAssignedIds.value.length) {
ElMessage.warning('草稿中部分已选来源尚未恢复,请重试加载后再预排;原选择已保留')
return
@@ -312,6 +323,18 @@ async function submit() {
ElMessage.warning('请至少移入一个托盘')
return
}
const selectedContainers = containers.value.filter(container => container.palletIds.length > 0)
if (new Set(assignedIds.value).size !== assignedIds.value.length
|| selectedContainers.some(container => !container.id.trim())
|| new Set(selectedContainers.map(container => container.id)).size !== selectedContainers.length) {
ElMessage.warning('草稿中存在重复托盘或无效装车容器,请核对分组;原草稿已保留')
return
}
if (assigned.value.some(item => item.sourceType === 'RETURN_RECEIPT'
? !String(item.returnReceiptId || '').trim() : !String(item.planPalletId || '').trim())) {
ElMessage.warning('所选托盘来源标识缺失,请重新加载核对;原草稿已保留')
return
}
if (assigned.value.some((item) => !String(item.overseasWarehouseId || '').trim())) {
ElMessage.warning('所选托盘中存在尚未关联海外仓的托盘')
return
@@ -326,14 +349,39 @@ async function submit() {
ElMessage.warning('一张换标单的托盘需要单独排车')
return
}
if (relabelOrders.length && selectedContainers.length !== 1) {
ElMessage.warning('一张换标单仅支持一组排车,请将全部托盘移入同一个装车容器')
return
}
saving.value = true
mutationError.value = null
try {
await preArrangeDispatch({
planPalletIds: assigned.value.filter(item => item.sourceType !== 'RETURN_RECEIPT').map(palletId),
returnReceiptIds: assigned.value.filter(item => item.sourceType === 'RETURN_RECEIPT').map(item => String(item.returnReceiptId)),
...(relabelOrders.length ? { relabelNo: relabelOrders[0] } : { draftToken: ensureDraftToken() }),
})
if (relabelOrders.length) {
await preArrangeDispatch({
planPalletIds: assigned.value.filter(item => item.sourceType !== 'RETURN_RECEIPT').map(palletId),
returnReceiptIds: assigned.value.filter(item => item.sourceType === 'RETURN_RECEIPT').map(item => String(item.returnReceiptId)),
relabelNo: relabelOrders[0],
})
} else {
const groups = selectedContainers.map(container => {
const sources = container.palletIds.map(id => palletSnapshot(id)!)
return {
draftContainerId: container.id,
planPalletIds: sources.filter(item => item.sourceType !== 'RETURN_RECEIPT').map(item => String(item.planPalletId)),
returnReceiptIds: sources.filter(item => item.sourceType === 'RETURN_RECEIPT').map(item => String(item.returnReceiptId)),
}
})
const results = await preArrangeDraftDispatch({ draftToken: ensureDraftToken(), groups })
const expectedIds = new Set(groups.map(group => group.draftContainerId))
if (!Array.isArray(results) || results.length !== groups.length
|| results.some(result => !result || typeof result.draftContainerId !== 'string'
|| typeof result.dispatch?.dispatchNo !== 'string' || !result.dispatch.dispatchNo.trim())
|| new Set(results.map(result => result.draftContainerId)).size !== groups.length
|| results.some(result => !expectedIds.has(result.draftContainerId))
|| new Set(results.map(result => result.dispatch.dispatchNo)).size !== groups.length) {
throw new Error('预排车返回的分组结果与原草稿不一致,原草稿已保留,请核对保存结果')
}
}
emit('update:modelValue', false)
emit('created')
ElMessage.success('排车明细已同步到预排列表')
@@ -541,14 +589,14 @@ watch([warehouseTab, () => query.orderType], () => {
><span
>托数 <b>{{ container.palletIds.length }}</b
>/{{ container.capacity }} · 重量 <b>{{ formatWeightLb(containerWeightLb(container)) }}</b>/{{ container.maxWeightLb }}LB <em v-if="containerOverweight(container)">超限10%</em></span
><el-button class="density-action" data-tone="danger" :aria-label="`删除装车车型${index + 1}`" @click="deleteContainer(container)">删除</el-button>
><el-button class="density-action" data-tone="danger" :disabled="saving" :aria-label="`删除装车车型${index + 1}`" @click="deleteContainer(container)">删除</el-button>
</div>
<div class="container-body">
<div v-for="id in container.palletIds" :key="id" class="loaded-pallet">
<span
><strong>{{ text(palletSnapshot(id)?.palletNo || '托盘号待补充') }}</strong
><OrderReferenceText :value="text(palletSnapshot(id)?.orderNo)" /></span
><el-button class="density-action" data-tone="danger" :loading="busyPalletIds.includes(id)" @click="removePallet(id)">移除</el-button>
><el-button class="density-action" data-tone="danger" :disabled="saving" :loading="busyPalletIds.includes(id)" @click="removePallet(id)">移除</el-button>
</div>
<span v-if="!container.palletIds.length" class="drop-hint">拖入托盘,移入后立即锁定</span>
</div>
@@ -560,7 +608,7 @@ watch([warehouseTab, () => query.orderType], () => {
<template #footer><footer class="dialog-footer">
<span>托盘移入即锁定,重量按 1kg = 2.2046lb 换算。</span>
<div><el-button :disabled="saving" @click="close(false)">取消</el-button><el-button type="primary" :loading="saving" :disabled="!assignedIds.length" @click="submit">预排车</el-button></div>
<div><el-button :disabled="saving" @click="close(false)">取消</el-button><el-button type="primary" :loading="saving" :disabled="!assignedIds.length || busyPalletIds.length > 0" @click="submit">预排车</el-button></div>
</footer></template>
</AppDialog>
<el-dialog class="admin-density-dialog" v-model="vehicleDialogOpen" title="选择车型" width="360px" append-to-body>