Release/test 合并上生产 #2
@@ -3,16 +3,17 @@ import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ApiResponseError } from '@/shared/api/http'
|
||||
import TruckAppointmentForm from './TruckAppointmentForm.vue'
|
||||
import type { SaveTruckAppointmentRequest } from '../types'
|
||||
import { getOceanBooking, listOceanBookings } from '@/modules/ocean-bookings'
|
||||
import type { SaveTruckAppointmentRequest, TruckAppointmentResource } from '../types'
|
||||
import { getOceanBooking } from '@/modules/ocean-bookings'
|
||||
import { listTruckAppointmentCandidates } from '../api/platformTrucking'
|
||||
|
||||
vi.mock('@/modules/suppliers', () => ({
|
||||
listSuppliers: vi.fn().mockResolvedValue({ records: [{ id: 'carrier-1', name: '平台拖车公司', category: 'TRUCKING', status: 'ACTIVE' }] }),
|
||||
}))
|
||||
vi.mock('@/modules/ocean-bookings', () => ({
|
||||
listOceanBookings: vi.fn(),
|
||||
getOceanBooking: vi.fn(),
|
||||
}))
|
||||
vi.mock('../api/platformTrucking', () => ({ listTruckAppointmentCandidates: vi.fn() }))
|
||||
|
||||
const model: SaveTruckAppointmentRequest = {
|
||||
expectedVersion: 0,
|
||||
@@ -22,29 +23,28 @@ const model: SaveTruckAppointmentRequest = {
|
||||
carrierId: null,
|
||||
plannedLoadingAt: null,
|
||||
}
|
||||
const bookings = [
|
||||
{ id: 'booking-1', bookingNo: 'OB-1', status: 'RELEASED', billOfLadingNo: 'BL-1', warehouseId: 'WH-1', warehouseName: '义乌仓' },
|
||||
{ id: 'booking-2', bookingNo: 'OB-2', status: 'RELEASED', billOfLadingNo: 'BL-2', warehouseId: 'WH-2', warehouseName: '东莞仓' },
|
||||
{ id: 'booking-missing-bill', bookingNo: 'OB-3', status: 'RELEASED', billOfLadingNo: null, warehouseId: 'WH-3', warehouseName: '宁波仓' },
|
||||
const candidates = [
|
||||
{ oceanBookingId: 'booking-1', oceanBookingNo: 'OB-1', billOfLadingNo: 'BL-1', warehouseId: 'WH-1', warehouseName: '义乌仓', channelCode: 'SEA', channelName: '海运', requirementId: 'requirement-1', containerType: '40HC', requirementQuantity: 2, containerBookingId: 'container-1', cabinetNo: 'CAB-1', containerNo: 'CBHU8888883', loadingPlanId: 'plan-1', loadingPlanNo: 'LP-1', loadingPlanStatus: 'EDITING', bookingDocumentReady: true },
|
||||
{ oceanBookingId: 'booking-1', oceanBookingNo: 'OB-1', billOfLadingNo: 'BL-1', warehouseId: 'WH-1', warehouseName: '义乌仓', channelCode: 'SEA', channelName: '海运', requirementId: 'requirement-1', containerType: '40HC', requirementQuantity: 2, containerBookingId: 'container-pending', cabinetNo: 'CAB-X', containerNo: null, loadingPlanId: null, loadingPlanNo: null, loadingPlanStatus: null, bookingDocumentReady: true },
|
||||
{ oceanBookingId: 'booking-2', oceanBookingNo: 'OB-2', billOfLadingNo: 'BL-2', warehouseId: 'WH-2', warehouseName: '东莞仓', channelCode: 'SEA', channelName: '海运', requirementId: 'requirement-2', containerType: '40GP', requirementQuantity: 1, containerBookingId: 'container-2', cabinetNo: 'CAB-2', containerNo: 'MSCU6639870', loadingPlanId: null, loadingPlanNo: null, loadingPlanStatus: null, bookingDocumentReady: true },
|
||||
]
|
||||
function detail(id: string) {
|
||||
const booking = bookings.find((item) => item.id === id)!
|
||||
const candidate = candidates.find((item) => item.oceanBookingId === id)!
|
||||
return {
|
||||
...booking,
|
||||
id, bookingNo: candidate.oceanBookingNo, status: 'RELEASED', billOfLadingNo: candidate.billOfLadingNo, warehouseId: candidate.warehouseId, warehouseName: candidate.warehouseName,
|
||||
documents: [{ id: `doc-${id}`, documentType: 'BILL_OF_LADING', valid: true }],
|
||||
containers: id === 'booking-1'
|
||||
? [
|
||||
{ containerBookingId: 'container-1', cabinetNo: 'CAB-1', containerNo: 'CBHU8888883' },
|
||||
{ containerBookingId: 'container-pending', cabinetNo: 'CAB-X', containerNo: null, containerType: '40HC' },
|
||||
]
|
||||
: [{ containerBookingId: 'container-2', cabinetNo: 'CAB-2', containerNo: 'MSCU6639870' }],
|
||||
containers: [],
|
||||
}
|
||||
}
|
||||
function resource(id: string, bookingId: string, containerId: string): TruckAppointmentResource {
|
||||
const candidate = candidates.find((item) => item.oceanBookingId === bookingId && item.containerBookingId === containerId)!
|
||||
return { id, status: 'DRAFT', oceanBookingId: bookingId, oceanBookingNo: candidate.oceanBookingNo, containerBookingId: containerId, cabinetNo: candidate.cabinetNo, containerNo: candidate.containerNo, relatedContainerType: candidate.containerType }
|
||||
}
|
||||
|
||||
describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(listOceanBookings).mockResolvedValue({ records: bookings } as never)
|
||||
vi.mocked(listTruckAppointmentCandidates).mockResolvedValue({ records: candidates, page: 1, size: 200, total: candidates.length, pages: 1 })
|
||||
vi.mocked(getOceanBooking).mockImplementation(async (id) => detail(id) as never)
|
||||
})
|
||||
|
||||
@@ -55,7 +55,8 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
selects[0]!.vm.$emit('update:modelValue', 'booking-1')
|
||||
await flushPromises()
|
||||
|
||||
expect(getOceanBooking).toHaveBeenCalledWith('booking-1')
|
||||
expect(listTruckAppointmentCandidates).toHaveBeenCalledWith({ page: 1, size: 200 })
|
||||
expect(getOceanBooking).not.toHaveBeenCalled()
|
||||
const labels = wrapper.findAllComponents({ name: 'ElOption' }).map((option) => String(option.props('label') || ''))
|
||||
expect(labels.some((label) => label.includes('CAB-1') && label.includes('CBHU8888883'))).toBe(true)
|
||||
expect(labels.some((label) => label.includes('CAB-X') && label.includes('40HC') && label.includes('待补录'))).toBe(true)
|
||||
@@ -87,19 +88,18 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('ignores stale booking details when the user switches selections quickly', async () => {
|
||||
it('ignores stale current-booking details when a cached edit route changes', async () => {
|
||||
let resolveFirst!: (value: ReturnType<typeof detail>) => void
|
||||
let resolveSecond!: (value: ReturnType<typeof detail>) => void
|
||||
vi.mocked(getOceanBooking).mockImplementation((id) => new Promise((resolve) => {
|
||||
if (id === 'booking-1') resolveFirst = resolve as typeof resolveFirst
|
||||
else resolveSecond = resolve as typeof resolveSecond
|
||||
}) as never)
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model } }, global: { plugins: [ElementPlus] } })
|
||||
const firstResource = resource('appointment-1', 'booking-1', 'container-1')
|
||||
const secondResource = resource('appointment-2', 'booking-2', 'container-2')
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model, oceanBookingId: 'booking-1', containerBookingId: 'container-1' }, resource: firstResource }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
const bookingSelect = wrapper.findAllComponents({ name: 'ElSelect' })[0]!
|
||||
bookingSelect.vm.$emit('update:modelValue', 'booking-1')
|
||||
await flushPromises()
|
||||
bookingSelect.vm.$emit('update:modelValue', 'booking-2')
|
||||
await wrapper.setProps({ modelValue: { ...model, oceanBookingId: 'booking-2', containerBookingId: 'container-2' }, resource: secondResource })
|
||||
await flushPromises()
|
||||
|
||||
resolveSecond(detail('booking-2'))
|
||||
@@ -108,20 +108,17 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
await flushPromises()
|
||||
|
||||
const labels = wrapper.findAllComponents({ name: 'ElOption' }).map((option) => String(option.props('label') || ''))
|
||||
expect(labels.some((label) => label.includes('CAB-2'))).toBe(true)
|
||||
expect(labels.some((label) => label.includes('CAB-2') && label.includes('不可重选'))).toBe(true)
|
||||
expect(labels.some((label) => label.includes('CAB-1'))).toBe(false)
|
||||
expect(wrapper.text()).not.toContain('所选订舱缺少有效的订舱确认文件或提单文件')
|
||||
const emitted = wrapper.emitted('update:modelValue')?.at(-1)?.[0] as SaveTruckAppointmentRequest
|
||||
expect(emitted.oceanBookingId).toBe('booking-2')
|
||||
expect(emitted.billOfLadingNo).toBe('BL-2')
|
||||
expect(listTruckAppointmentCandidates).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('preserves the business code and request trace when booking readiness fails', async () => {
|
||||
vi.mocked(getOceanBooking).mockRejectedValueOnce(new ApiResponseError('订舱版本冲突', 'A0409', 409, undefined, 'req-booking', 'trace-booking'))
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model } }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
wrapper.findAllComponents({ name: 'ElSelect' })[0]!.vm.$emit('update:modelValue', 'booking-1')
|
||||
const current = resource('appointment-1', 'booking-1', 'container-1')
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model, oceanBookingId: 'booking-1', containerBookingId: 'container-1' }, resource: current }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('订舱版本冲突')
|
||||
expect(wrapper.text()).toContain('A0409')
|
||||
@@ -130,26 +127,13 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('blocks a released booking that has no bill of lading number', async () => {
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model } }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
wrapper.findAllComponents({ name: 'ElSelect' })[0]!.vm.$emit('update:modelValue', 'booking-missing-bill')
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('所选订舱缺少有效提单号')
|
||||
const emitted = wrapper.emitted('update:modelValue')?.at(-1)?.[0] as SaveTruckAppointmentRequest
|
||||
expect(emitted.billOfLadingNo).toBeNull()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('rejects a booking that becomes unreleased before its detail is loaded', async () => {
|
||||
it('flags an edit binding whose booking has become unreleased without adding it to create candidates', async () => {
|
||||
vi.mocked(getOceanBooking).mockResolvedValueOnce({ ...detail('booking-1'), status: 'DRAFT' } as never)
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model } }, global: { plugins: [ElementPlus] } })
|
||||
const current = resource('appointment-1', 'booking-1', 'container-1')
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model, oceanBookingId: 'booking-1', containerBookingId: 'container-1' }, resource: current }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
wrapper.findAllComponents({ name: 'ElSelect' })[0]!.vm.$emit('update:modelValue', 'booking-1')
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('所选订舱尚未放舱')
|
||||
const emitted = wrapper.emitted('update:modelValue')?.at(-1)?.[0] as SaveTruckAppointmentRequest
|
||||
expect(emitted.containerBookingId).toBeNull()
|
||||
expect(wrapper.text()).toContain('当前绑定订舱已不是放舱状态')
|
||||
expect(listTruckAppointmentCandidates).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
|
||||
@@ -7,21 +7,21 @@ const typesSource = readFileSync(resolve(process.cwd(), 'src/modules/trucking/ty
|
||||
|
||||
describe('TruckAppointmentForm interaction contract', () => {
|
||||
it('requires a released booking before an allocated internal container', () => {
|
||||
expect(source).toContain("listOceanBookings({ page: 1, size: 200, status: 'RELEASED' })")
|
||||
expect(source).toContain('listTruckAppointmentCandidates({ page: 1, size: 200 })')
|
||||
expect(source).toContain("oceanBookingId: [{ required: true, message: '请选择已放舱订舱'")
|
||||
expect(source).toContain("containerBookingId: [{ required: true, message: '请选择该订舱下已分配的柜次'")
|
||||
expect(source).toContain("containerBookingId: [{ required: true, message: '请选择该订舱下可预约的柜次'")
|
||||
expect(source.indexOf('label="订舱"')).toBeLessThan(source.indexOf('label="柜次"'))
|
||||
expect(source).toContain(':disabled="!draft.oceanBookingId || bookingContainersLoading"')
|
||||
expect(source).toContain(':disabled="relationLocked || !draft.oceanBookingId || optionsLoading || bookingContainersLoading"')
|
||||
expect(source).not.toContain('关联订舱(可选)')
|
||||
expect(source).not.toContain('关联柜次(可选)')
|
||||
})
|
||||
|
||||
it('loads every container allocated to the selected booking without requiring a physical number', () => {
|
||||
expect(source).toContain('getOceanBooking(bookingId)')
|
||||
expect(source).toContain('containers.value = booking.containers || []')
|
||||
expect(source).toContain('bookingDocuments.value = booking.documents || []')
|
||||
expect(source).toContain('const selectableContainers = computed(() => containers.value)')
|
||||
expect(source).toContain('该订舱暂无已分配柜次')
|
||||
it('uses only server-owned create candidates and never rebuilds availability from booking detail', () => {
|
||||
expect(source).toContain('candidates.value = results[1].value.records')
|
||||
expect(source).toContain('candidates.value.filter((item) => item.oceanBookingId === draft.oceanBookingId)')
|
||||
expect(source).toContain('该订舱暂无可预约柜次')
|
||||
expect(source).not.toContain('booking.containers')
|
||||
expect(source).not.toContain('listOceanBookings')
|
||||
expect(source).not.toContain('isIso6346ContainerNumber')
|
||||
expect(source).not.toContain('listContainers')
|
||||
})
|
||||
@@ -29,7 +29,8 @@ describe('TruckAppointmentForm interaction contract', () => {
|
||||
it('clears an incompatible container whenever the booking changes', () => {
|
||||
expect(source).toContain('draft.containerBookingId = null')
|
||||
expect(source).toContain('draft.loadingPlanId = null')
|
||||
expect(source).toContain('if (!selectableContainers.value.some((item) => item.containerBookingId === draft.containerBookingId))')
|
||||
expect(source).toContain('draft.containerBookingId = null')
|
||||
expect(source).toContain('candidate.loadingPlanId')
|
||||
})
|
||||
|
||||
it('freezes warehouse and bill of lading snapshots without retaining customer data', () => {
|
||||
@@ -48,13 +49,29 @@ describe('TruckAppointmentForm interaction contract', () => {
|
||||
expect(source).toContain('所选订舱缺少有效提单号')
|
||||
expect(source).toContain("booking.status !== 'RELEASED'")
|
||||
expect(source).toContain('bookingContainersError')
|
||||
expect(source).toContain("getApiErrorDetails(cause, '该订舱柜次加载失败,请重试')")
|
||||
expect(source).toContain("getApiErrorDetails(cause, '当前绑定订舱资料加载失败,请重试')")
|
||||
expect(source).toContain('<ApiErrorMeta :details="bookingContainersError" />')
|
||||
expect(source).toContain('重新加载选项')
|
||||
expect(source).toContain('重新加载')
|
||||
expect(source).toContain('请选择有效的合作中拖车供应商')
|
||||
})
|
||||
|
||||
it('freezes the current edit relation and marks its fallback options as non-selectable', () => {
|
||||
expect(source).toContain('const relationLocked = computed(() => Boolean(props.resource))')
|
||||
expect(source).toContain('(当前绑定,不可重选)')
|
||||
expect(source).toContain(':disabled="relationLocked"')
|
||||
expect(source).toContain('<el-option v-if="selectedContainerFallback"')
|
||||
expect(source).toContain(':value="selectedContainerFallback.id" disabled')
|
||||
})
|
||||
|
||||
it('guards current booking detail loads against stale route reuse responses', () => {
|
||||
expect(source).toContain('const sequence = ++bookingLoadSequence')
|
||||
expect(source).toContain("sequence !== bookingLoadSequence || String(draft.oceanBookingId || '') !== bookingId")
|
||||
expect(source).toContain('const sequence = ++optionsLoadSequence')
|
||||
expect(source).toContain('if (sequence !== optionsLoadSequence) return')
|
||||
expect(source).toContain("watch(() => props.resource?.id")
|
||||
})
|
||||
|
||||
it('labels the internal cabinet, type and pickup-time physical number separately', () => {
|
||||
expect(source).toContain('柜次号:${item.cabinetNo')
|
||||
expect(source).toContain('柜型:${item.containerType')
|
||||
|
||||
@@ -50,11 +50,12 @@ const candidates = ref<TruckAppointmentCandidate[]>([])
|
||||
const optionsLoading = ref(false)
|
||||
const optionsError = ref('')
|
||||
let bookingLoadSequence = 0
|
||||
let optionsLoadSequence = 0
|
||||
const selectableCarriers = computed(() => carriers.value.filter((item) => (
|
||||
item.status === 'ACTIVE'
|
||||
)))
|
||||
const bookings = computed(() => {
|
||||
const result = new Map<string, { id: string; bookingNo: string; billOfLadingNo: string; warehouseId: string; warehouseName: string | null; bookingDocumentReady: boolean }>()
|
||||
const result = new Map<string, { id: string; bookingNo: string; billOfLadingNo: string | null; warehouseId: string; warehouseName: string | null; bookingDocumentReady: boolean }>()
|
||||
for (const item of candidates.value) if (!result.has(item.oceanBookingId)) result.set(item.oceanBookingId, {
|
||||
id: item.oceanBookingId,
|
||||
bookingNo: item.oceanBookingNo,
|
||||
@@ -139,12 +140,14 @@ async function selectBooking(bookingId: string) {
|
||||
draft.billOfLadingNo = item.billOfLadingNo
|
||||
}
|
||||
async function loadOptions() {
|
||||
const sequence = ++optionsLoadSequence
|
||||
optionsLoading.value = true; optionsError.value = ''
|
||||
if (relationLocked.value) candidates.value = []
|
||||
candidates.value = []
|
||||
const results = await Promise.allSettled([
|
||||
listSuppliers({ page: 1, size: 200, category: 'TRUCKING', status: 'ACTIVE' }),
|
||||
relationLocked.value ? Promise.resolve({ records: [] as TruckAppointmentCandidate[] }) : listTruckAppointmentCandidates({ page: 1, size: 200 }),
|
||||
])
|
||||
if (sequence !== optionsLoadSequence) return
|
||||
if (results[0].status === 'fulfilled') carriers.value = results[0].value.records.filter((item) => item.category === 'TRUCKING' && item.status === 'ACTIVE')
|
||||
if (results[1].status === 'fulfilled') candidates.value = results[1].value.records
|
||||
const failed = ['承运商', '可预约订舱柜次'].filter((_, index) => results[index]?.status === 'rejected')
|
||||
@@ -179,7 +182,7 @@ watch(() => props.resource?.id, (id, previous) => { if (id && id !== previous) v
|
||||
<el-alert v-if="optionsError" type="warning" :closable="false" show-icon :title="optionsError" class="relation-alert"><el-button link type="warning" :loading="optionsLoading" @click="loadOptions">重新加载选项</el-button></el-alert>
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24"><el-divider content-position="left">订舱与柜次(必填)</el-divider></el-col>
|
||||
<el-col class="relation-column" :xs="24" :sm="12"><el-form-item label="订舱" prop="oceanBookingId"><el-select :model-value="draft.oceanBookingId" filterable :disabled="relationLocked" placeholder="请选择仍有可预约柜次的已放舱订舱" :loading="optionsLoading" no-data-text="暂无仍有可预约柜次的已放舱订舱" style="width:100%" @update:model-value="selectBooking(String($event || ''))"><el-option v-if="selectedBookingFallback" :key="selectedBookingFallback.id" :label="selectedBookingFallback.label" :value="selectedBookingFallback.id" disabled /><el-option v-for="item in bookings" :key="item.id" :label="`${item.bookingNo || '订舱号缺失'} / 提单号:${item.billOfLadingNo}`" :value="item.id" /></el-select></el-form-item></el-col>
|
||||
<el-col class="relation-column" :xs="24" :sm="12"><el-form-item label="订舱" prop="oceanBookingId"><el-select :model-value="draft.oceanBookingId" filterable :disabled="relationLocked" placeholder="请选择仍有可预约柜次的已放舱订舱" :loading="optionsLoading" no-data-text="暂无仍有可预约柜次的已放舱订舱" style="width:100%" @update:model-value="selectBooking(String($event || ''))"><el-option v-if="selectedBookingFallback" :key="selectedBookingFallback.id" :label="selectedBookingFallback.label" :value="selectedBookingFallback.id" disabled /><el-option v-for="item in bookings" :key="item.id" :label="`${item.bookingNo || '订舱号缺失'} / 提单号:${item.billOfLadingNo || '缺失'}`" :value="item.id" /></el-select></el-form-item></el-col>
|
||||
<el-col class="relation-column" :xs="24" :sm="12"><el-form-item label="柜次" prop="containerBookingId"><el-select :model-value="draft.containerBookingId" filterable :disabled="relationLocked || !draft.oceanBookingId || optionsLoading || bookingContainersLoading" placeholder="请先选择订舱,再选择可预约柜次" :loading="optionsLoading || bookingContainersLoading" :no-data-text="draft.oceanBookingId ? '该订舱暂无可预约柜次' : '请先选择订舱'" style="width:100%" @update:model-value="selectContainer(String($event || ''))"><el-option v-if="selectedContainerFallback" :key="selectedContainerFallback.id" :label="selectedContainerFallback.label" :value="selectedContainerFallback.id" disabled /><el-option v-for="item in selectableContainers" :key="item.containerBookingId" :label="containerOptionLabel(item)" :value="item.containerBookingId" /></el-select><div v-if="bookingContainersError" class="field-error"><span>{{ bookingContainersError.message }}</span><ApiErrorMeta :details="bookingContainersError" /><el-button link type="primary" :loading="bookingContainersLoading" @click="draft.oceanBookingId && loadCurrentBookingContext(String(draft.oceanBookingId))">重新加载</el-button></div><div v-else-if="draft.oceanBookingId && !optionsLoading && !bookingContainersLoading && !selectableContainers.length && !relationLocked" class="field-hint">该订舱当前没有可创建拖车预约的柜次。</div></el-form-item></el-col>
|
||||
<el-col :xs="24"><el-divider content-position="left">基本信息</el-divider></el-col>
|
||||
<el-col v-if="draft.oceanBookingId && !bookingContainersLoading && !bookingContainersError && !draft.billOfLadingNo?.trim()" :xs="24"><el-alert title="所选订舱缺少有效提单号,暂不能创建或保存拖车预约" type="warning" :closable="false" show-icon /></el-col>
|
||||
|
||||
@@ -207,7 +207,7 @@ export interface TruckAppointmentCandidateQuery {
|
||||
export interface TruckAppointmentCandidate {
|
||||
oceanBookingId: string
|
||||
oceanBookingNo: string
|
||||
billOfLadingNo: string
|
||||
billOfLadingNo: string | null
|
||||
warehouseId: string
|
||||
warehouseName: string | null
|
||||
channelCode: string | null
|
||||
|
||||
Reference in New Issue
Block a user