feat(admin): allow late container number entry

This commit is contained in:
2026-09-01 15:24:37 +08:00
parent b4c29e2226
commit 0b676ddb74
23 changed files with 155 additions and 71 deletions
@@ -14,7 +14,7 @@ import { getTruckAppointment, listTruckAppointmentAttachments, listTruckAppointm
import TruckAppointmentForm from '../components/TruckAppointmentForm.vue'
import TruckAttachmentDialog from '../components/TruckAttachmentDialog.vue'
import { TRUCK_APPOINTMENT_SAVE_FIELDS, normalizeTruckAppointmentCurrency, truckAppointmentActionLabel, truckAppointmentStatusLabel, truckAppointmentStatusLabels, type SaveTruckAppointmentRequest, type TruckAppointmentResource, type TruckAppointmentStatus, type TruckAppointmentStatusHistory } from '../types'
import { isIso6346ContainerNumber } from '../iso6346'
import { isIso6346ContainerNumber, normalizeIso6346ContainerNumber } from '../iso6346'
const route = useRoute(); const router = useRouter(); const loading = ref(false); const saving = ref(false); const statusSaving = ref(false); const attachmentOpen = ref(false); const resource = ref<TruckAppointmentResource | null>(null); const error = ref<ApiErrorDetails | null>(null); const form = reactive<SaveTruckAppointmentRequest>({ expectedVersion: 0, carrierId: null, oceanBookingId: null, containerBookingId: null, plannedLoadingAt: null })
const history = ref<TruckAppointmentStatusHistory[]>([])
const bookingReadiness = ref<OceanBookingDetail | null>(null)
@@ -51,15 +51,27 @@ const dispatchBlockReason = computed(() => {
if (!form.oceanBookingId) return '确认派车前必须关联已放舱订舱'
if (bookingReadiness.value && bookingReadiness.value.status !== 'RELEASED') return '确认派车前必须关联已放舱订舱'
if (!form.containerBookingId) return '确认派车前必须关联该订舱下的柜次'
if (!isIso6346ContainerNumber(resource.value.containerNo)) return '确认派车前关联柜次必须录入真实 ISO 集装箱号'
if (!form.billOfLadingNo?.trim()) return '确认派车前所选订舱必须填写提单号'
if (bookingReadiness.value && !bookingReadiness.value.documents.some((document) => document.valid && ['BOOKING_CONFIRMATION', 'BILL_OF_LADING'].includes(document.documentType))) return '确认派车前所选订舱必须上传有效的订舱确认文件或提单文件'
return ''
})
async function confirmStatusChange(status: TruckAppointmentStatus) {
if (resource.value?.status === 'DISPATCHED' && status === 'PICKUP_IN_PROGRESS') {
const { value } = await ElMessageBox.prompt('提柜后可填写,也可稍后在预装补录。', '开始提柜', {
confirmButtonText: '确认提柜', cancelButtonText: '取消', type: 'warning',
inputValue: normalizeIso6346ContainerNumber(resource.value.containerNo),
inputPlaceholder: '选填,例如 MSCU6639870',
inputValidator: (value) => !normalizeIso6346ContainerNumber(value) || isIso6346ContainerNumber(value) || '请输入有效的 ISO 6346 集装箱号',
})
return { containerNo: normalizeIso6346ContainerNumber(value) || null }
}
await ElMessageBox.confirm(`确认将状态改为“${truckAppointmentStatusLabels[status]}”?`, '状态变更确认', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' })
return {}
}
async function changeStatus(status: TruckAppointmentStatus) {
if (!resource.value || status === resource.value.status || statusSaving.value) return
if (status === 'DISPATCHED' && dispatchBlockReason.value) { error.value = { message: dispatchBlockReason.value }; return }
try { await ElMessageBox.confirm(`确认将状态改为“${truckAppointmentStatusLabels[status]}”?`, '状态变更确认', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning' }); statusSaving.value = true; if (status === 'RESCHEDULED') { await rescheduleTruckAppointment(id.value, form.plannedLoadingAt || '', form.expectedVersion || 0, '后台改约') } else { const action = actionByTarget[status]; if (!action) throw new Error('当前状态没有可用动作'); await performTruckAppointmentAction(id.value, action, form.expectedVersion || 0) } ElMessage.success('状态已更新'); await load() } catch (err) { if (err !== 'cancel' && !(err instanceof Error && err.message === 'cancel')) error.value = getApiErrorDetails(err, '状态更新失败,请稍后重试') } finally { statusSaving.value = false }
try { const actionPayload = await confirmStatusChange(status); statusSaving.value = true; if (status === 'RESCHEDULED') { await rescheduleTruckAppointment(id.value, form.plannedLoadingAt || '', form.expectedVersion || 0, '后台改约') } else { const action = actionByTarget[status]; if (!action) throw new Error('当前状态没有可用动作'); await performTruckAppointmentAction(id.value, action, { expectedVersion: form.expectedVersion || 0, ...actionPayload }) } ElMessage.success('状态已更新'); await load() } catch (err) { if (err !== 'cancel' && !(err instanceof Error && err.message === 'cancel')) error.value = getApiErrorDetails(err, '状态更新失败,请稍后重试') } finally { statusSaving.value = false }
}
onMounted(load)
onActivated(() => {