|
|
|
@@ -157,10 +157,10 @@ async function mountView() {
|
|
|
|
|
return wrapper
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function openPending() {
|
|
|
|
|
async function openAfterDeparture() {
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
|
const pendingTab = wrapper.findAll('[role="tab"]').find((item) => item.text() === '开船前')
|
|
|
|
|
await pendingTab!.trigger('click')
|
|
|
|
|
const departedTab = wrapper.findAll('[role="tab"]').find((item) => item.text() === '开船后')
|
|
|
|
|
await departedTab!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
return wrapper
|
|
|
|
|
}
|
|
|
|
@@ -308,9 +308,13 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
await flushPromises()
|
|
|
|
|
expect(listLastMileOrders).toHaveBeenLastCalledWith(expect.objectContaining({ orderType: 'FIRST_LEG', sailingStage: stage, detained: false }))
|
|
|
|
|
expect(toolbar.props('availableFields')?.some((field) => field.key === 'orderType')).toBe(false)
|
|
|
|
|
await button(wrapper, '导出订单')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
expect(submitExport).toHaveBeenLastCalledWith('LAST_MILE_ORDER_LIST', '尾程订单', vi.mocked(listLastMileOrders).mock.lastCall![0])
|
|
|
|
|
if (stage === 'BEFORE_DEPARTURE') {
|
|
|
|
|
expect(button(wrapper, '导出订单')).toBeUndefined()
|
|
|
|
|
} else {
|
|
|
|
|
await button(wrapper, '导出订单')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
expect(submitExport).toHaveBeenLastCalledWith('LAST_MILE_ORDER_LIST', '尾程订单', vi.mocked(listLastMileOrders).mock.lastCall![0])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
await wrapper.findAll('[role="tab"]').find((tab) => tab.text() === '全部')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
@@ -359,6 +363,18 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
expect(button(wrapper, '高级筛选')).toBeUndefined()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it.each([null, 'ESTIMATED_DEPARTURE'])('keeps a first-leg order without actual departure read-only: %s', async containerNode => {
|
|
|
|
|
vi.mocked(listLastMileOrders).mockResolvedValue({ records: [{ id: 'before-1', customerId, orderNo: 'LM-BEFORE', currentStatus: 'PENDING_ASSIGNMENT', orderType: 'FIRST_LEG', containerNode }], page: 1, size: 10, total: 1, pages: 1 })
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
|
const actions = wrapper.get('.el-table__body-wrapper .row-actions')
|
|
|
|
|
expect(actions.findAll('button').map(button => button.text().trim())).toEqual(['查看'])
|
|
|
|
|
await selectRows(wrapper, [(wrapper.findComponent(DataTable).props('data') as object[])[0]])
|
|
|
|
|
expect((wrapper.vm as unknown as { selectedRows: object[] }).selectedRows).toEqual([expect.objectContaining({ orderTypeCode: 'FIRST_LEG', containerNode: containerNode || '—' })])
|
|
|
|
|
expect(wrapper.find('.data-table__business-actions').exists()).toBe(false)
|
|
|
|
|
expect(createManualInboundPlan).not.toHaveBeenCalled()
|
|
|
|
|
expect(transferLastMileOrders).not.toHaveBeenCalled()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('keeps the original carton count and adds only the signed difference in the list', async () => {
|
|
|
|
|
canAccess.mockReturnValue(false)
|
|
|
|
|
vi.mocked(listLastMileOrders).mockResolvedValue({ records: [{
|
|
|
|
@@ -381,7 +397,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
[['inbound-order:create', 'last-mile-order:edit'], ['查看', '入库', '修改节点'], false],
|
|
|
|
|
])('only collapses row actions above three for %j', async (allowed, labels, overflow) => {
|
|
|
|
|
canAccess.mockImplementation(code => allowed.some(action => code === 'platform:overseas-warehouse:' + action))
|
|
|
|
|
vi.mocked(listLastMileOrders).mockResolvedValue({ records: [{ id: 'order-1', customerId, orderNo: 'LM-1', currentStatus: 'PENDING_ASSIGNMENT', orderType: 'FIRST_LEG' }], page: 1, size: 10, total: 1, pages: 1 })
|
|
|
|
|
vi.mocked(listLastMileOrders).mockResolvedValue({ records: [{ id: 'order-1', customerId, orderNo: 'LM-1', currentStatus: 'PENDING_ASSIGNMENT', orderType: 'FIRST_LEG', containerNode: 'ACTUAL_DEPARTURE' }], page: 1, size: 10, total: 1, pages: 1 })
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
|
const actions = wrapper.get('.el-table__body-wrapper .row-actions')
|
|
|
|
|
expect(actions.findAll('button').map(button => button.text().trim())).toEqual(labels)
|
|
|
|
@@ -507,7 +523,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
])('only shows a new hold after inbound completion for %s', async (currentStatus, visible) => {
|
|
|
|
|
canAccess.mockImplementation(code => code === 'platform:overseas-warehouse:last-mile-order:edit')
|
|
|
|
|
vi.mocked(listLastMileOrders).mockResolvedValue({ records: [{
|
|
|
|
|
id: 'order-1', customerId, orderNo: 'LM-1', currentStatus, orderType: 'FIRST_LEG', detained: false,
|
|
|
|
|
id: 'order-1', customerId, orderNo: 'LM-1', currentStatus, orderType: 'FIRST_LEG', containerNode: 'ACTUAL_DEPARTURE', detained: false,
|
|
|
|
|
}], page: 1, size: 10, total: 1, pages: 1 })
|
|
|
|
|
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
@@ -520,7 +536,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
it('keeps release hold available for an already detained order', async () => {
|
|
|
|
|
canAccess.mockImplementation(code => code === 'platform:overseas-warehouse:last-mile-order:edit')
|
|
|
|
|
vi.mocked(listLastMileOrders).mockResolvedValue({ records: [{
|
|
|
|
|
id: 'order-held', customerId, orderNo: 'LM-HELD', currentStatus: 'READY_FOR_DISPATCH', orderType: 'FIRST_LEG', detained: true,
|
|
|
|
|
id: 'order-held', customerId, orderNo: 'LM-HELD', currentStatus: 'READY_FOR_DISPATCH', orderType: 'FIRST_LEG', containerNode: 'ACTUAL_DEPARTURE', detained: true,
|
|
|
|
|
}], page: 1, size: 10, total: 1, pages: 1 })
|
|
|
|
|
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
@@ -533,7 +549,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
it('removes generation and hold actions after an inbound plan has been submitted', async () => {
|
|
|
|
|
canAccess.mockReturnValue(true)
|
|
|
|
|
vi.mocked(listLastMileOrders).mockResolvedValue({ records: [{
|
|
|
|
|
id: 'order-planned', customerId, orderNo: 'LM-PLANNED', currentStatus: 'INBOUND_PLANNED', orderType: 'FIRST_LEG', detained: false,
|
|
|
|
|
id: 'order-planned', customerId, orderNo: 'LM-PLANNED', currentStatus: 'INBOUND_PLANNED', orderType: 'FIRST_LEG', containerNode: 'ACTUAL_DEPARTURE', detained: false,
|
|
|
|
|
}], page: 1, size: 10, total: 1, pages: 1 })
|
|
|
|
|
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
@@ -546,8 +562,8 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
expect((button(wrapper, '入库')!.element as HTMLButtonElement).disabled).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('keeps the original pending order table and does not render a view switch or container table', async () => {
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
it('keeps after-departure order actions in the existing table without a container view switch', async () => {
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
expect(wrapper.text()).toContain('尾程单号')
|
|
|
|
|
expect(wrapper.text()).toContain('海外仓')
|
|
|
|
|
expect(wrapper.text()).toContain('转仓')
|
|
|
|
@@ -556,7 +572,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
expect(wrapper.text()).not.toContain('按订单')
|
|
|
|
|
expect(wrapper.text()).not.toContain('待分配整柜')
|
|
|
|
|
expect(wrapper.text()).not.toContain('柜子工作台')
|
|
|
|
|
expect(listLastMileOrders).toHaveBeenLastCalledWith(expect.objectContaining({ page: 1, size: 10, currentStatus: undefined, orderType: 'FIRST_LEG', sailingStage: 'BEFORE_DEPARTURE' }))
|
|
|
|
|
expect(listLastMileOrders).toHaveBeenLastCalledWith(expect.objectContaining({ page: 1, size: 10, currentStatus: undefined, orderType: 'FIRST_LEG', sailingStage: 'AFTER_DEPARTURE' }))
|
|
|
|
|
expect(listContainerPlanProgress).not.toHaveBeenCalled()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
@@ -613,7 +629,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('opens the required single-order inbound-plan form without invoking the cabinet conversion', async () => {
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await selectRows(wrapper, [selectedRow()])
|
|
|
|
|
await button(wrapper, '入库')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
@@ -633,7 +649,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('shows cabinet-frozen facts but never sends arrival time or per-order warehouse overrides', async () => {
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await selectRows(wrapper, [selectedRow()])
|
|
|
|
|
await button(wrapper, '入库')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
@@ -655,7 +671,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('blocks a held order from generating its inbound plan', async () => {
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await selectRows(wrapper, [selectedRow({
|
|
|
|
|
detained: true,
|
|
|
|
|
orderTypeCode: 'ONE_CLICK_DROPSHIP',
|
|
|
|
@@ -676,7 +692,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
[{ ...arrivalTime, estimatedWarehouseArrivalAt: null, version: 0, planCount: 1, plannedDate: '2026-11-02' }, '2026-11-02'],
|
|
|
|
|
])('reads the latest cabinet arrival on opening and permits planning with %s', async (arrival, expectedText) => {
|
|
|
|
|
vi.mocked(listContainerArrivalTimes).mockResolvedValue([arrival])
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openArrivalPlan(wrapper, arrival.estimatedWarehouseArrivalAt ? selectedRow() : selectedRow({ estimatedWarehouseAt: '', warehouseEta: '—' }))
|
|
|
|
|
|
|
|
|
|
expect(listContainerArrivalTimes).toHaveBeenCalledWith([bookingId])
|
|
|
|
@@ -697,7 +713,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
{ arrivals: [{ ...arrivalTime, version: -1 }] },
|
|
|
|
|
])('blocks manual planning when the authoritative cabinet arrival is missing or inconsistent: $arrivals', async ({ arrivals }) => {
|
|
|
|
|
vi.mocked(listContainerArrivalTimes).mockResolvedValue(arrivals)
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openArrivalPlan(wrapper)
|
|
|
|
|
|
|
|
|
|
const confirm = [...document.body.querySelectorAll('button')]
|
|
|
|
@@ -708,7 +724,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
|
|
|
|
|
it('shows arrival read failures and does not reuse the list timestamp', async () => {
|
|
|
|
|
vi.mocked(listContainerArrivalTimes).mockRejectedValue(new ApiResponseError('到仓来源暂不可读', 'A0503', 503))
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openArrivalPlan(wrapper)
|
|
|
|
|
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('入库计划 ·'))!
|
|
|
|
|
expect(dialog.text()).toContain('到仓来源暂不可读')
|
|
|
|
@@ -721,7 +737,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
it('keeps confirmation blocked during arrival loading and ignores an older opening of the same order', async () => {
|
|
|
|
|
let resolveOld!: (value: typeof arrivalTime[]) => void
|
|
|
|
|
vi.mocked(listContainerArrivalTimes).mockReturnValueOnce(new Promise(resolve => { resolveOld = resolve }))
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openArrivalPlan(wrapper)
|
|
|
|
|
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('入库计划 ·'))!
|
|
|
|
|
expect(dialog.props('confirmDisabled')).toBe(true)
|
|
|
|
@@ -750,7 +766,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
}],
|
|
|
|
|
}],
|
|
|
|
|
} as never)
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await selectRows(wrapper, [selectedRow({ orderTypeCode: 'ONE_CLICK_DROPSHIP', pallets: 2 })])
|
|
|
|
|
await button(wrapper, '入库')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
@@ -768,7 +784,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('opens a cabinet selector without requiring order selection and loads server summaries only then', async () => {
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openContainerDialog(wrapper)
|
|
|
|
|
expect(listContainerPlanProgress).toHaveBeenCalledTimes(1)
|
|
|
|
|
expect(listContainerPlanProgress).toHaveBeenCalledWith({ page: 1, size: 50, containerNo: undefined })
|
|
|
|
@@ -781,7 +797,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('selects one cabinet and sends one long-id convert-container request without using current-page row counts', async () => {
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openContainerDialog(wrapper)
|
|
|
|
|
await chooseContainer(wrapper)
|
|
|
|
|
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认生成')) as HTMLButtonElement
|
|
|
|
@@ -807,7 +823,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
vi.mocked(convertLastMileContainerToInboundPlans).mockRejectedValueOnce(new ApiResponseError(
|
|
|
|
|
'柜级生成失败', code, 200, undefined, `request-${code}`, `trace-${code}`,
|
|
|
|
|
))
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openContainerDialog(wrapper)
|
|
|
|
|
await chooseContainer(wrapper)
|
|
|
|
|
const confirm = [...document.body.querySelectorAll('button')].find((item) => item.textContent?.includes('确认生成')) as HTMLButtonElement
|
|
|
|
@@ -826,7 +842,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
if (id === bookingId) resolveFirst = resolve
|
|
|
|
|
else resolveSecond = resolve
|
|
|
|
|
}) as never)
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openContainerDialog(wrapper)
|
|
|
|
|
const selects = wrapper.findAllComponents({ name: 'ElSelect' })
|
|
|
|
|
const containerSelect = selects[selects.length - 1]
|
|
|
|
@@ -848,7 +864,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
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()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openContainerDialog(wrapper)
|
|
|
|
|
await chooseContainer(wrapper)
|
|
|
|
|
const findConfirm = () => [...document.body.querySelectorAll('button')]
|
|
|
|
@@ -931,7 +947,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
.mockResolvedValueOnce(resourceCommittedPreview)
|
|
|
|
|
.mockResolvedValue({ ...resourceCommittedPreview, resourceRetry: undefined })
|
|
|
|
|
vi.mocked(convertLastMileContainerToInboundPlans).mockResolvedValue(originalResourcePlans as never)
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
cabinetDialog(wrapper).vm.$emit('update:modelValue', false); await flushPromises()
|
|
|
|
|
await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
@@ -955,7 +971,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
|
|
|
|
|
it('keeps ordinary zero-pending previews without resourceRetry disabled in both UI and handler', async () => {
|
|
|
|
|
vi.mocked(getContainerInboundPlanPreview).mockResolvedValue({ ...resourceCommittedPreview, resourceRetry: undefined })
|
|
|
|
|
const wrapper = await openPending(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
const wrapper = await openAfterDeparture(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
expect(cabinetDialog(wrapper).props('confirmDisabled')).toBe(true)
|
|
|
|
|
expect(cabinetDialog(wrapper).props('confirmText')).toBe('确认生成')
|
|
|
|
|
cabinetDialog(wrapper).vm.$emit('confirm'); await flushPromises()
|
|
|
|
@@ -964,7 +980,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
|
|
|
|
|
it('uses the visible missing-container label in the preview tooltip without exposing the booking ID', async () => {
|
|
|
|
|
vi.mocked(getContainerInboundPlanPreview).mockResolvedValue({ ...preview, containerNo: null } as never)
|
|
|
|
|
const wrapper = await openPending(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
const wrapper = await openAfterDeparture(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
const identity = document.body.querySelector('.container-preview__identity')!
|
|
|
|
|
expect(identity.textContent).toBe('未录入货柜号')
|
|
|
|
|
expect(identity.getAttribute('title')).toBe('未录入货柜号')
|
|
|
|
@@ -973,7 +989,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
|
|
|
|
|
it('keeps server readiness blocks effective even for a resource retry', async () => {
|
|
|
|
|
vi.mocked(getContainerInboundPlanPreview).mockResolvedValue({ ...resourceCommittedPreview, ready: false, blockedOrderCount: 1 })
|
|
|
|
|
const wrapper = await openPending(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
const wrapper = await openAfterDeparture(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
expect(cabinetDialog(wrapper).props('confirmDisabled')).toBe(true)
|
|
|
|
|
cabinetDialog(wrapper).vm.$emit('confirm'); await flushPromises()
|
|
|
|
|
expect(convertLastMileContainerToInboundPlans).not.toHaveBeenCalled()
|
|
|
|
@@ -982,7 +998,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
it('guards resource retry at the handler when the caller lacks create permission', async () => {
|
|
|
|
|
canAccess.mockImplementation(code => code !== 'platform:overseas-warehouse:inbound-order:create')
|
|
|
|
|
vi.mocked(getContainerInboundPlanPreview).mockResolvedValue(resourceCommittedPreview)
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
const view = wrapper.vm as unknown as { openContainerConversion(): void }
|
|
|
|
|
view.openContainerConversion(); await flushPromises(); await chooseContainer(wrapper)
|
|
|
|
|
expect(cabinetDialog(wrapper).props('confirmDisabled')).toBe(true)
|
|
|
|
@@ -995,7 +1011,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
let resolveOptions!: (value: Awaited<ReturnType<typeof listContainerPlanProgress>>) => void
|
|
|
|
|
vi.mocked(getContainerInboundPlanPreview).mockReturnValue(new Promise(resolve => { resolvePreview = resolve }))
|
|
|
|
|
vi.mocked(listContainerPlanProgress).mockReturnValue(new Promise(resolve => { resolveOptions = resolve }))
|
|
|
|
|
const wrapper = await openPending(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
const wrapper = await openAfterDeparture(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
expect(cabinetDialog(wrapper).props('confirmDisabled')).toBe(true)
|
|
|
|
|
cabinetDialog(wrapper).vm.$emit('confirm'); await flushPromises()
|
|
|
|
|
expect(convertLastMileContainerToInboundPlans).not.toHaveBeenCalled()
|
|
|
|
@@ -1011,7 +1027,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
const success = vi.spyOn(ElMessage, 'success').mockImplementation(() => ({ close: vi.fn() } as never))
|
|
|
|
|
vi.mocked(getContainerInboundPlanPreview).mockResolvedValue(resourceCommittedPreview)
|
|
|
|
|
vi.mocked(convertLastMileContainerToInboundPlans).mockRejectedValueOnce(new ApiResponseError('资源确认失败', 'S0503', 503))
|
|
|
|
|
const wrapper = await openPending(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
const wrapper = await openAfterDeparture(); await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
cabinetDialog(wrapper).vm.$emit('confirm'); await flushPromises()
|
|
|
|
|
expect(cabinetDialog(wrapper).props('modelValue')).toBe(true)
|
|
|
|
|
expect(cabinetDialog(wrapper).props('confirmDisabled')).toBe(false)
|
|
|
|
@@ -1233,7 +1249,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('loads only active FBA master data and submits exact string fields for a cross-customer transfer', async () => {
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await selectRows(wrapper, [selectedRow(), selectedRow({ id: '2092873463568596995', customerId: '2092618717861048322' })])
|
|
|
|
|
await button(wrapper, '转仓')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
@@ -1257,7 +1273,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('clears all selected warehouse facts when the FBA candidate is cleared', async () => {
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await selectRows(wrapper, [selectedRow()])
|
|
|
|
|
await button(wrapper, '转仓')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
@@ -1273,7 +1289,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
|
|
|
|
|
it('renders an explicit empty state when there are no active FBA candidates', async () => {
|
|
|
|
|
vi.mocked(listAllPlatformWarehouses).mockResolvedValueOnce([])
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await selectRows(wrapper, [selectedRow()])
|
|
|
|
|
await button(wrapper, '转仓')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
@@ -1285,7 +1301,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
vi.mocked(listAllPlatformWarehouses).mockRejectedValueOnce(new ApiResponseError(
|
|
|
|
|
'FBA candidate failed', 'A0503', 200, undefined, 'request-fba', 'trace-fba',
|
|
|
|
|
))
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await selectRows(wrapper, [selectedRow()])
|
|
|
|
|
await button(wrapper, '转仓')!.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
@@ -1355,21 +1371,22 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
await dialog.findAll('button').find(item => item.text() === '保存')!.trigger('click'); await flushPromises()
|
|
|
|
|
expect(updateLastMileOrderHold).toHaveBeenCalledWith(row.id, { held: !detained, chargeable: false, reason: '原因', expectedVersion: 0 }, expect.stringContaining('last-mile-hold-'))
|
|
|
|
|
})
|
|
|
|
|
it('renders all eight node times and submits the complete set with zero version', async () => {
|
|
|
|
|
it('edits only customs nodes while preserving hidden node facts and zero version', async () => {
|
|
|
|
|
vi.mocked(getLastMileOrderManualNodes).mockResolvedValue({ orderId: '2092873463568596994', manualOverrideEnabled: true, nodes: [{ nodeCode: 'ACTUAL_DEPARTURE', nodeAt: '2026-10-19T08:00:00' }], version: 0 })
|
|
|
|
|
const wrapper = await mountView()
|
|
|
|
|
const row = selectedRow()
|
|
|
|
|
await (wrapper.vm as unknown as { openManualNodes(row: object): Promise<void> }).openManualNodes(row)
|
|
|
|
|
await flushPromises()
|
|
|
|
|
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('修改节点 ·'))!
|
|
|
|
|
expect(dialog.findAll('.manual-nodes-editor tbody tr')).toHaveLength(8)
|
|
|
|
|
expect(dialog.findAll('.manual-nodes-editor tbody tr').map(row => row.find('th').text())).toEqual(['国内报关完成', '国外清关完成'])
|
|
|
|
|
const dates = dialog.findAllComponents({ name: 'ElDatePicker' })
|
|
|
|
|
expect(dates).toHaveLength(8)
|
|
|
|
|
expect(dates).toHaveLength(2)
|
|
|
|
|
dates[0]!.vm.$emit('update:modelValue', '2026-10-20T08:00:00')
|
|
|
|
|
await dialog.get('button[aria-label="展开全屏"]').trigger('click')
|
|
|
|
|
await dialog.findAll('button').find(item => item.text() === '保存')!.trigger('click'); await flushPromises()
|
|
|
|
|
expect(updateLastMileOrderManualNodes).toHaveBeenCalledWith(row.id, 0, [
|
|
|
|
|
{ nodeCode: 'DOMESTIC_CUSTOMS_COMPLETED', nodeAt: '2026-10-20T08:00:00' },
|
|
|
|
|
{ nodeCode: 'ESTIMATED_DEPARTURE', nodeAt: null }, { nodeCode: 'ACTUAL_DEPARTURE', nodeAt: null },
|
|
|
|
|
{ nodeCode: 'ESTIMATED_DEPARTURE', nodeAt: null }, { nodeCode: 'ACTUAL_DEPARTURE', nodeAt: '2026-10-19T08:00:00' },
|
|
|
|
|
{ nodeCode: 'ESTIMATED_ARRIVAL', nodeAt: null }, { nodeCode: 'OVERSEAS_CUSTOMS_COMPLETED', nodeAt: null },
|
|
|
|
|
{ nodeCode: 'ACTUAL_ARRIVAL_WAITING_UNLOAD', nodeAt: null }, { nodeCode: 'PICKUP_APPOINTED', nodeAt: null }, { nodeCode: 'UNLOADED', nodeAt: null },
|
|
|
|
|
], expect.stringContaining('last-mile-manual-nodes-'))
|
|
|
|
@@ -1381,7 +1398,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
groups: Array.from({ length: 13 }, (_, index) => ({ ...group, destinationWarehouseCode: 'FBA-' + index, orderCount: 1, pendingOrderCount: 1, pendingPalletCount: 1, sourcePalletCount: 1, supplementPalletCount: 0,
|
|
|
|
|
orders: [{ ...group.orders[0]!, orderId: 'order-' + index, businessOrderNo: '业务单-' + index, sourcePalletCount: 1, targetPalletCount: 1, supplementPalletCount: 0 }] })),
|
|
|
|
|
})
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await openContainerDialog(wrapper); await chooseContainer(wrapper)
|
|
|
|
|
const dialog = cabinetDialog(wrapper)
|
|
|
|
|
expect(dialog.findAll('[aria-label="目的地分组明细"]')).toHaveLength(13)
|
|
|
|
@@ -1394,7 +1411,7 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
|
|
|
|
it('keeps transfer drafts locked during submission and editable after rejection', async () => {
|
|
|
|
|
let reject!: (reason: unknown) => void
|
|
|
|
|
vi.mocked(transferLastMileOrders).mockReturnValueOnce(new Promise((_, no) => { reject = no }))
|
|
|
|
|
const wrapper = await openPending()
|
|
|
|
|
const wrapper = await openAfterDeparture()
|
|
|
|
|
await selectRows(wrapper, [selectedRow()])
|
|
|
|
|
await button(wrapper, '转仓')!.trigger('click'); await flushPromises()
|
|
|
|
|
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('转仓 ·'))!
|
|
|
|
|