Release/test 合并上生产 #2

Merged
admin merged 736 commits from release/test into main 2026-09-17 11:50:56 +08:00
4 changed files with 46 additions and 4 deletions
Showing only changes of commit 66ea73e766 - Show all commits
@@ -51,4 +51,12 @@ describe('ContainerDetailView customs integration', () => {
expect(overviewSource).not.toContain(retired)
}
})
it('offers the released-booking link workflow only for an eligible unlinked simulation container', () => {
expect(source).toContain("detail.value?.sourceType === 'SIMULATION'")
expect(source).toContain("!detail.value.oceanBookingId")
expect(source).toContain("['WAIT_LOADING', 'EDITING'].includes(detail.value.planStatus)")
expect(source).toContain('关联已放舱订舱')
expect(source).toContain("query: { linkContainerId: detail.value.id }")
})
})
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ArrowLeft, FileUp } from '@lucide/vue'
import { ArrowLeft, FileUp, Link2 } from '@lucide/vue'
import { ElMessage, ElMessageBox, type UploadFile } from 'element-plus'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
@@ -46,6 +46,10 @@ const canConfirmOutboundDispatch = computed(() => access.can('platform:firstleg:
const canViewPreload = computed(() => access.can('platform:firstleg:preload:list'))
const canViewLoadingList = computed(() => access.can('platform:firstleg:loading-list:list'))
const canViewContainerFees = computed(() => access.can('firstleg:inbound:fee-detail:view'))
const canLinkOceanBooking = computed(() => Boolean(canEdit.value
&& detail.value?.sourceType === 'SIMULATION'
&& !detail.value.oceanBookingId
&& ['WAIT_LOADING', 'EDITING'].includes(detail.value.planStatus)))
const loading = ref(false)
const detail = ref<ContainerBookingResource | null>(null)
const overview = ref<ContainerOverview | null>(null)
@@ -122,6 +126,11 @@ const feeBusinessReferences = computed(() => (loadingDetail.value?.items || []).
inboundCode: item.inboundCode,
})))
function openOceanBookingLink() {
if (!detail.value || !canLinkOceanBooking.value) return
void router.push({ name: 'container-loading', query: { linkContainerId: detail.value.id } })
}
function toOverviewDocument(document: BusinessDocument, documentType: string, fallback?: ContainerOverviewDocument | null): ContainerOverviewDocument {
return {
id: document.id || fallback?.id || document.fileId,
@@ -361,7 +370,7 @@ onMounted(async () => { await load(); if (route.query.action === 'documents') op
<LoadErrorState v-if="overviewError" compact title="履约摘要加载失败" :message="overviewError.message" :show-retry="false" />
<section class="detail-section surface-panel" aria-labelledby="container-information-heading">
<h2 id="container-information-heading">货柜信息</h2>
<div class="detail-fields"><dl><div><dt>真实订舱</dt><dd>{{ detail.oceanBookingId ? (detail.billOfLadingNo || '已关联') : '未关联' }}</dd></div><div><dt>异常状态</dt><dd>{{ detail.exceptionActive ? '异常' : '正常' }}</dd></div><div><dt>目的仓</dt><dd>{{ detail.destinationWarehouseName || detail.destinationWarehouseCode || '—' }}</dd></div><div><dt>拖车公司</dt><dd>{{ detail.terminalTruckingCompany || '—' }}</dd></div></dl></div>
<div class="detail-fields"><dl><div><dt>真实订舱</dt><dd><span>{{ detail.oceanBookingId ? (detail.billOfLadingNo || '已关联') : '未关联' }}</span><el-button v-if="canLinkOceanBooking" link type="primary" @click="openOceanBookingLink"><Link2 :size="14" />关联已放舱订舱</el-button></dd></div><div><dt>异常状态</dt><dd>{{ detail.exceptionActive ? '异常' : '正常' }}</dd></div><div><dt>目的仓</dt><dd>{{ detail.destinationWarehouseName || detail.destinationWarehouseCode || '—' }}</dd></div><div><dt>拖车公司</dt><dd>{{ detail.terminalTruckingCompany || '—' }}</dd></div></dl></div>
<ContainerOverviewPanel v-if="overview" :overview="overview" :sections="['exception']" :can-manage-documents="canEdit" :can-manage-customs="canManageCustoms" :can-manage-exception="canEdit" @manage-exception="openException" />
</section>
@@ -243,6 +243,14 @@ describe('ContainersView', () => {
expect(source).toContain(':loading="loading" stripe preference-key="container-loading-table"')
})
it('opens the existing released-booking workflow when entered from container detail', () => {
const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/ContainersView.vue'), 'utf8')
expect(source).toContain('route.query.linkContainerId')
expect(source).toContain('const target = await getContainer(linkContainerId)')
expect(source).toContain('await openLink(target)')
expect(source).toContain("void router.replace({ name: 'container-loading' })")
})
it('normalizes the mandatory booking-link container number and keeps it as the only initial-entry write', async () => {
vi.mocked(linkContainerOceanBooking).mockResolvedValue({} as never)
const router = createTestRouter()
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { Boxes, Check, Eye, FileText, FileUp, Link2, ListChecks, MoreHorizontal, Pencil, Plus, Sparkles, TriangleAlert } from '@lucide/vue'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
@@ -25,6 +25,7 @@ type Filters = QueryModel & { customerId: string; keyword: string; containerType
type ContainerForm = ContainerSaveRequest & Pick<ContainerBookingResource, 'cabinetNo'> & { id: string; version: number }
const router = useRouter()
const route = useRoute()
const access = useAccessPreviewStore()
const rows = ref<ContainerBookingResource[]>([])
const customers = ref<PlatformTenantResource[]>([])
@@ -503,7 +504,23 @@ watch(linkOpen, (open) => {
linkSequence += 1
linkLoading.value = false
})
onMounted(() => { void Promise.all([loadCustomers(), loadWarehouses(), loadBusinessOptions(), loadRows()]) })
onMounted(async () => {
await Promise.all([loadCustomers(), loadWarehouses(), loadBusinessOptions(), loadRows()])
const linkContainerId = typeof route.query.linkContainerId === 'string' ? route.query.linkContainerId : ''
if (!linkContainerId || !canEdit.value) return
try {
const target = await getContainer(linkContainerId)
if (target.sourceType !== 'SIMULATION' || target.oceanBookingId || !['WAIT_LOADING', 'EDITING'].includes(target.planStatus)) {
ElMessage.warning('当前货柜已关联订舱或所处阶段不允许关联')
return
}
await openLink(target)
} catch (error) {
ElMessage.error(getApiErrorDetails(error, '关联货柜加载失败').message)
} finally {
void router.replace({ name: 'container-loading' })
}
})
</script>
<template>