Merge remote-tracking branch 'origin/release/test' into dev

This commit is contained in:
2026-09-03 20:28:49 +08:00
2 changed files with 65 additions and 20 deletions
@@ -95,7 +95,6 @@ function containerPage(records: ContainerBookingResource[] = [container]) {
function mockContainerSearch(records: ContainerBookingResource[]) { function mockContainerSearch(records: ContainerBookingResource[]) {
vi.mocked(listSmartLoadingWorkbenchContainers).mockImplementation(async (query) => containerPage(records.filter((item) => ( vi.mocked(listSmartLoadingWorkbenchContainers).mockImplementation(async (query) => containerPage(records.filter((item) => (
item.planStatus !== 'COMPLETED' item.planStatus !== 'COMPLETED'
&& (!query.warehouseId || item.warehouseId === query.warehouseId)
&& query.channelGroupCodes.includes(resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)?.code || '') && query.channelGroupCodes.includes(resolveSmartLoadingChannelGroup(item.channelCode, item.channelName)?.code || '')
)))) ))))
} }
@@ -259,7 +258,7 @@ describe('GlobalSmartLoadingView', () => {
expect(wrapper.text()).not.toContain('美森正班海派') expect(wrapper.text()).not.toContain('美森正班海派')
}) })
it('moves the shipping warehouse into the query filters', async () => { it('keeps the shipping warehouse as a candidate-only query filter', async () => {
const dongguanContainer = { const dongguanContainer = {
...container, ...container,
id: '9007199254740998', id: '9007199254740998',
@@ -277,16 +276,18 @@ describe('GlobalSmartLoadingView', () => {
expect(wrapper.text()).toContain('MSCU-1') expect(wrapper.text()).toContain('MSCU-1')
expect(wrapper.text()).toContain('MSCU-DG') expect(wrapper.text()).toContain('MSCU-DG')
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' }) wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' })
await flushPromises() await flushPromises()
expect(wrapper.text()).toContain('MSCU-DG') expect(wrapper.text()).toContain('MSCU-DG')
expect(wrapper.text()).not.toContain('MSCU-1') expect(wrapper.text()).toContain('MSCU-1')
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], warehouseId: dongguanContainer.warehouseId, page: 1, size: 20 }) expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], warehouseId: dongguanContainer.warehouseId, page: 1, size: 20 })
expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount)
}) })
it('keeps the selected channel when the warehouse changes even if that warehouse has no matching container', async () => { it('keeps channel cabinets unchanged when another search field has no matching cabinet', async () => {
const dongguanOvertimeContainer = { const dongguanOvertimeContainer = {
...container, ...container,
id: '9007199254740998', id: '9007199254740998',
@@ -302,15 +303,49 @@ describe('GlobalSmartLoadingView', () => {
mockContainerSearch([container, dongguanOvertimeContainer]) mockContainerSearch([container, dongguanOvertimeContainer])
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises() await flushPromises()
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' }) wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' })
await flushPromises() await flushPromises()
expect(wrapper.find('.channel-tags .el-checkbox-button.is-checked').text()).toBe('美森正班') expect(wrapper.find('.channel-tags .el-checkbox-button.is-checked').text()).toBe('美森正班')
expect(wrapper.findAll('.container-option')).toHaveLength(0) expect(wrapper.findAll('.container-option')).toHaveLength(1)
expect(wrapper.text()).toContain('MSCU-1')
expect(wrapper.text()).not.toContain('MSCU-DG-MAX') expect(wrapper.text()).not.toContain('MSCU-DG-MAX')
expect(listSmartLoadingCandidates).toHaveBeenCalledTimes(2) expect(listSmartLoadingCandidates).toHaveBeenCalledTimes(2)
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], warehouseId: '18', page: 1, size: 20 }) expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], warehouseId: '18', page: 1, size: 20 })
expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount)
})
it('filters candidate details by inbound code without filtering channel cabinets', async () => {
const secondContainer = {
...container,
id: '9007199254740998',
planId: '9007199254740999',
cabinetNo: 'CB-2',
containerNo: 'MSCU-2',
}
vi.mocked(listContainers).mockResolvedValue({ records: [container, secondContainer], page: 1, size: 100, total: 2, pages: 1 })
mockContainerSearch([container, secondContainer])
vi.mocked(getSmartLoadingPlanByBooking).mockImplementation(async (bookingId) => ({ ...plan, bookingId }))
vi.mocked(listSmartLoadingCandidates)
.mockResolvedValueOnce(candidatePage([candidate]))
.mockResolvedValueOnce(candidatePage([{ ...candidate, inboundCode: 'NYEXX2609030002' }]))
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises()
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
wrapper.findComponent(QueryToolbar).vm.$emit('search', {
warehouseId: '', customerId: '', inboundCode: 'NYEXX2609030002', expectedArrivalRange: [], orderStatus: '',
})
await flushPromises()
expect(wrapper.get('.candidate-pane').text()).toContain('NYEXX2609030002')
expect(wrapper.findAll('.container-option')).toHaveLength(2)
expect(wrapper.text()).toContain('MSCU-1')
expect(wrapper.text()).toContain('MSCU-2')
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], inboundCode: 'NYEXX2609030002', page: 1, size: 20 })
expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount)
}) })
it('sends the active name-only customer selector to the server candidate query', () => { it('sends the active name-only customer selector to the server candidate query', () => {
@@ -336,6 +371,7 @@ describe('GlobalSmartLoadingView', () => {
.mockResolvedValueOnce(candidatePage([{ ...candidate, inboundId: '51', inboundCode: 'IN-MATCH', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-11T08:00:00' }])) .mockResolvedValueOnce(candidatePage([{ ...candidate, inboundId: '51', inboundCode: 'IN-MATCH', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-11T08:00:00' }]))
const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } })
await flushPromises() await flushPromises()
const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length
wrapper.getComponent(QueryToolbar).vm.$emit('search', { wrapper.getComponent(QueryToolbar).vm.$emit('search', {
customerId: '', inboundCode: '', orderStatus: 'WAIT_SPLIT', expectedArrivalRange: ['2026-09-10', '2026-09-12'], customerId: '', inboundCode: '', orderStatus: 'WAIT_SPLIT', expectedArrivalRange: ['2026-09-10', '2026-09-12'],
@@ -347,7 +383,8 @@ describe('GlobalSmartLoadingView', () => {
expect(candidates).not.toContain('IN-WRONG-STATUS') expect(candidates).not.toContain('IN-WRONG-STATUS')
expect(candidates).not.toContain('IN-WRONG-DATE') expect(candidates).not.toContain('IN-WRONG-DATE')
expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], expectedArrivalStart: '2026-09-10', expectedArrivalEnd: '2026-09-12', orderStatus: 'WAIT_SPLIT', page: 1, size: 20 }) expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], expectedArrivalStart: '2026-09-10', expectedArrivalEnd: '2026-09-12', orderStatus: 'WAIT_SPLIT', page: 1, size: 20 })
expect(listSmartLoadingWorkbenchContainers).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], expectedArrivalStart: '2026-09-10', expectedArrivalEnd: '2026-09-12', orderStatus: 'WAIT_SPLIT', page: 1, size: 200 }) expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount)
expect(listSmartLoadingWorkbenchContainers).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], page: 1, size: 200 })
}) })
it('paginates large inbound candidate results with twenty rows by default', async () => { it('paginates large inbound candidate results with twenty rows by default', async () => {
@@ -219,19 +219,19 @@ const candidateColumns: DataTableColumn[] = [
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 }, { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 },
{ key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 80 }, { key: 'availablePackageCount', prop: 'availablePackageCount', label: '可用箱数', width: 80 },
// { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' }, // { key: 'cartonIdentityStatus', label: '箱号归属', width: 105, slotName: 'cartonIdentityStatus' },
{ key: 'unitGrossWeightKg', label: '平均单箱毛重', width: 100, slotName: 'unitGrossWeight' }, { key: 'unitGrossWeightKg', label: '单箱毛重', width: 80, slotName: 'unitGrossWeight' },
{ key: 'totalGrossWeightKg', label: '总重量', width: 90, slotName: 'candidateTotalWeight' }, { key: 'totalGrossWeightKg', label: '重量', width: 90, slotName: 'candidateTotalWeight' },
{ key: 'averageUnitVolumeCbm', label: '平均单箱体积', width: 100, slotName: 'averageUnitVolume' }, { key: 'averageUnitVolumeCbm', label: '单箱体积', width: 80, slotName: 'averageUnitVolume' },
{ key: 'totalVolumeCbm', label: '总体积', width: 90, slotName: 'candidateTotalVolume' }, { key: 'totalVolumeCbm', label: '体积', width: 90, slotName: 'candidateTotalVolume' },
] ]
const itemColumns: DataTableColumn[] = [ const itemColumns: DataTableColumn[] = [
{ key: 'actions', label: '操作', width: 90, fixed: 'left', slotName: 'actions' }, { key: 'actions', label: '操作', width: 80, fixed: 'left', slotName: 'actions' },
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 190, fixed: 'left', showOverflowTooltip: true }, { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 140, fixed: 'left', showOverflowTooltip: true },
{ key: 'coLoadGroup', label: '同柜不可拆', minWidth: 210, slotName: 'coLoadGroup' }, { key: 'coLoadGroup', label: '同柜', minWidth: 80, slotName: 'coLoadGroup' },
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 140 }, { key: 'customerName', prop: 'customerName', label: '客户', minWidth: 70 },
{ key: 'packageCount', label: '分配箱数', width: 140, slotName: 'packageCount' }, { key: 'packageCount', label: '分配箱数', width: 100, slotName: 'packageCount' },
{ key: 'grossWeightKg', label: '毛重', width: 110, slotName: 'weight' }, { key: 'grossWeightKg', label: '毛重', width: 80, slotName: 'weight' },
{ key: 'volumeCbm', label: '体积', width: 105, slotName: 'volume' }, { key: 'volumeCbm', label: '体积', width: 80, slotName: 'volume' },
] ]
function formatNumber(value: number, digits = 3) { function formatNumber(value: number, digits = 3) {
@@ -306,6 +306,14 @@ function candidateQueryParams(): Parameters<typeof listSmartLoadingCandidates>[0
return params return params
} }
function containerQueryParams(page = 1): Parameters<typeof listSmartLoadingWorkbenchContainers>[0] {
return {
channelGroupCodes: [...selectedChannelCodes.value],
page,
size: 200,
}
}
async function loadCandidateData() { async function loadCandidateData() {
if (!selectedChannelCodes.value.length) return if (!selectedChannelCodes.value.length) return
const request = ++candidateRequest const request = ++candidateRequest
@@ -357,9 +365,9 @@ async function loadContainerData(preserved: Record<string, number> = {}) {
if (!selectedChannelCodes.value.length) return if (!selectedChannelCodes.value.length) return
const request = ++containerRequest const request = ++containerRequest
try { try {
const firstPage = await listSmartLoadingWorkbenchContainers({ ...candidateQueryParams(), page: 1, size: 200 }) const firstPage = await listSmartLoadingWorkbenchContainers(containerQueryParams())
const remainingPages = firstPage.pages > 1 const remainingPages = firstPage.pages > 1
? await Promise.all(Array.from({ length: firstPage.pages - 1 }, (_, index) => listSmartLoadingWorkbenchContainers({ ...candidateQueryParams(), page: index + 2, size: 200 }))) ? await Promise.all(Array.from({ length: firstPage.pages - 1 }, (_, index) => listSmartLoadingWorkbenchContainers(containerQueryParams(index + 2))))
: [] : []
if (request !== containerRequest) return if (request !== containerRequest) return
containers.value = [firstPage, ...remainingPages].flatMap((page) => page.records) containers.value = [firstPage, ...remainingPages].flatMap((page) => page.records)
@@ -431,7 +439,7 @@ async function selectChannels(codes: string[]) {
async function applyFilters(model: QueryModel) { async function applyFilters(model: QueryModel) {
appliedQuery.value = { ...model } appliedQuery.value = { ...model }
candidatePage.value = 1 candidatePage.value = 1
await loadWorkbenchData() await loadCandidateData()
} }
async function changeCandidatePage(page: number) { async function changeCandidatePage(page: number) {