feat(admin): allow late container number entry
This commit is contained in:
@@ -41,10 +41,10 @@ describe('TruckAppointmentDetailView status contract', () => {
|
||||
expect(source).toContain("error.value = getApiErrorDetails(err, '保存失败,请稍后重试')")
|
||||
})
|
||||
|
||||
it('blocks dispatch until the released booking, allocated real container and frozen bill number are present', () => {
|
||||
it('blocks dispatch until the released booking, allocated internal container and frozen documents are present', () => {
|
||||
expect(source).toContain("return '确认派车前必须关联已放舱订舱'")
|
||||
expect(source).toContain("return '确认派车前必须关联该订舱下的柜次'")
|
||||
expect(source).toContain("return '确认派车前关联柜次必须录入真实 ISO 集装箱号'")
|
||||
expect(source).not.toContain("return '确认派车前关联柜次必须录入真实 ISO 集装箱号'")
|
||||
expect(source).toContain("return '确认派车前所选订舱必须填写提单号'")
|
||||
expect(source).toContain("return '确认派车前所选订舱必须上传有效的订舱确认文件或提单文件'")
|
||||
expect(source).toContain('getOceanBooking(resource.value.oceanBookingId)')
|
||||
@@ -52,6 +52,16 @@ describe('TruckAppointmentDetailView status contract', () => {
|
||||
expect(source).not.toContain("item.attachmentType === 'BILL_OF_LADING'")
|
||||
})
|
||||
|
||||
it('collects a real ISO container number only when dispatched work starts pickup', () => {
|
||||
expect(source).toContain("resource.value?.status === 'DISPATCHED' && status === 'PICKUP_IN_PROGRESS'")
|
||||
expect(source).toContain("ElMessageBox.prompt('提柜后可填写,也可稍后在预装补录。'")
|
||||
expect(source).toContain('inputValue: normalizeIso6346ContainerNumber(resource.value.containerNo)')
|
||||
expect(source).toContain("inputValidator: (value) => !normalizeIso6346ContainerNumber(value) || isIso6346ContainerNumber(value) || '请输入有效的 ISO 6346 集装箱号'")
|
||||
expect(source).toContain('return { containerNo: normalizeIso6346ContainerNumber(value) || null }')
|
||||
expect(source).toContain('performTruckAppointmentAction(id.value, action, { expectedVersion: form.expectedVersion || 0, ...actionPayload })')
|
||||
expect(source).toContain('await ElMessageBox.confirm(`确认将状态改为“${truckAppointmentStatusLabels[status]}”?`')
|
||||
})
|
||||
|
||||
it('keeps the main detail available when attachments or history fail', () => {
|
||||
expect(source).toContain('Promise.allSettled([')
|
||||
expect(source).toContain("attachments.status === 'fulfilled'")
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -129,6 +129,7 @@ describe('TruckAppointmentsView', () => {
|
||||
expect(source.indexOf("key: 'payableAmount'")).toBeLessThan(source.indexOf("key: 'plannedLoadingAt'"))
|
||||
expect(source).not.toContain('receivableAmount')
|
||||
expect(source).not.toContain('应收拖车费')
|
||||
expect(source).toContain("row.containerNo || '待补录'")
|
||||
expect(source).toContain("toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })")
|
||||
expect(source).toContain("key: 'attachmentCount'")
|
||||
})
|
||||
|
||||
@@ -29,7 +29,7 @@ const columns: DataTableColumn[] = [
|
||||
{ key: 'appointmentNo', prop: 'appointmentNo', label: '派车单号', minWidth: 170 },
|
||||
{ key: 'oceanBookingNo', prop: 'oceanBookingNo', label: '订舱号', minWidth: 150, formatter: (row: TruckAppointmentResource) => row.oceanBookingNo || '—' },
|
||||
{ key: 'cabinetNo', prop: 'cabinetNo', label: '柜次号', minWidth: 150, formatter: (row: TruckAppointmentResource) => row.cabinetNo || '—' },
|
||||
{ key: 'containerNo', prop: 'containerNo', label: '集装箱号', minWidth: 145, formatter: (row: TruckAppointmentResource) => row.containerNo || '—' },
|
||||
{ key: 'containerNo', prop: 'containerNo', label: '集装箱号', minWidth: 145, formatter: (row: TruckAppointmentResource) => row.containerNo || '待补录' },
|
||||
{ key: 'warehouseName', prop: 'warehouseName', label: '装柜仓库', minWidth: 150, formatter: (row: TruckAppointmentResource) => row.warehouseName || '—' },
|
||||
{ key: 'shippingCompanyName', prop: 'shippingCompanyName', label: '船公司', minWidth: 150, formatter: (row: TruckAppointmentResource) => row.shippingCompanyName || '—' },
|
||||
{ key: 'bookingAgentName', prop: 'bookingAgentName', label: '订舱代理', minWidth: 150, formatter: (row: TruckAppointmentResource) => row.bookingAgentName || '—' },
|
||||
|
||||
Reference in New Issue
Block a user