Merge release/test and preserve pickup execution and photo workflows

This commit is contained in:
2026-10-21 17:35:43 +08:00
39 changed files with 1093 additions and 120 deletions
@@ -19,6 +19,28 @@ vi.mock('@/modules/overseas-master-data', async () => ({
const options = { global: { plugins: [ElementPlus], stubs: { DataTable: DataTableStub, DispatchWorkflowDialog: true, TransportExecutionPanel: true } } }
beforeEach(() => { vi.clearAllMocks(); vi.mocked(getDispatch).mockResolvedValue(detail()); can.mockReturnValue(false); push.mockReset(); Object.keys(query).forEach(key => delete query[key]) })
describe('whole-dispatch four-tab detail', () => {
it.each(['omitted', 'null', 'invalid'] as const)('keeps schedule fields readable when exception records are %s and allows retry', async state => {
const value = detail()
if (state === 'omitted') Reflect.deleteProperty(value.dispatchInfo, 'exceptions')
else Reflect.set(value.dispatchInfo, 'exceptions', state === 'null' ? null : {})
vi.mocked(getDispatch).mockResolvedValueOnce(value)
const renderError = vi.fn()
const wrapper = mount(DispatchDetailView, { global: { ...options.global, config: { errorHandler: renderError } } })
await flushPromises()
await wrapper.get('#tab-dispatch').trigger('click'); await flushPromises()
expect(renderError).not.toHaveBeenCalled()
const pane = wrapper.get('#pane-dispatch')
expect(pane.isVisible()).toBe(true)
expect(pane.text()).toContain('LAX → ONT8')
expect(pane.text()).toContain('派送异常记录读取失败')
expect(pane.findAll('dt').map(field => field.text())).toEqual(['路线', '发车时间', '预计到达时间', 'FBA预约'])
expect(pane.find('[aria-label="派送异常记录"]').exists()).toBe(false)
await pane.get('button').trigger('click'); await flushPromises()
expect(getDispatch).toHaveBeenCalledTimes(2)
expect(wrapper.get('#pane-dispatch').isVisible()).toBe(true)
expect(wrapper.get('#pane-dispatch').text()).not.toContain('派送异常记录读取失败')
wrapper.unmount()
})
it('shows saved review attachments beside resource evidence without providing upload controls', async () => {
const value = detail(); value.vehicleInfo.vehicle!.evidenceFileIds = ['301']
value.reviews.dispatchReview.attachments = { vehicleFileIds: ['301', '302'], driverPhotoFileIds: ['303'], licenseFileIds: [] }
@@ -60,6 +82,29 @@ describe('whole-dispatch four-tab detail', () => {
expect(wrapper.findAll('[role="tab"]')).toHaveLength(0)
expect(wrapper.find('[aria-label="排车操作"]').exists()).toBe(false); wrapper.unmount()
})
it('keeps detail tabs read-only when return-to-review is available from the list', async () => {
can.mockReturnValue(true)
vi.mocked(getDispatch).mockResolvedValueOnce(detail({ stage: 'LOADING', actions: { RETURN_TO_REVIEW: { allowed: true, blockCode: null, blockReason: null } } }))
const wrapper = mount(DispatchDetailView, options); await flushPromises()
await wrapper.findAll('[role="tab"]')[1]!.trigger('click'); await flushPromises()
expect(wrapper.find('footer[aria-label="排车操作"]').exists()).toBe(false)
expect(wrapper.findAll('button').map(button => button.text())).not.toContain('退回装车前重审')
expect(wrapper.findComponent({ name: 'DispatchWorkflowDialog' }).exists()).toBe(false)
wrapper.unmount()
})
it('keeps dispatch labels and placeholders visible when no schedule or appointments are recorded', async () => {
const value = detail()
value.dispatchInfo = { ...value.dispatchInfo, route: null, fbaAppointments: [] }
vi.mocked(getDispatch).mockResolvedValueOnce(value)
const wrapper = mount(DispatchDetailView, options); await flushPromises()
for (const name of ['dispatch', 'documents', 'vehicle', 'dispatch']) {
await wrapper.get(`#tab-${name}`).trigger('click'); await flushPromises()
expect(wrapper.get(`#pane-${name}`).isVisible()).toBe(true)
}
expect(wrapper.get('#pane-dispatch').findAll('dd').map(field => field.text())).toEqual(['—', '—', '—', '—'])
expect(wrapper.find('[aria-label="排车操作"]').exists()).toBe(false)
wrapper.unmount()
})
it('returns to the original appointment date and view from the dispatch detail', async () => {
query.appointmentReturn = '/appointment-calendar?date=2026-10-21&mode=week'
const wrapper = mount(DispatchDetailView, options); await flushPromises()
@@ -86,6 +131,8 @@ describe('whole-dispatch four-tab detail', () => {
await tabs[1]!.trigger('click'); await flushPromises()
expect(tabs[1]!.attributes('aria-selected')).toBe('true')
const dispatchInfo = wrapper.get(`#${tabs[1]!.attributes('aria-controls')}`)
expect(dispatchInfo.isVisible()).toBe(true)
expect(dispatchInfo.get('dl').isVisible()).toBe(true)
expect(dispatchInfo.text()).toContain('LAX → ONT8'); expect(dispatchInfo.text()).toContain('FBA预约'); expect(dispatchInfo.text()).toContain('ONT8 · FBA-AP-1 · CONFIRMED')
expect(dispatchInfo.findAll('dt').map(field => field.text())).toEqual(['路线', '发车时间', '预计到达时间', 'FBA预约'])
expect(getDispatch).toHaveBeenCalledTimes(1); expect(wrapper.text()).toContain('装车前'); wrapper.unmount()