优化
This commit is contained in:
@@ -8,11 +8,13 @@ import { resolve } from 'node:path'
|
|||||||
import ContainersView from './ContainersView.vue'
|
import ContainersView from './ContainersView.vue'
|
||||||
import { listAllPlatformWarehouses } from '@/modules/warehouses'
|
import { listAllPlatformWarehouses } from '@/modules/warehouses'
|
||||||
import { listPlatformPartners } from '@/modules/master-data'
|
import { listPlatformPartners } from '@/modules/master-data'
|
||||||
|
import { listSuppliers } from '@/modules/suppliers'
|
||||||
import { listOceanBookings } from '@/modules/ocean-bookings'
|
import { listOceanBookings } from '@/modules/ocean-bookings'
|
||||||
import { getContainer, getContainerOverview, listContainers } from '../api/platformContainerLoading'
|
import { getContainer, getContainerOverview, listContainers } from '../api/platformContainerLoading'
|
||||||
|
|
||||||
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
|
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
|
||||||
vi.mock('@/modules/master-data', () => ({ listPlatformPartners: vi.fn() }))
|
vi.mock('@/modules/master-data', () => ({ listPlatformPartners: vi.fn() }))
|
||||||
|
vi.mock('@/modules/suppliers', () => ({ listSuppliers: vi.fn() }))
|
||||||
vi.mock('@/modules/ocean-bookings', () => ({ listOceanBookings: vi.fn() }))
|
vi.mock('@/modules/ocean-bookings', () => ({ listOceanBookings: vi.fn() }))
|
||||||
vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }) }))
|
vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }) }))
|
||||||
const accessCan = vi.hoisted(() => vi.fn())
|
const accessCan = vi.hoisted(() => vi.fn())
|
||||||
@@ -34,6 +36,7 @@ describe('ContainersView', () => {
|
|||||||
accessCan.mockReset(); accessCan.mockReturnValue(true)
|
accessCan.mockReset(); accessCan.mockReturnValue(true)
|
||||||
vi.mocked(listAllPlatformWarehouses).mockResolvedValue([])
|
vi.mocked(listAllPlatformWarehouses).mockResolvedValue([])
|
||||||
vi.mocked(listPlatformPartners).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 1 })
|
vi.mocked(listPlatformPartners).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 1 })
|
||||||
|
vi.mocked(listSuppliers).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 1 })
|
||||||
vi.mocked(listOceanBookings).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 1 })
|
vi.mocked(listOceanBookings).mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 1 })
|
||||||
vi.mocked(listContainers).mockResolvedValue({ records: [{ id: '9001', planId: null, bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', containerType: '40HQ', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 28000, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null }], page: 1, size: 20, total: 1, pages: 1 })
|
vi.mocked(listContainers).mockResolvedValue({ records: [{ id: '9001', planId: null, bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', containerType: '40HQ', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 28000, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null }], page: 1, size: 20, total: 1, pages: 1 })
|
||||||
vi.mocked(getContainer).mockResolvedValue({ id: '9001', planId: null, bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', containerType: '40HQ', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 28000, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null })
|
vi.mocked(getContainer).mockResolvedValue({ id: '9001', planId: null, bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', containerType: '40HQ', planStatus: 'WAIT_LOADING', version: 0, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: '宁波', destinationPortCode: null, destinationPortName: '长滩', destinationWarehouseCode: null, destinationWarehouseName: null, capacityCbm: 68, maxWeightKg: 28000, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null })
|
||||||
@@ -247,3 +250,14 @@ describe('ContainersView', () => {
|
|||||||
expect(source).toContain('unlinkContainerOceanBooking(target.id, target.version, unlinkReason.value.trim())')
|
expect(source).toContain('unlinkContainerOceanBooking(target.id, target.version, unlinkReason.value.trim())')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('ContainersView trucking supplier contract', () => {
|
||||||
|
it('uses active TRUCKING suppliers without allowing free-text carrier names', () => {
|
||||||
|
const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/ContainersView.vue'), 'utf8')
|
||||||
|
expect(source).toContain("listSuppliers({ page: 1, size: 200, category: 'TRUCKING', status: 'ACTIVE' })")
|
||||||
|
expect(source).toContain("supplier.category === 'TRUCKING' && supplier.status === 'ACTIVE'")
|
||||||
|
expect(source).toContain('selectTruckingSupplier')
|
||||||
|
expect(source).toContain('请选择有效的合作中拖车供应商')
|
||||||
|
expect(source).not.toContain('allow-create')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|||||||
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
|
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
|
||||||
import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from '@/modules/warehouses'
|
import { listAllPlatformWarehouses, type PlatformWarehouseSummaryResource } from '@/modules/warehouses'
|
||||||
import { listPlatformPartners, type PlatformPartnerResource } from '@/modules/master-data'
|
import { listPlatformPartners, type PlatformPartnerResource } from '@/modules/master-data'
|
||||||
|
import { listSuppliers, type Supplier } from '@/modules/suppliers'
|
||||||
import { getOceanBooking, listOceanBookings, listWarehouseLogisticsChannels, type OceanBookingDetail, type OceanBookingResource, type OceanLogisticsChannel } from '@/modules/ocean-bookings'
|
import { getOceanBooking, listOceanBookings, listWarehouseLogisticsChannels, type OceanBookingDetail, type OceanBookingResource, type OceanLogisticsChannel } from '@/modules/ocean-bookings'
|
||||||
import { uploadLoadingDeclaration } from '@/modules/loading-lists'
|
import { uploadLoadingDeclaration } from '@/modules/loading-lists'
|
||||||
import AdminPage from '@/shared/components/AdminPage.vue'
|
import AdminPage from '@/shared/components/AdminPage.vue'
|
||||||
@@ -33,6 +34,7 @@ const customersLoading = ref(false)
|
|||||||
const warehouses = ref<PlatformWarehouseSummaryResource[]>([])
|
const warehouses = ref<PlatformWarehouseSummaryResource[]>([])
|
||||||
const destinationWarehouses = ref<PlatformWarehouseSummaryResource[]>([])
|
const destinationWarehouses = ref<PlatformWarehouseSummaryResource[]>([])
|
||||||
const partners = ref<PlatformPartnerResource[]>([])
|
const partners = ref<PlatformPartnerResource[]>([])
|
||||||
|
const truckingSuppliers = ref<Supplier[]>([])
|
||||||
const oceanBookings = ref<OceanBookingResource[]>([])
|
const oceanBookings = ref<OceanBookingResource[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const warehouseLoading = ref(false)
|
const warehouseLoading = ref(false)
|
||||||
@@ -88,6 +90,7 @@ const linkError = ref<ApiErrorDetails | null>(null)
|
|||||||
const linkTarget = ref<ContainerBookingResource | null>(null)
|
const linkTarget = ref<ContainerBookingResource | null>(null)
|
||||||
const linkCandidateKey = ref('')
|
const linkCandidateKey = ref('')
|
||||||
const linkCandidates = ref<Array<{ key: string; oceanBookingId: string; requirementId: string; label: string; detail: OceanBookingDetail }>>([])
|
const linkCandidates = ref<Array<{ key: string; oceanBookingId: string; requirementId: string; label: string; detail: OceanBookingDetail }>>([])
|
||||||
|
const selectedTruckingSupplierId = ref('')
|
||||||
const unlinkOpen = ref(false)
|
const unlinkOpen = ref(false)
|
||||||
const unlinkReason = ref('')
|
const unlinkReason = ref('')
|
||||||
let listSequence = 0
|
let listSequence = 0
|
||||||
@@ -109,6 +112,7 @@ const rules: FormRules<ContainerForm> = {
|
|||||||
cabinetNo: [{ required: true, message: '请输入柜次号', trigger: 'blur' }],
|
cabinetNo: [{ required: true, message: '请输入柜次号', trigger: 'blur' }],
|
||||||
warehouseId: [{ required: true, message: '请选择装柜仓库', trigger: 'change' }],
|
warehouseId: [{ required: true, message: '请选择装柜仓库', trigger: 'change' }],
|
||||||
containerType: [{ required: true, message: '请选择柜型', trigger: 'change' }],
|
containerType: [{ required: true, message: '请选择柜型', trigger: 'change' }],
|
||||||
|
terminalTruckingCompany: [{ validator: (_rule, value, callback) => callback(!value || selectedTruckingSupplierId.value ? undefined : new Error('请选择有效的合作中拖车供应商')), trigger: 'change' }],
|
||||||
}
|
}
|
||||||
|
|
||||||
const canView = computed(() => access.can('platform:firstleg:container:list'))
|
const canView = computed(() => access.can('platform:firstleg:container:list'))
|
||||||
@@ -124,7 +128,7 @@ const documentDialogTitle = computed(() => ({ DELIVERY_ORDER: 'DO 资料', PICKU
|
|||||||
const exceptionDialogTitle = computed(() => overview.value?.exception.active ? '取消货柜异常' : '标记货柜异常')
|
const exceptionDialogTitle = computed(() => overview.value?.exception.active ? '取消货柜异常' : '标记货柜异常')
|
||||||
const transportOnly = computed(() => Boolean(form.id) && detail.value?.planStatus === 'LOADING')
|
const transportOnly = computed(() => Boolean(form.id) && detail.value?.planStatus === 'LOADING')
|
||||||
const shippingCompanies = computed(() => partners.value.filter((partner) => partner.status === 'ACTIVE' && partner.types.includes('CARRIER')))
|
const shippingCompanies = computed(() => partners.value.filter((partner) => partner.status === 'ACTIVE' && partner.types.includes('CARRIER')))
|
||||||
const truckingCompanies = computed(() => partners.value.filter((partner) => partner.status === 'ACTIVE' && partner.types.some((type) => ['CARRIER', 'SUPPLIER'].includes(type))))
|
const truckingCompanies = computed(() => truckingSuppliers.value.filter((supplier) => supplier.status === 'ACTIVE' && supplier.category === 'TRUCKING'))
|
||||||
const simulationRules: FormRules<ContainerSimulationCreateRequest> = {
|
const simulationRules: FormRules<ContainerSimulationCreateRequest> = {
|
||||||
warehouseId: [{ required: true, message: '请选择装柜仓库', trigger: 'change' }],
|
warehouseId: [{ required: true, message: '请选择装柜仓库', trigger: 'change' }],
|
||||||
channelCode: [{ required: true, message: '请选择物流渠道', trigger: 'change' }],
|
channelCode: [{ required: true, message: '请选择物流渠道', trigger: 'change' }],
|
||||||
@@ -196,6 +200,7 @@ function containerViewLabel(row: ContainerBookingResource) {
|
|||||||
}
|
}
|
||||||
function replaceForm(source: ContainerBookingResource | null) {
|
function replaceForm(source: ContainerBookingResource | null) {
|
||||||
Object.assign(form, emptyForm(), source || {})
|
Object.assign(form, emptyForm(), source || {})
|
||||||
|
selectedTruckingSupplierId.value = truckingCompanies.value.find((supplier) => supplier.name === form.terminalTruckingCompany)?.id || ''
|
||||||
}
|
}
|
||||||
function payload(): ContainerSaveRequest {
|
function payload(): ContainerSaveRequest {
|
||||||
const selectedWarehouse = warehouses.value.find((warehouse) => String(warehouse.id) === form.warehouseId)
|
const selectedWarehouse = warehouses.value.find((warehouse) => String(warehouse.id) === form.warehouseId)
|
||||||
@@ -219,6 +224,11 @@ function payload(): ContainerSaveRequest {
|
|||||||
function partnerLabel(partner: PlatformPartnerResource) {
|
function partnerLabel(partner: PlatformPartnerResource) {
|
||||||
return `${partner.partnerName}${partner.partnerCode ? `(${partner.partnerCode})` : ''}`
|
return `${partner.partnerName}${partner.partnerCode ? `(${partner.partnerCode})` : ''}`
|
||||||
}
|
}
|
||||||
|
function selectTruckingSupplier(supplierId: string) {
|
||||||
|
const supplier = truckingCompanies.value.find((item) => item.id === supplierId)
|
||||||
|
selectedTruckingSupplierId.value = supplier?.id || ''
|
||||||
|
form.terminalTruckingCompany = supplier?.name || null
|
||||||
|
}
|
||||||
function portLabel(port: PortOption) {
|
function portLabel(port: PortOption) {
|
||||||
return port.name && port.code ? `${port.name}(${port.code})` : port.name || port.code || '未命名港口'
|
return port.name && port.code ? `${port.name}(${port.code})` : port.name || port.code || '未命名港口'
|
||||||
}
|
}
|
||||||
@@ -278,12 +288,15 @@ async function loadBusinessOptions() {
|
|||||||
businessOptionsLoading.value = true
|
businessOptionsLoading.value = true
|
||||||
businessOptionsError.value = ''
|
businessOptionsError.value = ''
|
||||||
const errors: string[] = []
|
const errors: string[] = []
|
||||||
const [partnerResult, bookingResult] = await Promise.allSettled([
|
const [partnerResult, supplierResult, bookingResult] = await Promise.allSettled([
|
||||||
loadAllPartners(),
|
loadAllPartners(),
|
||||||
|
listSuppliers({ page: 1, size: 200, category: 'TRUCKING', status: 'ACTIVE' }),
|
||||||
loadAllOceanBookings(),
|
loadAllOceanBookings(),
|
||||||
])
|
])
|
||||||
if (partnerResult.status === 'fulfilled') partners.value = partnerResult.value
|
if (partnerResult.status === 'fulfilled') partners.value = partnerResult.value
|
||||||
else errors.push(getApiErrorDetails(partnerResult.reason, '船公司与拖车公司加载失败').message)
|
else errors.push(getApiErrorDetails(partnerResult.reason, '船公司与拖车公司加载失败').message)
|
||||||
|
if (supplierResult.status === 'fulfilled') truckingSuppliers.value = supplierResult.value.records.filter((supplier) => supplier.category === 'TRUCKING' && supplier.status === 'ACTIVE')
|
||||||
|
else errors.push(getApiErrorDetails(supplierResult.reason, '拖车供应商加载失败').message)
|
||||||
if (bookingResult.status === 'fulfilled') oceanBookings.value = bookingResult.value
|
if (bookingResult.status === 'fulfilled') oceanBookings.value = bookingResult.value
|
||||||
else errors.push(getApiErrorDetails(bookingResult.reason, '港口候选加载失败').message)
|
else errors.push(getApiErrorDetails(bookingResult.reason, '港口候选加载失败').message)
|
||||||
businessOptionsError.value = errors.join(';')
|
businessOptionsError.value = errors.join(';')
|
||||||
@@ -655,7 +668,7 @@ onMounted(() => { void Promise.all([loadCustomers(), loadWarehouses(), loadBusin
|
|||||||
<el-form-item label="目的仓"><el-select :model-value="destinationWarehouseValue" filterable clearable placeholder="请选择目的仓" :loading="warehouseLoading" loading-text="目的仓加载中" no-data-text="暂无可用海外/FBA仓库" :disabled="transportOnly" @update:model-value="selectDestinationWarehouse(String($event || ''))"><el-option v-if="destinationWarehouseValue && !destinationWarehouses.some((warehouse) => warehouse.warehouseCode === destinationWarehouseValue)" :label="form.destinationWarehouseName || form.destinationWarehouseCode || '原目的仓'" :value="destinationWarehouseValue" /><el-option v-for="warehouse in destinationWarehouses" :key="String(warehouse.id)" :label="`${warehouse.warehouseName}(${warehouse.warehouseCode})`" :value="warehouse.warehouseCode" /></el-select></el-form-item>
|
<el-form-item label="目的仓"><el-select :model-value="destinationWarehouseValue" filterable clearable placeholder="请选择目的仓" :loading="warehouseLoading" loading-text="目的仓加载中" no-data-text="暂无可用海外/FBA仓库" :disabled="transportOnly" @update:model-value="selectDestinationWarehouse(String($event || ''))"><el-option v-if="destinationWarehouseValue && !destinationWarehouses.some((warehouse) => warehouse.warehouseCode === destinationWarehouseValue)" :label="form.destinationWarehouseName || form.destinationWarehouseCode || '原目的仓'" :value="destinationWarehouseValue" /><el-option v-for="warehouse in destinationWarehouses" :key="String(warehouse.id)" :label="`${warehouse.warehouseName}(${warehouse.warehouseCode})`" :value="warehouse.warehouseCode" /></el-select></el-form-item>
|
||||||
<el-form-item label="货柜容量(m³)"><el-input-number v-model="form.capacityCbm" :min="0.001" :precision="3" controls-position="right" placeholder="请输入货柜容量" :disabled="transportOnly" /></el-form-item>
|
<el-form-item label="货柜容量(m³)"><el-input-number v-model="form.capacityCbm" :min="0.001" :precision="3" controls-position="right" placeholder="请输入货柜容量" :disabled="transportOnly" /></el-form-item>
|
||||||
<el-form-item label="重量上限(kg)"><el-input-number v-model="form.maxWeightKg" :min="0.001" :precision="3" controls-position="right" placeholder="请输入重量上限" :disabled="transportOnly" /></el-form-item>
|
<el-form-item label="重量上限(kg)"><el-input-number v-model="form.maxWeightKg" :min="0.001" :precision="3" controls-position="right" placeholder="请输入重量上限" :disabled="transportOnly" /></el-form-item>
|
||||||
<el-form-item label="拖车公司"><el-select v-model="form.terminalTruckingCompany" filterable clearable allow-create default-first-option placeholder="请选择或输入拖车公司" :loading="businessOptionsLoading" loading-text="拖车公司加载中" no-data-text="暂无合作单位,可直接输入" :disabled="transportOnly"><el-option v-for="partner in truckingCompanies" :key="String(partner.id)" :label="partnerLabel(partner)" :value="partner.partnerName" /></el-select></el-form-item>
|
<el-form-item label="拖车公司" prop="terminalTruckingCompany"><el-select :model-value="selectedTruckingSupplierId" filterable clearable placeholder="请选择拖车供应商" :loading="businessOptionsLoading" loading-text="拖车供应商加载中" no-data-text="暂无可用拖车供应商" :disabled="transportOnly" @update:model-value="selectTruckingSupplier(String($event || ''))"><el-option v-if="form.terminalTruckingCompany && !selectedTruckingSupplierId" :label="`${form.terminalTruckingCompany}(需重新选择)`" value="" disabled /><el-option v-for="supplier in truckingCompanies" :key="supplier.id" :label="supplier.name" :value="supplier.id" /></el-select></el-form-item>
|
||||||
<el-form-item label="计划装柜时间"><el-date-picker v-model="form.loadingAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择计划装柜时间" :disabled="transportOnly" /></el-form-item>
|
<el-form-item label="计划装柜时间"><el-date-picker v-model="form.loadingAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择计划装柜时间" :disabled="transportOnly" /></el-form-item>
|
||||||
<el-form-item label="预计开船时间"><el-date-picker v-model="form.estimatedSailingAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计开船时间" :disabled="transportOnly" /></el-form-item>
|
<el-form-item label="预计开船时间"><el-date-picker v-model="form.estimatedSailingAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计开船时间" :disabled="transportOnly" /></el-form-item>
|
||||||
<el-form-item label="预计到港时间"><el-date-picker v-model="form.estimatedPortArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计到港时间" :disabled="transportOnly" /></el-form-item>
|
<el-form-item label="预计到港时间"><el-date-picker v-model="form.estimatedPortArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计到港时间" :disabled="transportOnly" /></el-form-item>
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|||||||
import { http } from '@/shared/api/http'
|
import { http } from '@/shared/api/http'
|
||||||
import { createCustomsCase, listCustomsCases, submitCustomsCase } from './platformCustoms'
|
import { createCustomsCase, listCustomsCases, submitCustomsCase } from './platformCustoms'
|
||||||
vi.mock('@/shared/api/http', async () => { const actual = await vi.importActual<typeof import('@/shared/api/http')>('@/shared/api/http'); return { ...actual, http: { get: vi.fn(), post: vi.fn() } } })
|
vi.mock('@/shared/api/http', async () => { const actual = await vi.importActual<typeof import('@/shared/api/http')>('@/shared/api/http'); return { ...actual, http: { get: vi.fn(), post: vi.fn() } } })
|
||||||
describe('platformCustoms', () => { beforeEach(() => vi.clearAllMocks()); it('normalizes case ids', async () => { vi.mocked(http.get).mockResolvedValue({ code: '200', data: { records: [{ id: 1, customsNo: 'C1', orderId: 2, inboundId: 3, checklistVersionId: 4, checklistCode: 'US', checklistVersionNo: 'V1', status: 'DRAFT', version: '1' }], page: 1, size: 20, total: 1, pages: 1 } }); const page = await listCustomsCases({ page: 1, size: 20 }); expect(page.records[0]).toMatchObject({ id: '1', orderId: '2', inboundId: '3', checklistVersionId: '4', version: 1 }) }); it('writes with idempotency and version', async () => { const detail = { customsCase: { id: 1, customsNo: 'C1', orderId: 2, inboundId: 3, checklistVersionId: 4, checklistCode: 'US', checklistVersionNo: 'V1', status: 'DRAFT', version: 0 }, documents: [], supplements: [], submissionBatches: [], receipts: [], history: [] }; vi.mocked(http.post).mockResolvedValueOnce({ code: '200', data: detail }).mockResolvedValueOnce({ code: '200', data: {} }); await createCustomsCase({ customerId: '8', orderId: '2', inboundId: '3', checklistVersionId: '4', expectedVersion: 0 }); await submitCustomsCase('1', { customsAgentName: '代理', expectedVersion: 2 }); expect(vi.mocked(http.post).mock.calls[0]?.[2]).toMatchObject({ headers: { 'Idempotency-Key': expect.any(String) } }); expect(vi.mocked(http.post).mock.calls[1]?.[1]).toMatchObject({ expectedVersion: 2 }) }) })
|
describe('platformCustoms', () => { beforeEach(() => vi.clearAllMocks()); it('normalizes case ids', async () => { vi.mocked(http.get).mockResolvedValue({ code: '200', data: { records: [{ id: 1, customsNo: 'C1', orderId: 2, inboundId: 3, checklistVersionId: 4, checklistCode: 'US', checklistVersionNo: 'V1', status: 'DRAFT', version: '1' }], page: 1, size: 20, total: 1, pages: 1 } }); const page = await listCustomsCases({ page: 1, size: 20 }); expect(page.records[0]).toMatchObject({ id: '1', orderId: '2', inboundId: '3', checklistVersionId: '4', version: 1 }) }); it('writes with idempotency, supplier identity and version', async () => { const detail = { customsCase: { id: 1, customsNo: 'C1', orderId: 2, inboundId: 3, checklistVersionId: 4, checklistCode: 'US', checklistVersionNo: 'V1', status: 'DRAFT', version: 0 }, documents: [], supplements: [], submissionBatches: [], receipts: [], history: [] }; vi.mocked(http.post).mockResolvedValueOnce({ code: '200', data: detail }).mockResolvedValueOnce({ code: '200', data: {} }); await createCustomsCase({ customerId: '8', orderId: '2', inboundId: '3', checklistVersionId: '4', expectedVersion: 0 }); await submitCustomsCase('1', { customsAgentId: '81', customsAgentName: '代理', expectedVersion: 2 }); expect(vi.mocked(http.post).mock.calls[0]?.[2]).toMatchObject({ headers: { 'Idempotency-Key': expect.any(String) } }); expect(vi.mocked(http.post).mock.calls[1]?.[1]).toMatchObject({ customsAgentId: '81', customsAgentName: '代理', expectedVersion: 2 }) }) })
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ const source = readFileSync(resolve(process.cwd(), 'src/modules/customs/pages/Cu
|
|||||||
describe('CustomsCasesView', () => {
|
describe('CustomsCasesView', () => {
|
||||||
it('uses shared list and dialog components', () => { expect(source).toContain('QueryToolbar'); expect(source).toContain('DataTable'); expect(source).toContain('AppDialog'); expect(source).toContain("fixed: 'left'") })
|
it('uses shared list and dialog components', () => { expect(source).toContain('QueryToolbar'); expect(source).toContain('DataTable'); expect(source).toContain('AppDialog'); expect(source).toContain("fixed: 'left'") })
|
||||||
it('supports detail, history and agent submission', () => { expect(source).toContain('getCustomsCase'); expect(source).toContain('submitCustomsCase'); expect(source).toContain('detail.history') })
|
it('supports detail, history and agent submission', () => { expect(source).toContain('getCustomsCase'); expect(source).toContain('submitCustomsCase'); expect(source).toContain('detail.history') })
|
||||||
|
it('selects an active customs supplier and submits its stable identity and name snapshot', () => {
|
||||||
|
expect(source).toContain("listSuppliers({ page: 1, size: 200, category: 'CUSTOMS', status: 'ACTIVE' })")
|
||||||
|
expect(source).toContain("supplier.category === 'CUSTOMS' && supplier.status === 'ACTIVE'")
|
||||||
|
expect(source).toContain(':model-value="submitForm.customsAgentId" filterable clearable')
|
||||||
|
expect(source).toContain("submitForm.customsAgentId = supplier?.id || ''")
|
||||||
|
expect(source).toContain("submitForm.customsAgentName = supplier?.name || ''")
|
||||||
|
expect(source).not.toContain('v-model="submitForm.customsAgentName"')
|
||||||
|
expect(source).toContain("batch.customsAgentName || '服务端未返回名称快照'")
|
||||||
|
})
|
||||||
it('localizes backend statuses and timeline actions throughout the page', () => {
|
it('localizes backend statuses and timeline actions throughout the page', () => {
|
||||||
expect(source).toContain('customsCaseStatusOptions')
|
expect(source).toContain('customsCaseStatusOptions')
|
||||||
expect(source).toContain(':status="customsStatusLabel(row.status)"')
|
expect(source).toContain(':status="customsStatusLabel(row.status)"')
|
||||||
|
|||||||
Reference in New Issue
Block a user