fix: align audit and dispatch workflows and release failed requests

This commit is contained in:
2026-10-22 23:10:09 +08:00
parent 9cdcb9c077
commit 4a3456756f
124 changed files with 1296 additions and 2120 deletions
@@ -14,7 +14,7 @@ vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({
vi.mock('./scheduleApi', () => ({ getCalendarSources: vi.fn(), getWarehouseArrivals: vi.fn(), saveWarehouseArrival: vi.fn() }))
vi.mock('./tripCalendar', async original => ({ ...await original<typeof import('./tripCalendar')>(), listTripCalendar: vi.fn() }))
vi.mock('element-plus', () => ({ ElMessage: { success: vi.fn() }, ElMessageBox: { confirm: vi.fn() } }))
const warehouse: WarehouseArrival = { sourceContainerBookingId: '1', containerNo: 'TEST-CONT', warehouseName: '美国仓', warehouseCode: 'US-W', plannedAt: null, actualAt: null, version: 0, planCount: 0, canEdit: true }
const warehouse: WarehouseArrival = { sourceContainerBookingId: '1', containerNo: 'TEST-CONT', warehouseName: '美国仓', warehouseCode: 'US-W', plannedAt: null, plannedDate: '2026-10-21', isDefault: true, actualAt: null, version: 0, planCount: 0, canEdit: true }
const row: ScheduleEvent = { key: 'warehouse-1', type: 'warehouse', date: '2026-10-21', time: '', title: 'TEST-CONT 计划到仓', fields: [], warehouse, canEdit: true }
function dispatchEntry(overrides: Partial<TripCalendarEntry> = {}): TripCalendarEntry {
return { sourceType: 'DISPATCH', dispatchNo: 'PC-CALENDAR-001', containerNumbers: ['TSTU1234567', 'TSTU7654321'], cabinetNumbers: [], status: 'PLANNED', originWarehouseId: '1', originWarehouseCode: 'LAX', originWarehouseName: '洛杉矶仓', originCountryCode: 'US', originTimezone: 'America/Los_Angeles', originWarehouseStatus: 'ACTIVE', plannedDepartureAt: '2026-10-22T16:00:00Z', expectedDriverEndAt: null, departedAt: null, completedAt: null, stops: [{ name: '洛杉矶仓' }, { name: 'ONT8' }], incompleteReasons: [], ...overrides }
@@ -34,7 +34,7 @@ export function renderSchedule() {
} } })
}
beforeEach(() => {
vi.clearAllMocks(); routing.query = { date: '2026-10-19' }; routing.denied.clear()
vi.resetAllMocks(); routing.query = { date: '2026-10-19' }; routing.denied.clear()
vi.mocked(getCalendarSources).mockResolvedValue({ containers: [{ containerBookingId: '1', cabinetNo: 'D0001', containerNo: 'TEST-CONT', vesselName: null, voyageNo: null, destinationPortName: null, destinationWarehouseCode: null, arrivalAt: '2026-10-19T10:00:00', arrivalType: 'ACTUAL' }], emptyReturns: [], emptyReturnsReadable: true })
vi.mocked(getWarehouseArrivals).mockResolvedValue([warehouse])
vi.mocked(listTripCalendar).mockResolvedValue({ records: [], unresolvedRecords: [] })
@@ -110,29 +110,24 @@ describe('simplified appointment entry', () => {
expect(wrapper.getComponent({ name: 'AppointmentCalendarPanel' }).props('events')).toHaveLength(0)
wrapper.unmount()
})
it('retries an unknown save with the same payload and key, then only reloads reads', async () => {
it('releases an unknown save and sends the edited date with a fresh key', async () => {
vi.mocked(saveWarehouseArrival).mockRejectedValueOnce(new Error('network interrupted')).mockResolvedValueOnce({})
const wrapper = renderSchedule(); await flushPromises()
wrapper.getComponent({ name: 'AppointmentCalendarPanel' }).vm.$emit('edit', row); await flushPromises()
await wrapper.get('input[data-temporal-picker]').setValue('2026-10-22T09:30')
await wrapper.get('[data-save]').trigger('click'); await flushPromises()
expect(wrapper.text()).toContain('提交结果尚未确认')
expect(wrapper.get('input').attributes('disabled')).toBeDefined()
const guard = vi.mocked(onBeforeRouteLeave).mock.calls.at(-1)![0] as () => Promise<boolean>
expect(await guard()).toBe(false)
await wrapper.get('[data-save]').trigger('click'); await flushPromises()
expect(vi.mocked(saveWarehouseArrival).mock.calls[1]).toEqual(vi.mocked(saveWarehouseArrival).mock.calls[0])
expect(saveWarehouseArrival).toHaveBeenCalledWith('1', { estimatedWarehouseArrivalAt: '2026-10-22T09:30:00', expectedVersion: 0 }, expect.any(String))
expect(wrapper.find('[data-dialog]').exists()).toBe(false)
wrapper.unmount()
await wrapper.get('input[data-temporal-picker]').setValue('2026-10-22T09:30'); await wrapper.get('[data-save]').trigger('click'); await flushPromises()
expect(wrapper.get('input').attributes('disabled')).toBeUndefined()
await wrapper.get('input[data-temporal-picker]').setValue('2026-10-23T09:30'); await wrapper.get('[data-save]').trigger('click'); await flushPromises()
const calls = vi.mocked(saveWarehouseArrival).mock.calls
expect(calls[1]![2]).not.toBe(calls[0]![2]); expect(calls[1]![1].estimatedWarehouseArrivalAt).toBe('2026-10-23T09:30:00')
expect(wrapper.find('[data-dialog]').exists()).toBe(false); wrapper.unmount()
})
it('requires an explicit reread after a conflict and retains the entered date', async () => {
it('allows editing after a conflict and preserves input across an optional manual refresh', async () => {
vi.mocked(saveWarehouseArrival).mockRejectedValueOnce(new ApiResponseError('版本已变化', 'A0409', 409))
const wrapper = renderSchedule(); await flushPromises()
wrapper.getComponent({ name: 'AppointmentCalendarPanel' }).vm.$emit('edit', row); await flushPromises()
await wrapper.get('input').setValue('2026-10-22T09:30')
await wrapper.get('[data-save]').trigger('click'); await flushPromises()
expect(wrapper.get('[data-save]').attributes('disabled')).toBeDefined()
expect(wrapper.get('[data-save]').attributes('disabled')).toBeUndefined()
vi.mocked(getWarehouseArrivals).mockResolvedValueOnce([{ ...warehouse, version: 2 }])
await wrapper.get('[data-retry]').trigger('click'); await flushPromises()
expect((wrapper.get('input').element as HTMLInputElement).value).toBe('2026-10-22T09:30')