This commit is contained in:
2026-09-07 15:07:39 +08:00
parent 189d1eae11
commit d488e9ab40
10 changed files with 260 additions and 10 deletions
@@ -7,6 +7,7 @@ import DataTable from '@/shared/components/DataTable.vue'
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
import { listAllPlatformWarehouses } from '@/modules/warehouses'
import {
changeContainerReceivingWarehouse,
convertLastMileContainerToInboundPlans,
convertLastMileOrderToInboundPlan,
getContainerPlanProgress,
@@ -21,6 +22,7 @@ vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) }))
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
vi.mock('../api/lastMileOrders', () => ({
listLastMileOrders: vi.fn(), listContainerPlanProgress: vi.fn(), getContainerPlanProgress: vi.fn(),
changeContainerReceivingWarehouse: vi.fn(),
convertLastMileOrderToInboundPlan: vi.fn(), convertLastMileContainerToInboundPlans: vi.fn(), transferLastMileOrders: vi.fn(),
downloadLastMileOrders: vi.fn(),
}))
@@ -38,6 +40,8 @@ const bookingId = '2092873463568597001'
const customerId = '2092618717861048321'
const summary = {
sourceContainerBookingId: bookingId, containerNo: 'CONT-15', totalOrderCount: 15,
overseasWarehouseId: '9007199254740995', overseasWarehouseCode: 'LAX01', overseasWarehouseName: '洛杉矶接柜仓',
assignmentVersion: 7, canChangeReceivingWarehouse: true, receivingWarehouseLockReason: null,
inboundCodeCount: 15, missingInboundCodeCount: 0, planCount: 10, plannedInboundCodeCount: 10,
pendingPlanCount: 5, plannedArrivalAt: '2026-09-01T08:00:00', allPlansReady: false,
blockingReason: '存在5票尚未生成有效入库计划',
@@ -53,6 +57,10 @@ const fbaWarehouse = {
},
addressSummary: 'CA Ontario',
}
const receivingWarehouse = {
...fbaWarehouse,
id: '9007199254740996', warehouseCode: 'LAX02', warehouseName: '洛杉矶二号接柜仓', warehouseType: 'OVERSEAS',
}
function selectedRow(overrides: Record<string, unknown> = {}) {
return { id: '2092873463568596994', customerId, sourceContainerBookingId: bookingId, orderNo: 'LMO-260827-0001', ...overrides }
}
@@ -108,6 +116,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
})
vi.mocked(convertLastMileOrderToInboundPlan).mockReset().mockResolvedValue({})
vi.mocked(convertLastMileContainerToInboundPlans).mockReset().mockResolvedValue([{ planNo: 'LIP-1' }, { planNo: 'LIP-2' }] as never)
vi.mocked(changeContainerReceivingWarehouse).mockReset().mockResolvedValue({ ...summary, overseasWarehouseId: '9007199254740996', overseasWarehouseCode: 'LAX02', overseasWarehouseName: '洛杉矶二号接柜仓', assignmentVersion: 8 })
vi.mocked(listAllPlatformWarehouses).mockReset().mockResolvedValue([fbaWarehouse] as never)
vi.mocked(transferLastMileOrders).mockReset().mockResolvedValue(2)
})
@@ -276,6 +285,88 @@ describe('LastMileOrdersView pending order and container conversion', () => {
expect(getContainerPlanProgress).not.toHaveBeenCalled()
})
it('shows the receiving warehouse and changes it from ACTIVE OVERSEAS master data with versioning', async () => {
vi.mocked(listAllPlatformWarehouses).mockResolvedValueOnce([receivingWarehouse] as never)
const wrapper = await mountView()
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
await flushPromises()
expect(wrapper.text()).toContain('海外接柜仓')
expect(wrapper.text()).toContain('LAX01 · 洛杉矶接柜仓')
await button(wrapper, '修改接柜仓')!.trigger('click')
await flushPromises()
expect(listAllPlatformWarehouses).toHaveBeenLastCalledWith({ warehouseType: 'OVERSEAS', status: 'ACTIVE' })
const warehouseSelect = wrapper.findAllComponents({ name: 'ElSelect' }).find((item) => item.props('placeholder') === '请选择 ACTIVE 海外接柜仓')!
warehouseSelect.vm.$emit('update:modelValue', '9007199254740996')
await wrapper.vm.$nextTick()
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认修改')) as HTMLButtonElement
confirm.click()
await flushPromises()
expect(changeContainerReceivingWarehouse).toHaveBeenCalledWith(bookingId, {
overseasWarehouseId: '9007199254740996', expectedVersion: 7, remark: undefined,
}, expect.stringMatching(/^container-receiving-warehouse-/))
})
it('does not expose a receiving-warehouse edit when the server locks the container', async () => {
vi.mocked(listContainerPlanProgress).mockResolvedValueOnce({
records: [{ ...summary, canChangeReceivingWarehouse: false, receivingWarehouseLockReason: '海外仓已开始拆柜收货' }],
page: 1, size: 10, total: 1, pages: 1,
})
const wrapper = await mountView()
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
await flushPromises()
expect(wrapper.text()).toContain('海外仓已开始拆柜收货')
expect(button(wrapper, '修改接柜仓')).toBeUndefined()
expect(changeContainerReceivingWarehouse).not.toHaveBeenCalled()
})
it('keeps conflict metadata visible and refreshes the stale container summary', async () => {
vi.mocked(listAllPlatformWarehouses).mockResolvedValueOnce([receivingWarehouse] as never)
vi.mocked(changeContainerReceivingWarehouse).mockRejectedValueOnce(new ApiResponseError(
'version conflict', 'A0409', 200, undefined, 'request-receiving', 'trace-receiving',
))
const wrapper = await mountView()
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
await flushPromises()
await button(wrapper, '修改接柜仓')!.trigger('click')
await flushPromises()
const warehouseSelect = wrapper.findAllComponents({ name: 'ElSelect' }).find((item) => item.props('placeholder') === '请选择 ACTIVE 海外接柜仓')!
warehouseSelect.vm.$emit('update:modelValue', '9007199254740996')
await wrapper.vm.$nextTick()
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认修改')) as HTMLButtonElement
confirm.click()
await flushPromises()
expect(wrapper.text()).toContain('接柜仓信息已被其他操作更新,请刷新后重试')
expect(wrapper.text()).toContain('request-receiving')
expect(wrapper.text()).toContain('trace-receiving')
expect(listContainerPlanProgress).toHaveBeenCalledTimes(2)
})
it('renders an explicit empty state when there are no ACTIVE OVERSEAS receiving warehouses', async () => {
vi.mocked(listAllPlatformWarehouses).mockResolvedValueOnce([])
const wrapper = await mountView()
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
await flushPromises()
await button(wrapper, '修改接柜仓')!.trigger('click')
await flushPromises()
expect(document.body.textContent).toContain('暂无可用的 ACTIVE 海外接柜仓')
expect(changeContainerReceivingWarehouse).not.toHaveBeenCalled()
})
it('keeps receiving-warehouse candidate failure metadata visible without an FBA fallback', async () => {
vi.mocked(listAllPlatformWarehouses).mockRejectedValueOnce(new ApiResponseError(
'receiving warehouse candidate failed', 'A0503', 200, undefined, 'request-receiving-options', 'trace-receiving-options',
))
const wrapper = await mountView()
await wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子'))!.trigger('click')
await flushPromises()
await button(wrapper, '修改接柜仓')!.trigger('click')
await flushPromises()
expect(document.body.textContent).toContain('request-receiving-options')
expect(document.body.textContent).toContain('trace-receiving-options')
expect(document.body.textContent).not.toContain('请选择目的 FBA 仓')
expect(changeContainerReceivingWarehouse).not.toHaveBeenCalled()
})
it('loads only active FBA master data and submits exact string fields for a cross-customer transfer', async () => {
const wrapper = await openPending()
await selectRows(wrapper, [selectedRow(), selectedRow({ id: '2092873463568596995', customerId: '2092618717861048322' })])