fix: align audit and dispatch workflows and release failed requests
This commit is contained in:
@@ -53,11 +53,28 @@ async function render(action: DispatchAction, operation?: 'ROUTE', files = false
|
||||
function field(wrapper: Pick<VueWrapper, 'findAllComponents'>, label: string) { return wrapper.findAllComponents({ name: 'ElFormItem' }).find(item => item.props('label') === label)! }
|
||||
|
||||
describe('dispatch workflow native layout', () => {
|
||||
it('brings ten orders at one destination into one appointment input with confirmed time and all shipments', async () => {
|
||||
const initial = detail(), shipmentNumbers = Array.from({ length: 10 }, (_, index) => `SHIP-${index}`)
|
||||
const appointment = { batchKey: null, batchNo: null, customerId: null, appointmentNo: 'ISA-ONE', destinationKey: 'FBA:ONT8', destinationWarehouseCode: 'ONT8', status: 'SUCCESS', confirmedAt: '2026-10-25T17:00:00Z', blockReason: null, shipmentNumbers }
|
||||
const { dialog } = await render('EDIT', undefined, false, { orderDetails: shipmentNumbers.map((_, index) => ({ ...initial.orderDetails[0]!, orderNo: `LM-${index}`, customerId: `C-${index}` })), dispatchInfo: { ...initial.dispatchInfo, fbaAppointments: [appointment] } })
|
||||
const table = dialog.get('[aria-label="目的仓预约"]')
|
||||
expect(table.findAll('tbody tr')).toHaveLength(1)
|
||||
expect(table.findAll('input')).toHaveLength(1)
|
||||
expect((table.get('input').element as HTMLInputElement).value).toBe('ISA-ONE')
|
||||
expect(table.findAll('tbody td')[2]!.text()).not.toBe('—')
|
||||
for (const shipment of shipmentNumbers) expect(table.text()).toContain(shipment)
|
||||
await table.get('input').setValue('ISA-CHANGED')
|
||||
expect(table.findAll('tbody td')[2]!.text()).toBe('—')
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
expect(api.updateDispatch).toHaveBeenCalledWith('PC/1', expect.objectContaining({ maintenance: expect.objectContaining({ destinationAppointments: [{ destinationKey: 'FBA:ONT8', appointmentNo: 'ISA-CHANGED' }] }) }))
|
||||
})
|
||||
it('restores saved maintenance facts and shows associated orders without editable departure facts', async () => {
|
||||
const saved = { ...blankVehicleMaintenance(), plannedDeliveryDate: '2026-11-02', fleetEmail: 'fleet@example.test', appointmentNo: 'APPT-SAVED' }
|
||||
const { dialog } = await render('EDIT', undefined, false, { vehicleInfo: { ...resources(), maintenance: saved } })
|
||||
const saved = { ...blankVehicleMaintenance(), plannedDeliveryDate: '2026-11-02', fleetEmail: 'fleet@example.test', destinationAppointments: [{ destinationKey: 'FBA:ONT8', appointmentNo: 'CONF-REAL' }] }
|
||||
const { dialog } = await render('EDIT', undefined, false, { vehicleInfo: { ...resources(), maintenance: saved }, dispatchInfo: { ...detail().dispatchInfo, fbaAppointments: [{ batchKey: 'FBA_APPOINTMENT:1', customerId: 'A', shipmentNumbers: ['SHIP-REAL'], batchNo: 'AP-1', appointmentNo: 'CONF-REAL', destinationKey: 'FBA:ONT8', destinationWarehouseCode: 'ONT8', status: 'SUCCESS', confirmedAt: '2026-10-25T17:00:00Z', blockReason: null }] } })
|
||||
expect((field(dialog, '车队邮箱').get('input').element as HTMLInputElement).value).toBe('fleet@example.test')
|
||||
expect((field(dialog, '预约号').get('input').element as HTMLInputElement).value).toBe('APPT-SAVED')
|
||||
expect((field(dialog, 'ONT8 预约号').get('input').element as HTMLInputElement).value).toBe('CONF-REAL')
|
||||
expect(dialog.findAll('[aria-label="目的仓预约"] tbody tr')).toHaveLength(1)
|
||||
expect(dialog.text()).not.toContain('关联正式预约')
|
||||
expect(dialog.text()).toContain('关联订单 (1)')
|
||||
const orders = dialog.findAll('[role="tab"]').find(tab => tab.text().includes('关联订单'))!
|
||||
await orders.trigger('click')
|
||||
@@ -67,6 +84,29 @@ describe('dispatch workflow native layout', () => {
|
||||
expect(api.updateDispatch).toHaveBeenCalledWith('PC/1', expect.objectContaining({ maintenance: saved }))
|
||||
expect(vi.mocked(api.updateDispatch).mock.calls[0]![1]).not.toHaveProperty('departedAt')
|
||||
})
|
||||
it('edits one number per destination and can save an incomplete second destination', async () => {
|
||||
const destinations = ['ONT8', 'LGB8'].map(code => ({ batchKey: null, batchNo: null, customerId: null, appointmentNo: null, destinationKey: `FBA:${code}`, destinationWarehouseCode: code, status: 'PENDING', confirmedAt: null, blockReason: null, shipmentNumbers: [] }))
|
||||
const initial = detail()
|
||||
const { dialog } = await render('EDIT', undefined, false, { orderDetails: [...initial.orderDetails, { ...initial.orderDetails[0]!, customerId: 'OTHER', orderNo: 'LM-OTHER' }], dispatchInfo: { ...initial.dispatchInfo, fbaAppointments: destinations } })
|
||||
expect(dialog.findAll('[aria-label="目的仓预约"] tbody tr')).toHaveLength(2)
|
||||
await field(dialog, 'ONT8 预约号').get('input').setValue(' ISA-ONT8 ')
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
expect(api.updateDispatch).toHaveBeenCalledWith('PC/1', expect.objectContaining({ maintenance: expect.objectContaining({ destinationAppointments: [{ destinationKey: 'FBA:ONT8', appointmentNo: 'ISA-ONT8' }, { destinationKey: 'FBA:LGB8', appointmentNo: null }] }) }))
|
||||
})
|
||||
it('keeps input editable after rejection and adds concurrent destinations when current facts are refreshed', async () => {
|
||||
const appointment = { batchKey: null, batchNo: null, customerId: null, appointmentNo: null, destinationKey: 'FBA:ONT8', destinationWarehouseCode: 'ONT8', status: 'PENDING', confirmedAt: null, blockReason: null, shipmentNumbers: [] }
|
||||
const initial = detail()
|
||||
const { dialog } = await render('EDIT', undefined, false, { dispatchInfo: { ...initial.dispatchInfo, fbaAppointments: [appointment] } })
|
||||
await field(dialog, 'ONT8 预约号').get('input').setValue('ISA-KEEP')
|
||||
vi.mocked(api.updateDispatch).mockRejectedValueOnce(new ApiResponseError('排车版本已变化', 'A0409', 200))
|
||||
vi.mocked(api.getDispatch).mockResolvedValue(detail({ recordVersion: 2, actions, dispatchInfo: { ...initial.dispatchInfo, fbaAppointments: [appointment, { ...appointment, destinationKey: 'FBA:LGB8', destinationWarehouseCode: 'LGB8' }] } }))
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
expect(field(dialog, 'ONT8 预约号').get('input').attributes('disabled')).toBeUndefined()
|
||||
dialog.getComponent(LoadErrorState).vm.$emit('retry'); await flushPromises()
|
||||
expect((field(dialog, 'ONT8 预约号').get('input').element as HTMLInputElement).value).toBe('ISA-KEEP')
|
||||
expect(field(dialog, 'LGB8 预约号')).toBeDefined()
|
||||
expect(dialog.findAll('[aria-label="目的仓预约"] tbody tr')).toHaveLength(2)
|
||||
})
|
||||
it.each(['DISPATCH_REVIEW', 'LOADING_REVIEW'] as const)('blocks %s approval for missing appointment but keeps rejection available', async action => {
|
||||
vi.mocked(api.getDispatch).mockResolvedValue(detail({ actions: { ...actions, [action]: { allowed: true, blockCode: 'APPROVAL_BLOCKED', blockReason: '预约收货时间尚未确认' } } }))
|
||||
const wrapper = mount(DispatchWorkflowDialog, { attachTo: document.body, props: { modelValue: true, dispatchNo: 'PC/1', action }, global: { plugins: [ElementPlus] } }); await flushPromises()
|
||||
@@ -90,13 +130,12 @@ describe('dispatch workflow native layout', () => {
|
||||
for (const [label, value] of [['派送方式', '外配'], ['提货方式', '一提多卸'], ['装车类型', '混装'], ['车辆类型', '厢式货车']]) field(dialog, label!).getComponent({ name: 'ElSelect' }).vm.$emit('update:modelValue', value)
|
||||
await field(dialog, '车队邮箱').get('input').setValue(' fleet@example.test ')
|
||||
await field(dialog, '预约账号').get('input').setValue(' fleet-account ')
|
||||
await field(dialog, '预约号').get('input').setValue(' APPT-NEW ')
|
||||
expect(field(dialog, '发车时间').get('input').attributes('readonly')).toBeDefined()
|
||||
expect(dialog.find('textarea[aria-label="本次操作备注"]').exists()).toBe(false)
|
||||
await dialog.get('button[aria-label="展开全屏"]').trigger('click'); await flushPromises()
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
const { vehicleStatus: _status, ...vehicle } = resources().vehicle
|
||||
expect(api.updateDispatch).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, publishLooseboardDemand: true, idempotencyKey: expect.any(String), vehicle: { ...vehicle, plate: 'CA-NEW', capacity: { ...vehicle.capacity, palletSpaces: 22 } }, driver: { ...resources().driver, name: '新司机' }, maintenance: { plannedDeliveryDate: '2026-11-02', deliveryMethod: '外配', pickupMethod: '一提多卸', loadType: '混装', fleetEmail: 'fleet@example.test', vehicleType: '厢式货车', appointmentAccount: 'fleet-account', appointmentNo: 'APPT-NEW' }, plannedDepartureAt: '2026-11-01T10:00:00Z', estimatedArrivalAt: null, remark: '真实备注' })
|
||||
expect(api.updateDispatch).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, publishLooseboardDemand: true, idempotencyKey: expect.any(String), vehicle: { ...vehicle, plate: 'CA-NEW', capacity: { ...vehicle.capacity, palletSpaces: 22 } }, driver: { ...resources().driver, name: '新司机' }, maintenance: { plannedDeliveryDate: '2026-11-02', deliveryMethod: '外配', pickupMethod: '一提多卸', loadType: '混装', fleetEmail: 'fleet@example.test', vehicleType: '厢式货车', appointmentAccount: 'fleet-account', destinationAppointments: [] }, plannedDepartureAt: '2026-11-01T10:00:00Z', estimatedArrivalAt: null, remark: '真实备注' })
|
||||
})
|
||||
it('saves all three resource photos before submitting review with the refreshed version', async () => {
|
||||
const { dialog } = await render('EDIT')
|
||||
@@ -128,8 +167,15 @@ describe('dispatch workflow native layout', () => {
|
||||
})
|
||||
it.each(['DISPATCH_REVIEW', 'LOADING_REVIEW'] as const)('keeps %s facts read-only and requires a rejection reason', async action => {
|
||||
const { dialog } = await render(action)
|
||||
expect(dialog.props('width')).toBe('1100px')
|
||||
expect(dialog.get('[aria-label="本车审核依据"]').findAll('input')).toHaveLength(0)
|
||||
expect(dialog.get('[aria-label="本车审核依据"]').find('h3').exists()).toBe(false)
|
||||
expect(dialog.get('[aria-label="本车审核依据"] h3').text()).toBe('车辆与货物')
|
||||
expect(dialog.get('[aria-label="本车审核依据"]').text()).toContain('联系电话')
|
||||
expect(dialog.get('[aria-label="排车与预约"]').findAll('input')).toHaveLength(0)
|
||||
const photos = dialog.get('[aria-label="审核照片"]').findAllComponents(DispatchEvidenceFiles)
|
||||
expect(photos.map(photo => photo.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片'])
|
||||
expect(photos.every(photo => photo.props('readonly'))).toBe(true)
|
||||
expect(dialog.get('[aria-label="操作原因与意见"] h3').text()).toBe('审核意见')
|
||||
expect(dialog.text()).not.toContain('请核对本车资源与本趟证据')
|
||||
expect(dialog.findAll('button').some(button => button.text() === '取消')).toBe(false)
|
||||
expect(dialog.findComponent({ name: 'ElRadioGroup' }).exists()).toBe(false)
|
||||
@@ -191,6 +237,8 @@ describe('dispatch workflow native layout', () => {
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
const original = structuredClone(vi.mocked(api.updateDispatch).mock.calls[0]![1])
|
||||
expect(original).toMatchObject({ vehicle: { evidenceFileIds: ['301', '401'] }, driver: { evidence: { photoFileIds: ['402'], fileIds: expect.arrayContaining(['403']) } } })
|
||||
expect(dialog.props('confirmLoading')).toBe(false)
|
||||
dialog.getComponent(LoadErrorState).vm.$emit('retry'); await flushPromises()
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
const next=vi.mocked(api.updateDispatch).mock.calls[1]![1]
|
||||
expect(next).toMatchObject({...original,recordVersion:1,idempotencyKey:expect.any(String)})
|
||||
@@ -232,9 +280,9 @@ describe('dispatch workflow native layout', () => {
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
expect(api.reorderDispatchRoute).toHaveBeenCalledExactlyOnceWith('PC/1', { recordVersion: 0, idempotencyKey: expect.any(String), destinationKeys: ['B', 'A'], reason: '顺序调整' })
|
||||
})
|
||||
it.each(['ADD_CARGO', 'REMOVE_CARGO'] as const)('replaces the old %s editor with the smart dispatch entry notice', async action => {
|
||||
it.each(['ADD_CARGO', 'REMOVE_CARGO'] as const)('keeps the retired %s editor unavailable', async action => {
|
||||
const { dialog } = await render(action)
|
||||
expect(dialog.text()).toContain('货物安排统一在智能排车中修改')
|
||||
expect(dialog.text()).toContain('当前入口不支持调整已建排车货物')
|
||||
expect(dialog.props('confirmDisabled')).toBe(true)
|
||||
expect(dialog.findComponent({ name: 'ElForm' }).exists()).toBe(false)
|
||||
dialog.vm.$emit('confirm'); await flushPromises()
|
||||
|
||||
Reference in New Issue
Block a user