fix(inbound): refresh authoritative container arrival when opening plan
This commit is contained in:
@@ -11,6 +11,7 @@ import DataTable from '@/shared/components/DataTable.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
|
||||
import { listAllPlatformWarehouses } from '@/modules/warehouses'
|
||||
import { listContainerArrivalTimes } from '@/modules/container-loading'
|
||||
import {
|
||||
changeContainerReceivingWarehouse,
|
||||
createManualInboundPlan,
|
||||
@@ -35,6 +36,10 @@ vi.mock('@/shared/downloadCsv', () => ({ saveCsv: vi.fn() }))
|
||||
const routerPush = vi.hoisted(() => vi.fn())
|
||||
vi.mock('vue-router', () => ({ useRouter: () => ({ push: routerPush }) }))
|
||||
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn() }))
|
||||
vi.mock('@/modules/container-loading', async () => ({
|
||||
...await import('../../container-loading/warehouseArrival'),
|
||||
listContainerArrivalTimes: vi.fn(),
|
||||
}))
|
||||
vi.mock('../api/lastMileOrders', () => ({
|
||||
getReturnOrderStatistics: vi.fn(),
|
||||
listLastMileOrders: vi.fn(), listContainerPlanProgress: vi.fn(), getContainerPlanProgress: vi.fn(),
|
||||
@@ -54,6 +59,10 @@ function memoryStorage(): Storage {
|
||||
}
|
||||
|
||||
const bookingId = '2092873463568597001'
|
||||
const arrivalTime = {
|
||||
sourceContainerBookingId: bookingId, estimatedWarehouseArrivalAt: '2026-09-01T08:00:00',
|
||||
actualWarehouseArrivalAt: null, version: 1, planCount: 0, plannedDate: '2026-09-01', isDefault: false,
|
||||
}
|
||||
const emptyReturnStatistics = { totals: { orderCount: 0, skuTypes: 0, expectedQuantity: 0, receivedQuantity: 0, remainingQuantity: 0, receivedBoxCount: 0, receivedParcelCount: 0, receivedPalletCount: 0 }, pageDimensions: [] }
|
||||
beforeEach(() => {
|
||||
vi.mocked(getReturnOrderStatistics).mockReset().mockResolvedValue(emptyReturnStatistics)
|
||||
@@ -171,6 +180,12 @@ async function selectRows(wrapper: Awaited<ReturnType<typeof mountView>>, rows:
|
||||
await wrapper.vm.$nextTick()
|
||||
}
|
||||
|
||||
/** 定向验证入库弹框,独立于列表页签和批量操作入口。 */
|
||||
async function openArrivalPlan(wrapper: Awaited<ReturnType<typeof mountView>>, source = selectedRow()) {
|
||||
;(wrapper.vm as unknown as { openInboundPlan(source: object): void }).openInboundPlan(source)
|
||||
await flushPromises()
|
||||
}
|
||||
|
||||
function button(wrapper: Awaited<ReturnType<typeof mountView>>, text: string) {
|
||||
return wrapper.findAll('button').find((item) => item.text().includes(text))
|
||||
}
|
||||
@@ -359,6 +374,7 @@ 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(listContainerArrivalTimes).mockReset().mockResolvedValue([arrivalTime])
|
||||
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 })
|
||||
@@ -627,11 +643,35 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
expect(confirm).toBeUndefined()
|
||||
})
|
||||
|
||||
it('blocks manual plan creation when the cabinet has no frozen expected arrival time', async () => {
|
||||
it.each([
|
||||
[{ ...arrivalTime, estimatedWarehouseArrivalAt: '2026-10-25T09:30:00', plannedDate: '2026-10-25' }, '2026-10-25 09:30'],
|
||||
[{ ...arrivalTime, estimatedWarehouseArrivalAt: null, version: 0, plannedDate: '2026-11-01', isDefault: true }, '2026-11-01(默认推算)'],
|
||||
[{ ...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()
|
||||
await selectRows(wrapper, [selectedRow({ estimatedWarehouseAt: '', warehouseEta: '—' })])
|
||||
await button(wrapper, '入库')!.trigger('click')
|
||||
await openArrivalPlan(wrapper, arrival.estimatedWarehouseArrivalAt ? selectedRow() : selectedRow({ estimatedWarehouseAt: '', warehouseEta: '—' }))
|
||||
|
||||
expect(listContainerArrivalTimes).toHaveBeenCalledWith([bookingId])
|
||||
const source = wrapper.get('[aria-label="入库来源"]')
|
||||
expect(source.text()).toContain(expectedText)
|
||||
if (!arrival.estimatedWarehouseArrivalAt) expect(source.text()).not.toContain('00:00')
|
||||
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('入库计划 ·'))!
|
||||
expect(dialog.props('confirmDisabled')).toBe(false)
|
||||
dialog.vm.$emit('confirm')
|
||||
await flushPromises()
|
||||
expect(createManualInboundPlan).toHaveBeenCalledTimes(1)
|
||||
expect(vi.mocked(createManualInboundPlan).mock.calls[0][0]).not.toHaveProperty('plannedArrivalDate')
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ arrivals: [] },
|
||||
{ arrivals: [{ ...arrivalTime, estimatedWarehouseArrivalAt: null, plannedDate: null }] },
|
||||
{ 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()
|
||||
await openArrivalPlan(wrapper)
|
||||
|
||||
const confirm = [...document.body.querySelectorAll('button')]
|
||||
.find((item) => item.textContent?.includes('保存并生成入库单')) as HTMLButtonElement
|
||||
@@ -639,6 +679,37 @@ describe('LastMileOrdersView pending order and container conversion', () => {
|
||||
expect(createManualInboundPlan).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
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()
|
||||
await openArrivalPlan(wrapper)
|
||||
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('入库计划 ·'))!
|
||||
expect(dialog.text()).toContain('到仓来源暂不可读')
|
||||
expect(dialog.get('[aria-label="入库来源"]').text()).not.toContain('2026-09-01')
|
||||
expect(dialog.props('confirmDisabled')).toBe(true)
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
expect(createManualInboundPlan).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
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()
|
||||
await openArrivalPlan(wrapper)
|
||||
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('入库计划 ·'))!
|
||||
expect(dialog.props('confirmDisabled')).toBe(true)
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
expect(createManualInboundPlan).not.toHaveBeenCalled()
|
||||
dialog.vm.$emit('update:modelValue', false)
|
||||
await wrapper.vm.$nextTick()
|
||||
vi.mocked(listContainerArrivalTimes).mockResolvedValueOnce([{ ...arrivalTime, estimatedWarehouseArrivalAt: '2026-10-26T10:00:00', plannedDate: '2026-10-26' }])
|
||||
await openArrivalPlan(wrapper)
|
||||
expect(dialog.get('[aria-label="入库来源"]').text()).toContain('2026-10-26 10:00')
|
||||
resolveOld([arrivalTime]); await flushPromises()
|
||||
expect(dialog.get('[aria-label="入库来源"]').text()).toContain('2026-10-26 10:00')
|
||||
expect(dialog.props('confirmDisabled')).toBe(false)
|
||||
})
|
||||
|
||||
it('locks one-click dropship manual plans to the frozen source pallet count', async () => {
|
||||
vi.mocked(getContainerInboundPlanPreview).mockResolvedValue({
|
||||
...preview,
|
||||
|
||||
Reference in New Issue
Block a user