refactor(admin): scope truck orders to bookings
This commit is contained in:
@@ -18,15 +18,12 @@ vi.mock('../api/platformTrucking', () => ({ listTruckAppointmentCandidates: vi.f
|
||||
const model: SaveTruckAppointmentRequest = {
|
||||
expectedVersion: 0,
|
||||
oceanBookingId: null,
|
||||
containerBookingId: null,
|
||||
loadingPlanId: null,
|
||||
carrierId: null,
|
||||
plannedLoadingAt: null,
|
||||
}
|
||||
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 },
|
||||
{ oceanBookingId: 'booking-1', oceanBookingNo: 'OB-1', billOfLadingNo: 'BL-1', warehouseId: 'WH-1', warehouseName: '义乌仓', channelCode: 'SEA', channelName: '海运', bookingDocumentReady: true },
|
||||
{ oceanBookingId: 'booking-2', oceanBookingNo: 'OB-2', billOfLadingNo: 'BL-2', warehouseId: 'WH-2', warehouseName: '东莞仓', channelCode: 'SEA', channelName: '海运', bookingDocumentReady: true },
|
||||
]
|
||||
function detail(id: string) {
|
||||
const candidate = candidates.find((item) => item.oceanBookingId === id)!
|
||||
@@ -36,9 +33,9 @@ function detail(id: string) {
|
||||
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 }
|
||||
function resource(id: string, bookingId: string): TruckAppointmentResource {
|
||||
const candidate = candidates.find((item) => item.oceanBookingId === bookingId)!
|
||||
return { id, status: 'DRAFT', oceanBookingId: bookingId, oceanBookingNo: candidate.oceanBookingNo, billOfLadingNo: candidate.billOfLadingNo, warehouseId: candidate.warehouseId, warehouseName: candidate.warehouseName }
|
||||
}
|
||||
|
||||
describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
@@ -48,7 +45,7 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
vi.mocked(getOceanBooking).mockImplementation(async (id) => detail(id) as never)
|
||||
})
|
||||
|
||||
it('selects a released booking, exposes pending-pickup internal containers, and never emits customer fields', async () => {
|
||||
it('selects only server-approved released bookings and never exposes container identity', async () => {
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model } }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
const selects = wrapper.findAllComponents({ name: 'ElSelect' })
|
||||
@@ -58,8 +55,8 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
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)
|
||||
expect(labels.some((label) => label.includes('OB-1') && label.includes('BL-1'))).toBe(true)
|
||||
expect(labels.some((label) => label.includes('CAB-'))).toBe(false)
|
||||
const emitted = wrapper.emitted('update:modelValue')?.at(-1)?.[0] as SaveTruckAppointmentRequest
|
||||
expect(emitted.oceanBookingId).toBe('booking-1')
|
||||
expect(emitted.billOfLadingNo).toBe('BL-1')
|
||||
@@ -68,23 +65,20 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('clears a selected container when switching to another booking', async () => {
|
||||
it('replaces frozen booking snapshots when switching the selected booking', async () => {
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model } }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
const selects = wrapper.findAllComponents({ name: 'ElSelect' })
|
||||
selects[0]!.vm.$emit('update:modelValue', 'booking-1')
|
||||
await flushPromises()
|
||||
selects[1]!.vm.$emit('update:modelValue', 'container-1')
|
||||
await flushPromises()
|
||||
expect((wrapper.emitted('update:modelValue')?.at(-1)?.[0] as SaveTruckAppointmentRequest).containerBookingId).toBe('container-1')
|
||||
|
||||
selects[0]!.vm.$emit('update:modelValue', 'booking-2')
|
||||
await flushPromises()
|
||||
const emitted = wrapper.emitted('update:modelValue')?.at(-1)?.[0] as SaveTruckAppointmentRequest
|
||||
expect(emitted.oceanBookingId).toBe('booking-2')
|
||||
expect(emitted.containerBookingId).toBeNull()
|
||||
expect(emitted.billOfLadingNo).toBe('BL-2')
|
||||
expect(emitted.warehouseId).toBe('WH-2')
|
||||
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('OB-2'))).toBe(true)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
@@ -95,11 +89,11 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
if (id === 'booking-1') resolveFirst = resolve as typeof resolveFirst
|
||||
else resolveSecond = resolve as typeof resolveSecond
|
||||
}) as never)
|
||||
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] } })
|
||||
const firstResource = resource('appointment-1', 'booking-1')
|
||||
const secondResource = resource('appointment-2', 'booking-2')
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model, oceanBookingId: 'booking-1' }, resource: firstResource }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
await wrapper.setProps({ modelValue: { ...model, oceanBookingId: 'booking-2', containerBookingId: 'container-2' }, resource: secondResource })
|
||||
await wrapper.setProps({ modelValue: { ...model, oceanBookingId: 'booking-2' }, resource: secondResource })
|
||||
await flushPromises()
|
||||
|
||||
resolveSecond(detail('booking-2'))
|
||||
@@ -108,8 +102,8 @@ 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') && label.includes('不可重选'))).toBe(true)
|
||||
expect(labels.some((label) => label.includes('CAB-1'))).toBe(false)
|
||||
expect(labels.some((label) => label.includes('OB-2') && label.includes('不可重选'))).toBe(true)
|
||||
expect(labels.some((label) => label.includes('OB-1'))).toBe(false)
|
||||
expect(wrapper.text()).not.toContain('所选订舱缺少有效的订舱确认文件或提单文件')
|
||||
expect(listTruckAppointmentCandidates).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
@@ -117,8 +111,8 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
|
||||
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 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] } })
|
||||
const current = resource('appointment-1', 'booking-1')
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model, oceanBookingId: 'booking-1' }, resource: current }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('订舱版本冲突')
|
||||
expect(wrapper.text()).toContain('A0409')
|
||||
@@ -129,8 +123,8 @@ describe('TruckAppointmentForm booking-only behavior', () => {
|
||||
|
||||
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 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] } })
|
||||
const current = resource('appointment-1', 'booking-1')
|
||||
const wrapper = mount(TruckAppointmentForm, { props: { modelValue: { ...model, oceanBookingId: 'booking-1' }, resource: current }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('当前绑定订舱已不是放舱状态')
|
||||
expect(listTruckAppointmentCandidates).not.toHaveBeenCalled()
|
||||
|
||||
@@ -6,31 +6,28 @@ const source = readFileSync(resolve(process.cwd(), 'src/modules/trucking/compone
|
||||
const typesSource = readFileSync(resolve(process.cwd(), 'src/modules/trucking/types.ts'), 'utf8')
|
||||
|
||||
describe('TruckAppointmentForm interaction contract', () => {
|
||||
it('requires a released booking before an allocated internal container', () => {
|
||||
it('requires one server-approved released booking without exposing a container selector', () => {
|
||||
expect(source).toContain('listTruckAppointmentCandidates({ page: 1, size: 200 })')
|
||||
expect(source).toContain("oceanBookingId: [{ required: true, message: '请选择已放舱订舱'")
|
||||
expect(source).toContain("containerBookingId: [{ required: true, message: '请选择该订舱下可预约的柜次'")
|
||||
expect(source.indexOf('label="订舱"')).toBeLessThan(source.indexOf('label="柜次"'))
|
||||
expect(source).toContain(':disabled="relationLocked || !draft.oceanBookingId || optionsLoading || bookingContainersLoading"')
|
||||
expect(source).not.toContain('prop="containerBookingId"')
|
||||
expect(source).not.toContain('label="柜次"')
|
||||
expect(source).not.toContain('关联订舱(可选)')
|
||||
expect(source).not.toContain('关联柜次(可选)')
|
||||
})
|
||||
|
||||
it('uses only server-owned create candidates and never rebuilds availability from booking detail', () => {
|
||||
it('uses only server-owned booking 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).toContain('new Map<string')
|
||||
expect(source).not.toContain('booking.containers')
|
||||
expect(source).not.toContain('listOceanBookings')
|
||||
expect(source).not.toContain('isIso6346ContainerNumber')
|
||||
expect(source).not.toContain('listContainers')
|
||||
})
|
||||
|
||||
it('clears an incompatible container whenever the booking changes', () => {
|
||||
expect(source).toContain('draft.containerBookingId = null')
|
||||
expect(source).toContain('draft.loadingPlanId = null')
|
||||
expect(source).toContain('draft.containerBookingId = null')
|
||||
expect(source).toContain('candidate.loadingPlanId')
|
||||
it('does not write legacy container or loading-plan fields', () => {
|
||||
expect(source).not.toContain('draft.containerBookingId')
|
||||
expect(source).not.toContain('draft.loadingPlanId')
|
||||
expect(typesSource).not.toContain("'containerBookingId', 'loadingPlanId'")
|
||||
})
|
||||
|
||||
it('freezes warehouse and bill of lading snapshots without retaining customer data', () => {
|
||||
@@ -56,12 +53,11 @@ describe('TruckAppointmentForm interaction contract', () => {
|
||||
expect(source).toContain('请选择有效的合作中拖车供应商')
|
||||
})
|
||||
|
||||
it('freezes the current edit relation and marks its fallback options as non-selectable', () => {
|
||||
it('freezes the current edit booking and marks its fallback option 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')
|
||||
expect(source).not.toContain('selectedContainerFallback')
|
||||
})
|
||||
|
||||
it('guards current booking detail loads against stale route reuse responses', () => {
|
||||
@@ -72,10 +68,10 @@ describe('TruckAppointmentForm interaction contract', () => {
|
||||
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')
|
||||
expect(source).toContain("集装箱号:${item.containerNo || '待补录'}")
|
||||
it('keeps container identity out of the booking-scoped form', () => {
|
||||
expect(source).not.toContain('柜次号:')
|
||||
expect(source).not.toContain('集装箱号:')
|
||||
expect(source).toContain('真实柜次及集装箱号在货柜与预装流程维护')
|
||||
})
|
||||
|
||||
it('keeps the form usable at 390px', () => {
|
||||
|
||||
@@ -38,7 +38,6 @@ const rules: FormRules = {
|
||||
if (!draft.billOfLadingNo?.trim()) return callback(new Error('所选订舱缺少有效提单号'))
|
||||
return callback(bookingDocumentReady.value ? undefined : new Error('所选订舱缺少有效的订舱确认文件或提单文件'))
|
||||
}, trigger: 'change' }],
|
||||
containerBookingId: [{ required: true, message: '请选择该订舱下可预约的柜次', trigger: 'change' }, { validator: (_rule, value, callback) => callback(selectableContainers.value.some((item) => item.containerBookingId === String(value)) || (relationLocked.value && selectedContainerFallback.value?.id === String(value)) ? undefined : new Error('请选择该订舱下仍可预约的柜次')), trigger: 'change' }],
|
||||
carrierId: [{ required: true, message: '请选择承运商', trigger: 'change' }, { validator: (_rule, value, callback) => callback(selectableCarriers.value.some((item) => String(item.id) === String(value)) ? undefined : new Error('请选择有效的合作中拖车供应商')), trigger: 'change' }],
|
||||
plannedLoadingAt: [{ required: true, message: '请选择预计装箱时间', trigger: 'change' }],
|
||||
fleetPhone: [phoneRule('车队电话')], driverPhone: [phoneRule('司机电话')], storePhone: [phoneRule('门店电话')],
|
||||
@@ -70,19 +69,6 @@ const selectedReleasedBooking = computed(() => bookings.value.find((item) => ite
|
||||
const bookingDocumentReady = computed(() => relationLocked.value
|
||||
? bookingDocuments.value.some((document) => document.valid && ['BOOKING_CONFIRMATION', 'BILL_OF_LADING'].includes(document.documentType))
|
||||
: Boolean(selectedReleasedBooking.value?.bookingDocumentReady))
|
||||
const selectableContainers = computed(() => candidates.value.filter((item) => item.oceanBookingId === draft.oceanBookingId))
|
||||
function containerOptionLabel(item: TruckAppointmentCandidate) {
|
||||
return `柜次号:${item.cabinetNo || '待生成'} / 柜型:${item.containerType || '—'} / 集装箱号:${item.containerNo || '待补录'}`
|
||||
}
|
||||
const selectedContainerFallback = computed(() => {
|
||||
const id = draft.containerBookingId
|
||||
const snapshot = props.resource
|
||||
if (!id || !relationLocked.value || String(snapshot?.containerBookingId) !== String(id)) return null
|
||||
return {
|
||||
id: String(id),
|
||||
label: `柜次号:${snapshot?.cabinetNo || '待生成'} / 柜型:${snapshot?.relatedContainerType || '—'} / 集装箱号:${snapshot?.containerNo || '待补录'}(当前绑定,不可重选)`,
|
||||
}
|
||||
})
|
||||
const selectedBookingFallback = computed(() => {
|
||||
const id = draft.oceanBookingId
|
||||
const snapshot = props.resource
|
||||
@@ -90,16 +76,6 @@ const selectedBookingFallback = computed(() => {
|
||||
return { id: String(id), label: `${snapshot?.oceanBookingNo || '订舱号缺失'}(当前绑定,不可重选)` }
|
||||
})
|
||||
function selectCarrier(carrierId: string) { const carrier = selectableCarriers.value.find((item) => String(item.id) === carrierId); draft.carrierId = carrier?.id || null; draft.carrierName = carrier?.name || null }
|
||||
function selectContainer(containerId: string) {
|
||||
const item = selectableContainers.value.find((entry) => entry.containerBookingId === containerId)
|
||||
draft.containerBookingId = item?.containerBookingId || null
|
||||
relationWarning.value = ''
|
||||
if (!item) {
|
||||
draft.loadingPlanId = null
|
||||
return
|
||||
}
|
||||
draft.loadingPlanId = item.loadingPlanId
|
||||
}
|
||||
async function loadCurrentBookingContext(bookingId: string) {
|
||||
const sequence = ++bookingLoadSequence
|
||||
bookingContainersLoading.value = true
|
||||
@@ -127,8 +103,6 @@ async function selectBooking(bookingId: string) {
|
||||
bookingLoadSequence += 1
|
||||
const item = bookings.value.find((entry) => entry.id === bookingId)
|
||||
draft.oceanBookingId = item?.id || null
|
||||
draft.containerBookingId = null
|
||||
draft.loadingPlanId = null
|
||||
bookingDocuments.value = []
|
||||
relationWarning.value = ''
|
||||
if (!item) {
|
||||
@@ -155,15 +129,12 @@ async function loadOptions() {
|
||||
optionsLoading.value = false
|
||||
if (relationLocked.value && draft.oceanBookingId) await loadCurrentBookingContext(String(draft.oceanBookingId))
|
||||
else if (draft.oceanBookingId) {
|
||||
const candidate = candidates.value.find((item) => item.oceanBookingId === draft.oceanBookingId && item.containerBookingId === draft.containerBookingId)
|
||||
const candidate = candidates.value.find((item) => item.oceanBookingId === draft.oceanBookingId)
|
||||
if (candidate) {
|
||||
draft.warehouseId = candidate.warehouseId
|
||||
draft.billOfLadingNo = candidate.billOfLadingNo
|
||||
draft.loadingPlanId = candidate.loadingPlanId
|
||||
} else {
|
||||
draft.oceanBookingId = null
|
||||
draft.containerBookingId = null
|
||||
draft.loadingPlanId = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,13 +148,12 @@ watch(() => props.resource?.id, (id, previous) => { if (id && id !== previous) v
|
||||
|
||||
<template>
|
||||
<el-form ref="formRef" :model="draft" :rules="rules" label-position="top" :disabled="readonly || submitting" class="truck-form">
|
||||
<div class="relation-note"><span>拖车预约必须先选择已放舱订舱,再选择该订舱下已分配的内部柜次;真实集装箱号可待司机提柜时录入,仓库和提单号由订舱冻结资料带入,最终资格仍由服务端校验。</span><div v-if="$slots['relation-note-actions']" class="relation-note__actions"><slot name="relation-note-actions" /></div></div>
|
||||
<div class="relation-note"><span>一张已放舱订舱对应一张有效拖车单;仓库和提单号由订舱冻结资料带入,真实柜次及集装箱号在货柜与预装流程维护,最终资格仍由服务端校验。</span><div v-if="$slots['relation-note-actions']" class="relation-note__actions"><slot name="relation-note-actions" /></div></div>
|
||||
<el-alert v-if="relationWarning" type="warning" :closable="false" show-icon :title="relationWarning" class="relation-alert" />
|
||||
<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="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 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><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></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>
|
||||
<el-col v-if="draft.oceanBookingId && !bookingContainersLoading && !bookingContainersError && !bookingDocumentReady" :xs="24"><el-alert title="所选订舱缺少有效的订舱确认文件或提单文件,暂不能创建或保存拖车预约" type="warning" :closable="false" show-icon /></el-col>
|
||||
|
||||
Reference in New Issue
Block a user