Release/test 合并上生产 #2
@@ -11,6 +11,11 @@ import {
|
||||
recommendSmartLoading,
|
||||
} from '../api/platformContainerLoading'
|
||||
|
||||
const { routerPush } = vi.hoisted(() => ({ routerPush: vi.fn() }))
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRouter: () => ({ push: routerPush }),
|
||||
}))
|
||||
vi.mock('@/core/access/accessPreview', () => ({
|
||||
useAccessPreviewStore: () => ({ can: () => true }),
|
||||
}))
|
||||
@@ -82,6 +87,40 @@ describe('GlobalSmartLoadingView', () => {
|
||||
expect(wrapper.text()).toContain('CB-1 装柜方案')
|
||||
})
|
||||
|
||||
it('keeps pushed containers visible and routes them to the preload list without loading an editing draft', async () => {
|
||||
const pushed = { ...container, id: '9007199254740995', planId: '9007199254740996', cabinetNo: 'CB-2', planStatus: 'LOADING' as const }
|
||||
vi.mocked(listContainers).mockResolvedValue({ records: [pushed], page: 1, size: 100, total: 1, pages: 1 })
|
||||
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
|
||||
expect(getSmartLoadingPlanByBooking).not.toHaveBeenCalled()
|
||||
expect(listSmartLoadingCandidates).not.toHaveBeenCalled()
|
||||
expect(wrapper.text()).toContain('该柜次正在预装流程中')
|
||||
expect(wrapper.text()).not.toContain('智能推荐')
|
||||
expect(wrapper.findAll('button').some((item) => item.text().trim() === '推送预装')).toBe(false)
|
||||
|
||||
await wrapper.findAll('button').find((item) => item.text().includes('查看预装清单'))!.trigger('click')
|
||||
expect(routerPush).toHaveBeenCalledWith({
|
||||
name: 'loading-plans',
|
||||
query: { planId: pushed.planId, warehouseId: pushed.warehouseId },
|
||||
})
|
||||
})
|
||||
|
||||
it('routes completed containers to the read-only loading list', async () => {
|
||||
const completed = { ...container, id: '9007199254740997', planId: '9007199254740998', cabinetNo: 'CB-3', planStatus: 'COMPLETED' as const }
|
||||
vi.mocked(listContainers).mockResolvedValue({ records: [completed], page: 1, size: 100, total: 1, pages: 1 })
|
||||
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
|
||||
expect(getSmartLoadingPlanByBooking).not.toHaveBeenCalled()
|
||||
expect(wrapper.text()).toContain('该柜次已完成装柜')
|
||||
await wrapper.findAll('button').find((item) => item.text().includes('查看装柜清单'))!.trigger('click')
|
||||
expect(routerPush).toHaveBeenCalledWith({
|
||||
name: 'loading-lists',
|
||||
query: { planId: completed.planId, warehouseId: completed.warehouseId },
|
||||
})
|
||||
})
|
||||
|
||||
it('refreshes the latest plan after A0409 without losing the selected container', async () => {
|
||||
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Plus, RefreshCw, Save, Sparkles, Trash2, Upload } from '@lucide/vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
@@ -30,6 +31,7 @@ import type {
|
||||
} from '../types'
|
||||
|
||||
const access = useAccessPreviewStore()
|
||||
const router = useRouter()
|
||||
const containers = ref<ContainerBookingResource[]>([])
|
||||
const selectedContainerId = ref('')
|
||||
const plan = ref<SmartLoadingPlan | null>(null)
|
||||
@@ -55,6 +57,7 @@ const canDelete = computed(() => access.can('platform:firstleg:smart-loading:del
|
||||
const canPush = computed(() => access.can('platform:firstleg:smart-loading:push'))
|
||||
const selectedContainer = computed(() => containers.value.find((item) => item.id === selectedContainerId.value) || null)
|
||||
const editable = computed(() => plan.value?.status === 'EDITING')
|
||||
const selectedPlanStatus = computed(() => selectedContainer.value?.planStatus || null)
|
||||
const assignedDetailIds = computed(() => new Set((plan.value?.items || []).map((item) => item.inboundDetailId)))
|
||||
const totalPackages = computed(() => (plan.value?.items || []).reduce((sum, item) => sum + Number(item.packageCount || 0), 0))
|
||||
const totalVolume = computed(() => (plan.value?.items || []).reduce((sum, item) => sum + Number(item.volumeCbm || 0), 0))
|
||||
@@ -168,6 +171,9 @@ async function loadSelectedData(preserved: Record<string, number> = {}) {
|
||||
const request = ++selectionRequest
|
||||
clearSelectionData()
|
||||
if (!container) return
|
||||
// LOADING/COMPLETED 计划已离开智能装柜编辑态。不要调用仅支持 EDITING
|
||||
// 草稿恢复的接口,否则后端会按契约返回“已推送预装”冲突。
|
||||
if (container.planStatus !== 'EDITING') return
|
||||
selectionLoading.value = true
|
||||
try {
|
||||
const [candidateResult, planResult] = await Promise.all([
|
||||
@@ -189,6 +195,14 @@ async function loadSelectedData(preserved: Record<string, number> = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function openList(status: 'LOADING' | 'COMPLETED') {
|
||||
const routeName = status === 'LOADING' ? 'loading-plans' : 'loading-lists'
|
||||
const params = selectedContainer.value
|
||||
? { planId: selectedContainer.value.planId || undefined, warehouseId: selectedContainer.value.warehouseId }
|
||||
: undefined
|
||||
void router.push({ name: routeName, query: params })
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
loadError.value = null
|
||||
@@ -406,6 +420,18 @@ onMounted(load)
|
||||
<template #details><ApiErrorMeta :details="selectionError" /></template>
|
||||
</LoadErrorState>
|
||||
<el-empty v-else-if="!selectedContainer" description="请选择一个货柜" />
|
||||
<section v-else-if="selectedPlanStatus === 'LOADING'" class="readonly-status-card" aria-label="预装中柜次">
|
||||
<el-tag type="warning">已推送预装</el-tag>
|
||||
<strong>该柜次正在预装流程中</strong>
|
||||
<p>智能装柜方案已锁定,不能继续调整。预装运输信息和未拣货明细请在预装清单维护。</p>
|
||||
<el-button type="primary" @click="openList('LOADING')">查看预装清单</el-button>
|
||||
</section>
|
||||
<section v-else-if="selectedPlanStatus === 'COMPLETED'" class="readonly-status-card" aria-label="已完成柜次">
|
||||
<el-tag type="success">已完成</el-tag>
|
||||
<strong>该柜次已完成装柜</strong>
|
||||
<p>装柜计划、明细和运输信息已永久只读,请在装柜清单查看完整记录。</p>
|
||||
<el-button type="primary" @click="openList('COMPLETED')">查看装柜清单</el-button>
|
||||
</section>
|
||||
<el-empty v-else-if="noDraft" description="当前货柜尚未创建装柜方案">
|
||||
<el-button v-if="canCreate" type="primary" :loading="mutating" @click="createPlan"><Plus :size="16" />创建装柜方案</el-button>
|
||||
</el-empty>
|
||||
@@ -463,6 +489,9 @@ onMounted(load)
|
||||
.plan-actions,.row-actions { flex-wrap: wrap; }
|
||||
.plan-actions :deep(.el-button + .el-button),.row-actions :deep(.el-button + .el-button) { margin-left: 0; }
|
||||
.capacity-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; padding: 12px; border-bottom: 1px solid var(--cc-border); }
|
||||
.readonly-status-card { display: grid; align-content: start; justify-items: start; gap: 10px; padding: 28px 24px; }
|
||||
.readonly-status-card strong { font-size: 16px; }
|
||||
.readonly-status-card p { max-width: 460px; margin: 0; color: var(--cc-muted); line-height: 1.6; }
|
||||
.meter-title { justify-content: space-between; margin-bottom: 8px; font-size: 12px; }
|
||||
.plan-pane :deep(.el-input-number) { width: 120px; }
|
||||
.assign-dialog p { margin: 0 0 10px; }
|
||||
|
||||
Reference in New Issue
Block a user