Files
admin-web/src/modules/container-loading/pages/ContainerDetailView.test.ts
T

140 lines
9.0 KiB
TypeScript
Raw Normal View History

2026-08-28 16:54:02 +08:00
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
2026-08-18 17:40:40 +08:00
2026-08-28 16:54:02 +08:00
const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/ContainerDetailView.vue'), 'utf8')
const overviewSource = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/components/ContainerOverviewPanel.vue'), 'utf8')
const routeSource = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/routes.ts'), 'utf8')
const loadingRouteSource = readFileSync(resolve(process.cwd(), 'src/modules/loading-lists/routes.ts'), 'utf8')
const navigationSource = readFileSync(resolve(process.cwd(), 'src/core/navigation/adminNavigation.ts'), 'utf8')
2026-09-08 22:03:03 +08:00
const lastLegFeesSource = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/components/ContainerLastLegFeesPanel.vue'), 'utf8')
2026-08-26 11:10:48 +08:00
2026-08-28 16:54:02 +08:00
describe('ContainerDetailView customs integration', () => {
it('renders the requested detail sections in the confirmed business order', () => {
const headings = [
'container-information-heading">货柜信息',
'container-voyage-heading">船名航次信息',
'container-customs-heading">{{ isDomestic',
'container-receiving-heading">装柜作业与国内出仓',
]
let previous = -1
for (const heading of headings) {
const current = source.indexOf(heading)
expect(current, heading).toBeGreaterThan(previous)
previous = current
}
})
it('reuses the current schedule, customs, receipt and container-fee capabilities', () => {
expect(source).toContain('<CarrierScheduleQueryPanel')
expect(source).toContain(':sections="[\'documents\']"')
expect(source).toContain(':sections="[\'pda\']"')
expect(source).toContain('<LoadingListDetailDrawer')
2026-10-08 17:08:03 +08:00
expect(source).toContain('useLoadingListDetail')
expect(source).toContain('await loadListDetail(mode, aggregate.loadingPlanId')
expect(source).toContain('label="装柜清单"')
2026-10-08 17:08:03 +08:00
expect(source).not.toContain('function printLoadingList')
expect(source).toContain("const loadingListMode = computed<LoadingListMode>(() => detail.value?.planStatus === 'COMPLETED' ? 'confirmed' : 'preload')")
2026-10-08 17:08:03 +08:00
expect(source).toContain('<LoadingListDetailDrawer dense :mode="loadingListMode" back-label="返回货柜概览"')
expect(source).toContain(':sections="[\'pda\']"')
})
it('keeps the container detail entry alongside the standalone manifest', () => {
expect(routeSource).toContain("name: 'domestic-container-detail'")
expect(routeSource).toContain("name: 'overseas-container-detail'")
expect(loadingRouteSource).toContain("name: 'loading-lists'")
expect(navigationSource).toContain("title: '装柜清单'")
2026-09-08 22:03:03 +08:00
expect(source).toContain('<el-tabs v-model="activeTab"')
expect(source).toContain("v-if=\"isDomestic && activeTab === 'loading-list'\"")
2026-10-08 17:08:03 +08:00
expect(source).toContain("await loadListDetail(mode, aggregate.loadingPlanId || '', resource.warehouseId || '')")
expect(source).toContain('getContainer(containerId.value, props.workbench)')
expect(source).toContain('getContainerOverview(containerId.value)')
expect(source).toContain(':can-edit="false"')
})
2026-09-08 22:35:07 +08:00
it('keeps the loaded overview mounted while switching fee tabs', () => {
expect(source).toContain("v-show=\"activeTab === 'overview'\" class=\"detail-section surface-panel\" aria-labelledby=\"container-voyage-heading\"")
expect(source).toContain("v-if=\"isOverseas\" v-show=\"activeTab === 'overview'\"")
expect(source).not.toContain("v-if=\"activeTab === 'overview'\" class=\"detail-section surface-panel\"")
})
2026-09-09 10:18:12 +08:00
it('exposes empty return directly in overseas detail only when permission and server action both allow it', () => {
expect(source).toContain("access.can('platform:firstleg:terminal-drayage:empty-return:confirm')")
expect(source).toContain("hasAction('RECORD_EMPTY_RETURN')")
expect(source).toContain("terminalDrayage?.allowedActions.includes('RECORD_EMPTY_RETURN')")
expect(source).toContain('v-if="canRecordEmptyReturn" type="primary"')
2026-09-09 10:26:17 +08:00
expect(source).toContain('>确认还柜</el-button>')
2026-09-09 10:18:12 +08:00
expect(source).toContain('expectedVersion: current.version')
expect(source).toContain('actualAt: emptyReturnAt.value')
2026-09-09 10:26:17 +08:00
expect(source).toContain('remark: emptyReturnReason.value.trim() || undefined')
2026-09-09 10:18:12 +08:00
expect(source).toContain('await load()')
})
2026-09-08 22:03:03 +08:00
it('separates domestic first-leg fees and connects the overseas fee lifecycle lazily', () => {
2026-09-09 10:18:12 +08:00
expect(source).toContain('label="国内段费用(头程)" name="first-leg-fees"')
expect(source).toContain('label="目的港及海外段费用(尾程)" name="last-leg-fees"')
2026-09-09 11:55:10 +08:00
expect(source).toContain('v-if="canViewFirstLegFees && firstLegFeesOpened"')
2026-09-08 22:03:03 +08:00
expect(source).toContain('v-show="activeTab === \'first-leg-fees\'"')
2026-09-09 10:18:12 +08:00
expect(source).toContain('title="国内段费用(头程)" :plan-id="overview.loadingPlanId" :container-booking-id="detail.id"')
2026-09-09 11:55:10 +08:00
expect(source).not.toContain(':readonly="isOverseas"')
expect(source).toContain('v-if="canViewLastLegFees && lastLegFeesOpened"')
2026-09-09 10:18:12 +08:00
expect(source).toContain('title="目的港及海外段费用(尾程)"')
2026-09-09 11:55:10 +08:00
expect(source).not.toContain(':readonly="isDomestic"')
2026-09-08 22:03:03 +08:00
expect(source).toContain("if (isDomestic.value && activeTab.value === 'loading-list') await loadLoadingDetail(resource, aggregate)")
2026-09-08 23:23:47 +08:00
expect(source).toContain("const needsLoadingDetail = tabName === 'first-leg-fees' || (isDomestic.value && tabName === 'loading-list')")
2026-09-08 22:03:03 +08:00
expect(lastLegFeesSource.indexOf('label="操作"')).toBeLessThan(lastLegFeesSource.indexOf('label="客户"'))
expect(lastLegFeesSource).toContain('show-overflow-tooltip')
expect(lastLegFeesSource).toContain('尾程计费加载失败')
expect(lastLegFeesSource).toContain('empty-text="当前货柜暂无尾程费用"')
expect(lastLegFeesSource).toContain('v-loading="loading"')
2026-09-09 11:55:10 +08:00
expect(lastLegFeesSource).toContain("canEdit.value && access.can('platform:supplier:list') ? listContainerFeeSupplierOptions(null) : Promise.resolve([])")
2026-09-08 22:30:04 +08:00
expect(lastLegFeesSource).toContain("sourceType: 'OVERSEAS_CONTAINER_FEE', businessType: 'OVERSEAS_CONTAINER'")
2026-09-08 22:03:03 +08:00
expect(lastLegFeesSource).toContain("form.settlementMode === 'DIRECT_PAID'")
expect(lastLegFeesSource).toContain("access.can('platform:finance:fee-source:edit')")
expect(lastLegFeesSource).toContain("access.can('platform:finance:fee-source:review')")
2026-09-08 23:23:47 +08:00
expect(source).toContain("if (!overview.value?.loadingPlanId) return '当前货柜尚未关联装柜计划,暂无头程计费归属。'")
expect(source).toContain(':description="firstLegFeesEmptyReason"')
expect(source).toContain("const needsLoadingDetail = tabName === 'first-leg-fees' || (isDomestic.value && tabName === 'loading-list')")
2026-09-08 22:03:03 +08:00
})
2026-09-09 10:18:12 +08:00
it('uses domestic and overseas route-specific primary status and summary labels', () => {
expect(source).toContain("const primaryStatusText = computed(() => isDomestic.value ? statusText(detail.value?.planStatus) : overseasStageText.value)")
expect(source).toContain("UNLOADED: '待还柜'")
expect(source).toContain("EMPTY_RETURNED: '已还柜'")
expect(source).toContain("{{ isDomestic ? '装柜清单号' : '当前海外阶段' }}")
expect(source).toContain("{{ isDomestic ? '装柜仓库' : '目的仓 / 接柜仓' }}")
expect(source).toContain('emptyReturnWarningText')
})
2026-08-28 16:54:02 +08:00
it('mounts the new container customs panel only after loading completes', () => {
expect(source).toContain("detail.planStatus === 'COMPLETED' && overview?.loadingPlanId && canListCustoms")
expect(source).toContain('<ContainerCustomsPanel')
expect(source).toContain(':loading-plan-id="overview.loadingPlanId"')
expect(source).toContain(':can-manage="canManageCustoms"')
2026-08-18 17:40:40 +08:00
})
it('allows the overview panel to render only the section owned by each detail group', () => {
expect(overviewSource).toContain("sections?: Array<'exception' | 'documents' | 'pda'>")
expect(overviewSource).toContain("visibleSections.has('exception')")
expect(overviewSource).toContain("visibleSections.has('documents')")
expect(overviewSource).toContain("visibleSections.has('pda')")
})
2026-08-28 16:54:02 +08:00
it('removes the retired loading-list customs UI and calls', () => {
for (const retired of ['getLoadingDeclaration', 'uploadLoadingDeclaration', 'completeLoadingDeclaration', 'downloadLoadingDeclaration', 'CUSTOMS_DECLARATION']) {
expect(source).not.toContain(retired)
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('关联已放舱订舱')
2026-09-10 14:14:24 +08:00
expect(source).toContain('oceanBookingLinkOpen.value = true')
expect(source).toContain('<OceanBookingLinkDialog v-model="oceanBookingLinkOpen" :container="detail" @linked="load" />')
expect(source).not.toContain('linkContainerId')
})
2026-08-18 17:40:40 +08:00
})