fix: align inbound pallet plans with preview
This commit is contained in:
@@ -211,7 +211,7 @@ export interface ConvertContainerInboundPlanPayload {
|
||||
snapshotHash: string
|
||||
}
|
||||
|
||||
export type ContainerInboundPlanPreviewReadiness = 'READY' | 'PLANNED' | 'BLOCKED'
|
||||
export type ContainerInboundPlanPreviewReadiness = 'READY' | 'RESOURCE_PENDING' | 'PLANNED' | 'BLOCKED'
|
||||
|
||||
export interface ContainerInboundPlanPreviewOrderDto {
|
||||
customerId: string
|
||||
@@ -224,6 +224,7 @@ export interface ContainerInboundPlanPreviewOrderDto {
|
||||
destinationWarehouseId: string | null
|
||||
destinationWarehouseCode: string | null
|
||||
destinationWarehouseName: string | null
|
||||
fbaWarehouseCode: string | null
|
||||
packageCount: number
|
||||
volumeCbm: number
|
||||
suggestedPalletCount: number | null
|
||||
|
||||
@@ -120,6 +120,16 @@ async function openPending() {
|
||||
return wrapper
|
||||
}
|
||||
|
||||
async function openHiddenContainerWorkbench(wrapper: Awaited<ReturnType<typeof mountView>>) {
|
||||
const view = wrapper.vm as unknown as {
|
||||
statusTab: '全部' | '待分配' | '柜子'
|
||||
changeStatusTab(): void
|
||||
}
|
||||
view.statusTab = '柜子'
|
||||
view.changeStatusTab()
|
||||
await flushPromises()
|
||||
}
|
||||
|
||||
async function selectRows(wrapper: Awaited<ReturnType<typeof mountView>>, rows: object[]) {
|
||||
wrapper.findComponent(DataTable).vm.$emit('selection-change', rows)
|
||||
await wrapper.vm.$nextTick()
|
||||
@@ -178,6 +188,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
expect(wrapper.text()).toContain('按柜批量生成')
|
||||
expect(wrapper.text()).not.toContain('按订单')
|
||||
expect(wrapper.text()).not.toContain('待分配整柜')
|
||||
expect(wrapper.text()).not.toContain('柜子工作台')
|
||||
expect(listLastMileOrders).toHaveBeenLastCalledWith(expect.objectContaining({ page: 1, size: 10, currentStatus: 'PENDING_ASSIGNMENT' }))
|
||||
expect(listContainerPlanProgress).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -208,8 +219,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
await flushPromises()
|
||||
expect(listLastMileOrders).toHaveBeenLastCalledWith(expect.objectContaining({ currentStatus: 'PENDING_ASSIGNMENT', lxCode: 'LX-SEARCH' }))
|
||||
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
expect(listContainerPlanProgress).toHaveBeenLastCalledWith({
|
||||
page: 1, size: 10, containerNo: 'CONT-SEARCH',
|
||||
createdFrom: '2026-09-01T00:00:00', createdTo: '2026-09-07T23:59:59',
|
||||
@@ -265,7 +275,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
|
||||
expect(createManualInboundPlan).toHaveBeenCalledWith(expect.objectContaining({
|
||||
customerId, orderId: '2092873463568596994', expectedOrderVersion: 3,
|
||||
plannedArrivalDate: '2026-09-08', palletCount: 2,
|
||||
plannedArrivalDate: '2026-09-08', palletCount: 5, palletType: 'WOOD',
|
||||
}), expect.stringContaining('manual-inbound-plan-'))
|
||||
const payload = vi.mocked(createManualInboundPlan).mock.calls[0][0] as unknown as Record<string, unknown>
|
||||
expect(payload).not.toHaveProperty('inboundCode')
|
||||
@@ -373,11 +383,9 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
const nullArrival = { ...summary, sourceContainerBookingId: '2092873463568597002', containerNo: 'CONT-NULL', plannedArrivalAt: null, planCount: 0, pendingPlanCount: 15 }
|
||||
vi.mocked(listContainerPlanProgress).mockResolvedValueOnce({ records: [summary, nullArrival], page: 1, size: 10, total: 2, pages: 1 })
|
||||
const wrapper = await mountView()
|
||||
const containerTab = wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))
|
||||
await containerTab!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
expect(listContainerPlanProgress).toHaveBeenLastCalledWith({ page: 1, size: 10, containerNo: undefined, createdFrom: undefined, createdTo: undefined })
|
||||
expect(wrapper.text()).toContain('柜子工作台(2)')
|
||||
expect(wrapper.text()).not.toContain('柜子工作台(2)')
|
||||
expect(wrapper.text()).toContain('货柜工作台')
|
||||
expect(wrapper.text()).toContain('按柜查看和修改海外接柜仓,并为剩余入仓码生成独立入库计划')
|
||||
expect(wrapper.text()).toContain('5个入仓码待生成计划')
|
||||
@@ -401,8 +409,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
}
|
||||
vi.mocked(listContainerPlanProgress).mockResolvedValueOnce({ records: [ready], page: 1, size: 10, total: 1, pages: 1 })
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
expect(wrapper.text()).toContain('CONT-15')
|
||||
expect(wrapper.text()).toContain('0个入仓码待生成计划')
|
||||
expect((button(wrapper, '生成入库计划')!.element as HTMLButtonElement).disabled).toBe(true)
|
||||
@@ -412,8 +419,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
it('renders the all-cabinet empty state and unit', async () => {
|
||||
vi.mocked(listContainerPlanProgress).mockResolvedValueOnce({ records: [], page: 1, size: 10, total: 0, pages: 0 })
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
expect(wrapper.text()).toContain('暂无货柜数据')
|
||||
expect(wrapper.text()).not.toContain('已全部生成计划的柜不再展示')
|
||||
})
|
||||
@@ -421,9 +427,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
it('uses independent cabinet pagination and opens the complete long-id detail in a dialog', async () => {
|
||||
vi.mocked(listContainerPlanProgress).mockResolvedValue({ records: [summary], page: 1, size: 10, total: 21, pages: 3 })
|
||||
const wrapper = await mountView()
|
||||
const containerTab = wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))
|
||||
await containerTab!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
const pagination = wrapper.findComponent({ name: 'ElPagination' })
|
||||
pagination.vm.$emit('current-change', 2)
|
||||
await flushPromises()
|
||||
@@ -445,12 +449,10 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
|
||||
it('starts cabinet plan generation directly from the workbench card', async () => {
|
||||
const wrapper = await mountView()
|
||||
const containerTab = wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))
|
||||
await containerTab!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
await button(wrapper, '生成入库计划')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('预览已就绪,本次将新增 5 个计划、5 个托盘码')
|
||||
expect(document.body.textContent).toContain('预览已就绪,本次将处理 5 票订单、生成或补齐 5 个托盘码')
|
||||
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认生成')) as HTMLButtonElement
|
||||
expect(confirm.disabled).toBe(false)
|
||||
confirm.click()
|
||||
@@ -474,8 +476,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
vi.mocked(listContainerPlanProgress).mockResolvedValueOnce({ records: [invalid], page: 1, size: 10, total: 1, pages: 1 } as never)
|
||||
vi.mocked(getContainerPlanProgress).mockResolvedValueOnce({ summary: invalid, orders: [] } as never)
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
|
||||
expect(wrapper.text()).toContain('柜内接柜仓业务快照不一致')
|
||||
expect((button(wrapper, '生成入库计划')!.element as HTMLButtonElement).disabled).toBe(true)
|
||||
@@ -489,8 +490,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
it('shows the receiving warehouse and changes it from ACTIVE OVERSEAS master data with versioning', async () => {
|
||||
vi.mocked(listAllPlatformWarehouses).mockResolvedValueOnce([currentReceivingWarehouse, receivingWarehouse] as never)
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
expect(wrapper.text()).toContain('海外接柜仓')
|
||||
expect(wrapper.text()).toContain('LAX01 · 洛杉矶接柜仓')
|
||||
expect(wrapper.get('.container-workbench-card__warehouse strong').attributes('title')).toBe('LAX01 · 洛杉矶接柜仓')
|
||||
@@ -526,8 +526,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
let resolveCandidates!: (value: typeof receivingWarehouse[]) => void
|
||||
vi.mocked(listAllPlatformWarehouses).mockReturnValueOnce(new Promise((resolve) => { resolveCandidates = resolve }) as never)
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
await button(wrapper, '修改接柜仓')!.trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认修改')) as HTMLButtonElement
|
||||
@@ -544,8 +543,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
page: 1, size: 10, total: 1, pages: 1,
|
||||
})
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
expect(wrapper.text()).toContain('海外仓已开始拆柜收货')
|
||||
const changeButton = button(wrapper, '修改接柜仓')!
|
||||
expect(changeButton.exists()).toBe(true)
|
||||
@@ -557,8 +555,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
it('keeps a disabled receiving-warehouse action discoverable without edit permission', async () => {
|
||||
canAccess.mockImplementation((permissionCode: string) => permissionCode !== 'platform:overseas-warehouse:last-mile-order:edit')
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
const changeButton = button(wrapper, '修改接柜仓')!
|
||||
expect(changeButton.exists()).toBe(true)
|
||||
expect((changeButton.element as HTMLButtonElement).disabled).toBe(true)
|
||||
@@ -574,8 +571,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
'version conflict', 'A0409', 200, undefined, 'request-receiving', 'trace-receiving',
|
||||
))
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
await button(wrapper, '修改接柜仓')!.trigger('click')
|
||||
await flushPromises()
|
||||
const warehouseSelect = wrapper.findAllComponents({ name: 'ElSelect' }).find((item) => item.props('placeholder') === '请选择 ACTIVE 海外接柜仓')!
|
||||
@@ -593,8 +589,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
it('renders an explicit empty state when there are no ACTIVE OVERSEAS receiving warehouses', async () => {
|
||||
vi.mocked(listAllPlatformWarehouses).mockResolvedValueOnce([])
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
await button(wrapper, '修改接柜仓')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('暂无可用的 ACTIVE 海外接柜仓')
|
||||
@@ -606,8 +601,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
'receiving warehouse candidate failed', 'A0503', 200, undefined, 'request-receiving-options', 'trace-receiving-options',
|
||||
))
|
||||
const wrapper = await mountView()
|
||||
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
|
||||
await flushPromises()
|
||||
await openHiddenContainerWorkbench(wrapper)
|
||||
await button(wrapper, '修改接柜仓')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('request-receiving-options')
|
||||
|
||||
@@ -45,7 +45,8 @@ const inboundPlanOpen = ref(false)
|
||||
const inboundPlanWarehouseLabel = computed(() => [actionTarget.value?.overseasWarehouseCode, actionTarget.value?.warehouse]
|
||||
.filter((value) => value && value !== '—')
|
||||
.join(' · '))
|
||||
const inboundPlanForm = reactive({ plannedArrivalDate: '', inboundCode: '', palletCount: 1, palletType: 'STANDARD' as 'STANDARD' | 'WOOD' | 'PAPER', assigned: false, mergeRequired: false, urgent: false, remark: '' })
|
||||
const inboundPlanMinimumPalletCount = ref(1)
|
||||
const inboundPlanForm = reactive({ plannedArrivalDate: '', inboundCode: '', palletCount: 1, palletType: 'WOOD' as 'STANDARD' | 'WOOD' | 'PAPER', assigned: false, mergeRequired: false, urgent: false, remark: '' })
|
||||
const holdOpen = ref(false)
|
||||
const holdForm = reactive({ held: true, chargeable: true, reason: '' })
|
||||
const manualNodesOpen = ref(false)
|
||||
@@ -472,14 +473,31 @@ function openInboundPlan(source?: object) {
|
||||
if (!target) { ElMessage.warning('生成入库计划时请选择 1 条尾程订单'); return }
|
||||
if (target.detained) { ElMessage.warning('订单处于暂扣状态,解除暂扣后才能生成入库计划'); return }
|
||||
actionTarget.value = target
|
||||
Object.assign(inboundPlanForm, { plannedArrivalDate: '', inboundCode: target.inboundCode === '—' ? '' : target.inboundCode, palletCount: typeof target.pallets === 'number' && target.pallets > 0 ? target.pallets : 1, palletType: 'STANDARD', assigned: false, mergeRequired: false, urgent: false, remark: '' })
|
||||
const volumeMinimum = typeof target.volume === 'number' && target.volume > 0 ? Math.ceil(target.volume / 2) : 1
|
||||
inboundPlanMinimumPalletCount.value = Math.max(1, volumeMinimum,
|
||||
typeof target.pallets === 'number' ? target.pallets : 0)
|
||||
Object.assign(inboundPlanForm, { plannedArrivalDate: '', inboundCode: target.inboundCode === '—' ? '' : target.inboundCode, palletCount: inboundPlanMinimumPalletCount.value, palletType: 'WOOD', assigned: false, mergeRequired: false, urgent: false, remark: '' })
|
||||
actionError.value = null
|
||||
inboundPlanOpen.value = true
|
||||
void refreshManualPalletMinimum(target)
|
||||
}
|
||||
async function refreshManualPalletMinimum(target: LastMileOrderListItem) {
|
||||
if (!target.sourceContainerBookingId) return
|
||||
try {
|
||||
const preview = await getContainerInboundPlanPreview(target.sourceContainerBookingId)
|
||||
if (actionTarget.value?.id !== target.id || !inboundPlanOpen.value) return
|
||||
const order = preview.groups.flatMap((group) => group.orders).find((item) => item.orderId === target.id)
|
||||
const suggested = Number(order?.suggestedPalletCount || 0)
|
||||
inboundPlanMinimumPalletCount.value = Math.max(inboundPlanMinimumPalletCount.value, suggested)
|
||||
inboundPlanForm.palletCount = Math.max(inboundPlanForm.palletCount, inboundPlanMinimumPalletCount.value)
|
||||
} catch {
|
||||
// The backend repeats the same calculation and remains authoritative on submit.
|
||||
}
|
||||
}
|
||||
async function submitInboundPlan() {
|
||||
const row = actionTarget.value
|
||||
const frozenInboundCode = String(row?.inboundCode || '').trim()
|
||||
if (!row || !row.overseasWarehouseId || !inboundPlanForm.plannedArrivalDate || !frozenInboundCode || frozenInboundCode === '—' || inboundPlanForm.palletCount < 1) {
|
||||
if (!row || !row.overseasWarehouseId || !inboundPlanForm.plannedArrivalDate || !frozenInboundCode || frozenInboundCode === '—' || inboundPlanForm.palletCount < inboundPlanMinimumPalletCount.value) {
|
||||
ElMessage.warning('当前柜须已分配接柜仓,并完整填写预计到仓日期和托数')
|
||||
return
|
||||
}
|
||||
@@ -621,7 +639,7 @@ async function confirmContainerInboundPlans() {
|
||||
if (!containerConversionIdempotencyKey) containerConversionIdempotencyKey = idempotencyKey('container-inbound-plan')
|
||||
const bookingId = preview.sourceContainerBookingId
|
||||
const existingPlanNos = new Set(preview.groups.flatMap((group) => group.orders)
|
||||
.filter((order) => order.readiness === 'PLANNED' && order.existingPlanNo)
|
||||
.filter((order) => order.readiness !== 'READY' && order.existingPlanNo)
|
||||
.map((order) => String(order.existingPlanNo)))
|
||||
actionLoading.value = true
|
||||
actionError.value = null
|
||||
@@ -671,11 +689,11 @@ function orderTypeText(orderType: string) {
|
||||
}
|
||||
|
||||
function readinessText(readiness: ContainerInboundPlanPreviewOrderDto['readiness']) {
|
||||
return readiness === 'READY' ? '可生成' : readiness === 'PLANNED' ? '已有计划' : '已阻断'
|
||||
return readiness === 'READY' ? '可生成' : readiness === 'RESOURCE_PENDING' ? '待补托盘' : readiness === 'PLANNED' ? '已有计划' : '已阻断'
|
||||
}
|
||||
|
||||
function readinessType(readiness: ContainerInboundPlanPreviewOrderDto['readiness']) {
|
||||
return readiness === 'READY' ? 'success' : readiness === 'PLANNED' ? 'info' : 'danger'
|
||||
return readiness === 'READY' ? 'success' : readiness === 'RESOURCE_PENDING' ? 'warning' : readiness === 'PLANNED' ? 'info' : 'danger'
|
||||
}
|
||||
|
||||
function blockerText(blockers: string[]) {
|
||||
@@ -687,6 +705,8 @@ function blockerText(blockers: string[]) {
|
||||
RECEIVING_WAREHOUSE_INCONSISTENT: '收货仓不一致',
|
||||
DESTINATION_NOT_READY: '目的地未就绪',
|
||||
CARGO_FACT_INVALID: '货物数据无效',
|
||||
PLAN_ALREADY_STARTED: '已有计划已开始作业,不能再补生成托盘',
|
||||
PLAN_PALLET_BINDING_INCOMPLETE: '已有计划只绑定了部分真实托盘,请联系管理员处理',
|
||||
DROPSHIP_PALLET_SNAPSHOT_MISSING: '缺少一件代发托盘快照',
|
||||
PALLET_COUNT_NOT_READY: '计划托数暂未就绪',
|
||||
}
|
||||
@@ -719,7 +739,8 @@ onMounted(() => { void load() })
|
||||
<el-radio-group v-model="statusTab" aria-label="尾程订单状态" @change="changeStatusTab">
|
||||
<el-radio-button value="全部">全部</el-radio-button>
|
||||
<el-radio-button value="待分配">待分配</el-radio-button>
|
||||
<el-radio-button value="柜子">柜子工作台({{ containerTotal }})</el-radio-button>
|
||||
<!-- 柜子工作台入口暂时隐藏,保留现有实现便于后续恢复。 -->
|
||||
<!-- <el-radio-button value="柜子">柜子工作台({{ containerTotal }})</el-radio-button> -->
|
||||
</el-radio-group>
|
||||
<QueryToolbar v-model="filters" :fields="fields" :available-fields="allFields" :loading="loading" ariaLabel="尾程订单筛选" preference-key="overseas-last-mile-orders-query" :search-on-reset="false" @search="search" @reset="reset" />
|
||||
</section>
|
||||
@@ -808,12 +829,12 @@ onMounted(() => { void load() })
|
||||
</div>
|
||||
<div class="container-preview__metrics">
|
||||
<div class="container-preview__metric container-preview__metric--primary"><span>本次新增托盘码</span><strong>{{ containerPreview.pendingPalletCount }}</strong></div>
|
||||
<div class="container-preview__metric"><span>本次新增计划</span><strong>{{ containerPreview.pendingOrderCount }}</strong></div>
|
||||
<div class="container-preview__metric"><span>本次处理订单</span><strong>{{ containerPreview.pendingOrderCount }}</strong></div>
|
||||
<div class="container-preview__metric"><span>已有计划托盘</span><strong>{{ containerPreview.existingPlannedPalletCount }}</strong></div>
|
||||
<div class="container-preview__metric" :class="{ 'container-preview__metric--danger': containerPreview.blockedOrderCount > 0 }"><span>阻断订单</span><strong>{{ containerPreview.blockedOrderCount }}</strong></div>
|
||||
</div>
|
||||
<el-alert v-if="containerPreview.blockedOrderCount > 0" class="container-convert-alert" type="error" :closable="false" show-icon title="存在阻断订单,暂不可确认生成;请按下方原因处理后重新预览。" />
|
||||
<el-alert v-else-if="!containerConversionResult" class="container-convert-alert" type="success" :closable="false" show-icon :title="`预览已就绪,本次将新增 ${containerPreview.pendingOrderCount} 个计划、${containerPreview.pendingPalletCount} 个托盘码。`" />
|
||||
<el-alert v-else-if="!containerConversionResult" class="container-convert-alert" type="success" :closable="false" show-icon :title="`预览已就绪,本次将处理 ${containerPreview.pendingOrderCount} 票订单、生成或补齐 ${containerPreview.pendingPalletCount} 个托盘码。`" />
|
||||
<section v-if="containerConversionResult" class="container-conversion-result">
|
||||
<header><strong>生成完成</strong><span>新建 {{ containerConversionResult.newPlanCount }} 个 / 已有 {{ containerConversionResult.existingPlanCount }} 个</span></header>
|
||||
<div class="container-conversion-result__plans">
|
||||
@@ -828,7 +849,7 @@ onMounted(() => { void load() })
|
||||
<section v-for="group in containerPreview.groups" :key="`${group.destinationType}-${group.destinationWarehouseId || group.destinationWarehouseCode || 'default'}`" class="container-preview__group">
|
||||
<header class="container-preview__group-header">
|
||||
<strong class="text-ellipsis" :title="previewGroupTitle(group)">{{ previewGroupTitle(group) }}:{{ group.pendingPalletCount }}托</strong>
|
||||
<span>本次 {{ group.pendingOrderCount }} 票 / 已有 {{ group.existingPlannedPalletCount }} 托 / 共 {{ group.orderCount }} 票</span>
|
||||
<span>本次处理 {{ group.pendingOrderCount }} 票 / 已有 {{ group.existingPlannedPalletCount }} 托 / 共 {{ group.orderCount }} 票</span>
|
||||
</header>
|
||||
<el-table :data="group.orders" size="small" border>
|
||||
<el-table-column label="入仓码" min-width="148" show-overflow-tooltip><template #default="{ row }">{{ row.inboundCode || '—' }}</template></el-table-column>
|
||||
@@ -888,7 +909,7 @@ onMounted(() => { void load() })
|
||||
<AppDialog v-model="inboundPlanOpen" title="入库计划" width="min(720px, calc(100vw - 28px))" confirm-text="保存并生成入库单" :confirm-loading="actionLoading" @confirm="submitInboundPlan">
|
||||
<el-alert title="保存后生成入库单;清关完成后入库计划不可更改。" type="info" :closable="false" show-icon />
|
||||
<el-form class="command-form" label-position="top">
|
||||
<div class="command-form__grid"><el-form-item label="计划入库仓"><el-input :model-value="inboundPlanWarehouseLabel" disabled placeholder="当前柜接柜仓" /><small>接柜仓按柜统一;如需变更,请在柜子工作台执行“变更接柜仓”。</small></el-form-item><el-form-item label="预计到仓日期" required><el-date-picker v-model="inboundPlanForm.plannedArrivalDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择日期" /></el-form-item><el-form-item label="入仓码"><el-input :model-value="inboundPlanForm.inboundCode" disabled placeholder="订单冻结入仓码" /></el-form-item><el-form-item label="托数" required><el-input-number v-model="inboundPlanForm.palletCount" :min="1" :max="999" controls-position="right" placeholder="请输入托数" /></el-form-item><el-form-item label="托盘类型"><el-select v-model="inboundPlanForm.palletType" placeholder="请选择托盘类型"><el-option label="标准托盘" value="STANDARD" /><el-option label="木托盘" value="WOOD" /><el-option label="纸托盘" value="PAPER" /></el-select></el-form-item><el-form-item label="是否分配"><el-switch v-model="inboundPlanForm.assigned" inline-prompt active-text="是" inactive-text="否" /></el-form-item><el-form-item label="允许合托"><el-switch v-model="inboundPlanForm.mergeRequired" inline-prompt active-text="是" inactive-text="否" /><small>仅作为现场可选提示,不强制工人合托。</small></el-form-item><el-form-item label="是否加急"><el-switch v-model="inboundPlanForm.urgent" inline-prompt active-text="是" inactive-text="否" /></el-form-item></div>
|
||||
<div class="command-form__grid"><el-form-item label="计划入库仓"><el-input :model-value="inboundPlanWarehouseLabel" disabled placeholder="当前柜接柜仓" /><small>接柜仓按柜统一;如需变更,请在柜子工作台执行“变更接柜仓”。</small></el-form-item><el-form-item label="预计到仓日期" required><el-date-picker v-model="inboundPlanForm.plannedArrivalDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择日期" /></el-form-item><el-form-item label="入仓码"><el-input :model-value="inboundPlanForm.inboundCode" disabled placeholder="订单冻结入仓码" /></el-form-item><el-form-item label="托数" required><el-input-number v-model="inboundPlanForm.palletCount" :min="inboundPlanMinimumPalletCount" :max="999" controls-position="right" placeholder="请输入托数" /><small>按柜级预览至少 {{ inboundPlanMinimumPalletCount }} 托,可增加,不能减少。</small></el-form-item><el-form-item label="托盘类型"><el-select v-model="inboundPlanForm.palletType" placeholder="请选择托盘类型"><el-option label="标准托盘" value="STANDARD" /><el-option label="木托盘" value="WOOD" /><el-option label="纸托盘" value="PAPER" /></el-select></el-form-item><el-form-item label="是否分配"><el-switch v-model="inboundPlanForm.assigned" inline-prompt active-text="是" inactive-text="否" /></el-form-item><el-form-item label="允许合托"><el-switch v-model="inboundPlanForm.mergeRequired" inline-prompt active-text="是" inactive-text="否" /><small>仅作为现场可选提示,不强制工人合托。</small></el-form-item><el-form-item label="是否加急"><el-switch v-model="inboundPlanForm.urgent" inline-prompt active-text="是" inactive-text="否" /></el-form-item></div>
|
||||
<el-form-item label="备注"><el-input v-model="inboundPlanForm.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="请输入备注(选填)" /></el-form-item>
|
||||
</el-form>
|
||||
</AppDialog>
|
||||
|
||||
Reference in New Issue
Block a user