feat: confirm container inbound plans
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
changeContainerReceivingWarehouse,
|
||||
createManualInboundPlan,
|
||||
convertLastMileContainerToInboundPlans,
|
||||
getContainerInboundPlanPreview,
|
||||
getContainerPlanProgress,
|
||||
listContainerPlanProgress,
|
||||
listLastMileOrders,
|
||||
@@ -30,7 +31,7 @@ vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
|
||||
vi.mock('../api/lastMileOrders', () => ({
|
||||
listLastMileOrders: vi.fn(), listContainerPlanProgress: vi.fn(), getContainerPlanProgress: vi.fn(),
|
||||
changeContainerReceivingWarehouse: vi.fn(),
|
||||
createManualInboundPlan: vi.fn(), convertLastMileContainerToInboundPlans: vi.fn(), transferLastMileOrders: vi.fn(),
|
||||
createManualInboundPlan: vi.fn(), convertLastMileContainerToInboundPlans: vi.fn(), getContainerInboundPlanPreview: vi.fn(), transferLastMileOrders: vi.fn(),
|
||||
getLastMileOrderManualNodes: vi.fn(), updateLastMileOrderHold: vi.fn(), updateLastMileOrderManualNodes: vi.fn(),
|
||||
downloadLastMileOrders: vi.fn(),
|
||||
}))
|
||||
@@ -65,6 +66,26 @@ const fbaWarehouse = {
|
||||
},
|
||||
addressSummary: 'CA Ontario',
|
||||
}
|
||||
const preview = {
|
||||
sourceContainerBookingId: bookingId, containerNo: 'CONT-15',
|
||||
overseasWarehouseId: summary.overseasWarehouseId, overseasWarehouseCode: summary.overseasWarehouseCode,
|
||||
overseasWarehouseName: summary.overseasWarehouseName, snapshotVersion: 9, snapshotHash: 'a'.repeat(64),
|
||||
totalOrderCount: 5, readyOrderCount: 5, blockedOrderCount: 0, pendingOrderCount: 5,
|
||||
pendingPalletCount: 5, existingPlannedPalletCount: 0, totalSuggestedPalletCount: 5,
|
||||
totalPlannedPalletCount: 0, ready: true,
|
||||
groups: [{
|
||||
destinationType: 'FBA', destinationWarehouseId: null, destinationWarehouseCode: 'ONT8',
|
||||
destinationWarehouseName: 'Ontario FBA', orderCount: 5, pendingOrderCount: 5, pendingPalletCount: 5,
|
||||
existingPlannedPalletCount: 0, suggestedPalletCount: 5, plannedPalletCount: 0,
|
||||
orders: [{
|
||||
customerId, customerName: '测试客户', orderId: '2092873463568596994', businessOrderNo: 'LMO-1',
|
||||
inboundCode: 'IN-1', orderType: 'FIRST_LEG', destinationType: 'FBA', destinationWarehouseId: null,
|
||||
destinationWarehouseCode: 'ONT8', destinationWarehouseName: 'Ontario FBA', packageCount: 10,
|
||||
volumeCbm: 1, suggestedPalletCount: 5, plannedPalletCount: null, existingPlanNo: null,
|
||||
planStatus: null, orderVersion: 3, readiness: 'READY', blockers: [],
|
||||
}],
|
||||
}],
|
||||
}
|
||||
const receivingWarehouse = {
|
||||
...fbaWarehouse,
|
||||
id: '9007199254740996', warehouseCode: 'LAX02', warehouseName: '洛杉矶二号接柜仓', warehouseType: 'OVERSEAS',
|
||||
@@ -118,6 +139,8 @@ async function chooseContainer(wrapper: Awaited<ReturnType<typeof mountView>>) {
|
||||
const containerSelect = selects[selects.length - 1]
|
||||
expect(containerSelect).toBeDefined()
|
||||
containerSelect.vm.$emit('update:modelValue', bookingId)
|
||||
containerSelect.vm.$emit('change', bookingId)
|
||||
await flushPromises()
|
||||
await wrapper.vm.$nextTick()
|
||||
}
|
||||
|
||||
@@ -133,10 +156,14 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
orders: [{ orderId: '2092873463568596994', customerId, orderNo: 'LMO-1', inboundCode: 'IN-1', orderStatus: 'INBOUND_PLANNED', planId: '2092873463568597010', planNo: 'LIP-1', planStatus: 'PENDING_RECEIVING', planValid: true, plannedArrivalAt: '2026-09-01T08:00:00' }],
|
||||
})
|
||||
vi.mocked(createManualInboundPlan).mockReset().mockResolvedValue({} as never)
|
||||
vi.mocked(getContainerInboundPlanPreview).mockReset().mockResolvedValue(preview as never)
|
||||
vi.mocked(getLastMileOrderManualNodes).mockReset().mockResolvedValue({ orderId: '2092873463568596994', manualOverrideEnabled: false, nodes: [], version: 0 })
|
||||
vi.mocked(updateLastMileOrderHold).mockReset().mockResolvedValue({ orderId: '2092873463568596994', held: true, chargeable: true, version: 1 })
|
||||
vi.mocked(updateLastMileOrderManualNodes).mockReset().mockResolvedValue({ orderId: '2092873463568596994', manualOverrideEnabled: true, nodes: [], version: 1 })
|
||||
vi.mocked(convertLastMileContainerToInboundPlans).mockReset().mockResolvedValue([{ planNo: 'LIP-1' }, { planNo: 'LIP-2' }] as never)
|
||||
vi.mocked(convertLastMileContainerToInboundPlans).mockReset().mockResolvedValue([
|
||||
{ planNo: 'LIP-1', pallets: [{ id: 'PALLET-1', palletNo: 'PLT-001' }] },
|
||||
{ planNo: 'LIP-2', pallets: [{ id: 'PALLET-2', palletNo: 'PLT-002' }] },
|
||||
] 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)
|
||||
@@ -253,7 +280,8 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
expect(listContainerPlanProgress).toHaveBeenCalledTimes(1)
|
||||
expect(listContainerPlanProgress).toHaveBeenCalledWith({ page: 1, size: 50, containerNo: undefined })
|
||||
expect(document.body.textContent).toContain('选择待分配柜')
|
||||
expect(document.body.textContent).toContain('将为所选柜内所有待分配尾程订单分别生成独立入库计划')
|
||||
expect(document.body.textContent).toContain('确认后整柜只提交一次生成命令')
|
||||
expect(getContainerInboundPlanPreview).not.toHaveBeenCalled()
|
||||
expect(convertLastMileContainerToInboundPlans).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -261,14 +289,22 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
const wrapper = await openPending()
|
||||
await openContainerDialog(wrapper)
|
||||
await chooseContainer(wrapper)
|
||||
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认按柜生成')) as HTMLButtonElement
|
||||
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认生成')) as HTMLButtonElement
|
||||
expect(confirm.disabled).toBe(false)
|
||||
confirm.click()
|
||||
await flushPromises()
|
||||
expect(convertLastMileContainerToInboundPlans).toHaveBeenCalledTimes(1)
|
||||
expect(convertLastMileContainerToInboundPlans).toHaveBeenCalledWith({ sourceContainerBookingId: bookingId })
|
||||
expect(getContainerInboundPlanPreview).toHaveBeenCalledTimes(2)
|
||||
expect(getContainerInboundPlanPreview).toHaveBeenNthCalledWith(1, bookingId)
|
||||
expect(getContainerInboundPlanPreview).toHaveBeenNthCalledWith(2, bookingId)
|
||||
expect(convertLastMileContainerToInboundPlans).toHaveBeenCalledWith({
|
||||
sourceContainerBookingId: bookingId,
|
||||
snapshotVersion: 9,
|
||||
snapshotHash: 'a'.repeat(64),
|
||||
}, expect.stringContaining('container-inbound-plan-'))
|
||||
expect(createManualInboundPlan).not.toHaveBeenCalled()
|
||||
expect(document.body.textContent).toContain('已按柜生成 2 个独立入库计划')
|
||||
expect(document.body.textContent).toContain('新建 2 个 / 已有 0 个')
|
||||
expect(document.body.textContent).toContain('PLT-001')
|
||||
expect(listLastMileOrders).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
@@ -279,7 +315,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
const wrapper = await openPending()
|
||||
await openContainerDialog(wrapper)
|
||||
await chooseContainer(wrapper)
|
||||
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认按柜生成')) as HTMLButtonElement
|
||||
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认生成')) as HTMLButtonElement
|
||||
confirm.click()
|
||||
await flushPromises()
|
||||
expect(convertLastMileContainerToInboundPlans).toHaveBeenCalledTimes(1)
|
||||
@@ -288,6 +324,51 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
expect(wrapper.text()).toContain(`trace-${code}`)
|
||||
})
|
||||
|
||||
it('keeps the latest cabinet preview when an older request resolves last', async () => {
|
||||
let resolveFirst!: (value: typeof preview) => void
|
||||
let resolveSecond!: (value: typeof preview) => void
|
||||
vi.mocked(getContainerInboundPlanPreview).mockImplementation((id) => new Promise((resolve) => {
|
||||
if (id === bookingId) resolveFirst = resolve
|
||||
else resolveSecond = resolve
|
||||
}) as never)
|
||||
const wrapper = await openPending()
|
||||
await openContainerDialog(wrapper)
|
||||
const selects = wrapper.findAllComponents({ name: 'ElSelect' })
|
||||
const containerSelect = selects[selects.length - 1]
|
||||
containerSelect.vm.$emit('update:modelValue', bookingId)
|
||||
containerSelect.vm.$emit('change', bookingId)
|
||||
await wrapper.vm.$nextTick()
|
||||
containerSelect.vm.$emit('update:modelValue', '2092873463568597002')
|
||||
containerSelect.vm.$emit('change', '2092873463568597002')
|
||||
await wrapper.vm.$nextTick()
|
||||
resolveSecond({ ...preview, sourceContainerBookingId: '2092873463568597002', containerNo: 'CONT-NEW', pendingPalletCount: 2 })
|
||||
await flushPromises()
|
||||
expect(document.body.querySelector('.container-preview__identity')?.textContent).toContain('CONT-NEW')
|
||||
resolveFirst(preview)
|
||||
await flushPromises()
|
||||
expect(document.body.querySelector('.container-preview__identity')?.textContent).toContain('CONT-NEW')
|
||||
})
|
||||
|
||||
it('reuses the same idempotency key when an uncertain confirmation is retried', async () => {
|
||||
vi.mocked(convertLastMileContainerToInboundPlans)
|
||||
.mockRejectedValueOnce(new Error('network uncertain'))
|
||||
.mockResolvedValueOnce([{ planNo: 'LIP-RETRY', pallets: [{ id: 'PALLET-R', palletNo: 'PLT-RETRY' }] }] as never)
|
||||
const wrapper = await openPending()
|
||||
await openContainerDialog(wrapper)
|
||||
await chooseContainer(wrapper)
|
||||
const findConfirm = () => [...document.body.querySelectorAll('button')]
|
||||
.find((item) => item.textContent?.includes('确认生成')) as HTMLButtonElement
|
||||
findConfirm().click()
|
||||
await flushPromises()
|
||||
findConfirm().click()
|
||||
await flushPromises()
|
||||
expect(convertLastMileContainerToInboundPlans).toHaveBeenCalledTimes(2)
|
||||
expect(vi.mocked(convertLastMileContainerToInboundPlans).mock.calls[0][1]).toBe(
|
||||
vi.mocked(convertLastMileContainerToInboundPlans).mock.calls[1][1],
|
||||
)
|
||||
expect(document.body.textContent).toContain('PLT-RETRY')
|
||||
})
|
||||
|
||||
it('renders all cabinets as an arrival-ordered workbench with the remaining inbound-code count', async () => {
|
||||
const nullArrival = { ...summary, sourceContainerBookingId: '2092873463568597002', containerNo: 'CONT-NULL', plannedArrivalAt: null, planCount: 0, pendingPlanCount: 15 }
|
||||
vi.mocked(listContainerPlanProgress).mockResolvedValueOnce({ records: [summary, nullArrival], page: 1, size: 10, total: 2, pages: 1 })
|
||||
@@ -369,12 +450,16 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
await flushPromises()
|
||||
await button(wrapper, '生成入库计划')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('确认后将为该柜剩余 5 个入仓码分别生成独立入库计划')
|
||||
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认按柜生成')) as HTMLButtonElement
|
||||
expect(document.body.textContent).toContain('预览已就绪,本次将新增 5 个计划、5 个托盘码')
|
||||
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认生成')) as HTMLButtonElement
|
||||
expect(confirm.disabled).toBe(false)
|
||||
confirm.click()
|
||||
await flushPromises()
|
||||
expect(convertLastMileContainerToInboundPlans).toHaveBeenCalledWith({ sourceContainerBookingId: bookingId })
|
||||
expect(convertLastMileContainerToInboundPlans).toHaveBeenCalledWith({
|
||||
sourceContainerBookingId: bookingId,
|
||||
snapshotVersion: 9,
|
||||
snapshotHash: 'a'.repeat(64),
|
||||
}, expect.stringContaining('container-inbound-plan-'))
|
||||
expect(getContainerPlanProgress).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user