fix(admin): refresh container document versions
This commit is contained in:
@@ -2,8 +2,8 @@ import ElementPlus, { ElMessageBox } from 'element-plus'
|
||||
import { flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import ContainerDetailView from './ContainerDetailView.vue'
|
||||
import { downloadContainerDocument, getContainer, getContainerClearance, getContainerOverview, uploadContainerClearance, uploadContainerDocument } from '../api/platformContainerLoading'
|
||||
import { completeLoadingDeclaration, downloadLoadingDeclaration, uploadLoadingDeclaration } from '@/modules/loading-lists'
|
||||
import { downloadContainerClearance, downloadContainerDocument, getContainer, getContainerClearance, getContainerOverview, uploadContainerClearance, uploadContainerDocument } from '../api/platformContainerLoading'
|
||||
import { completeLoadingDeclaration, downloadLoadingDeclaration, getLoadingDeclaration, uploadLoadingDeclaration } from '@/modules/loading-lists'
|
||||
import { confirmContainerOutboundDispatch, getContainerOutboundDispatch } from '@/modules/outbound-dispatch/api/platformOutboundDispatch'
|
||||
import type { ContainerOutboundDispatchDetail } from '@/modules/outbound-dispatch/types'
|
||||
import { ApiResponseError } from '@/shared/api/http'
|
||||
@@ -12,12 +12,13 @@ const { accessCan, routerPush, carrierMounts } = vi.hoisted(() => ({ accessCan:
|
||||
vi.mock('vue-router', () => ({ useRoute: () => ({ params: { containerId: '2092438843630391297' }, query: {} }), useRouter: () => ({ push: routerPush }) }))
|
||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: accessCan }) }))
|
||||
vi.mock('@/modules/port-schedules', () => ({ CarrierScheduleQueryPanel: { template: '<div data-test="carrier-panel" />', mounted: () => { carrierMounts.value += 1 } } }))
|
||||
vi.mock('@/modules/loading-lists', () => ({ uploadLoadingDeclaration: vi.fn(), completeLoadingDeclaration: vi.fn(), downloadLoadingDeclaration: vi.fn() }))
|
||||
vi.mock('@/modules/loading-lists', () => ({ uploadLoadingDeclaration: vi.fn(), completeLoadingDeclaration: vi.fn(), downloadLoadingDeclaration: vi.fn(), getLoadingDeclaration: vi.fn() }))
|
||||
vi.mock('@/modules/outbound-dispatch/api/platformOutboundDispatch', () => ({ getContainerOutboundDispatch: vi.fn(), confirmContainerOutboundDispatch: vi.fn() }))
|
||||
vi.mock('../api/platformContainerLoading', () => ({
|
||||
getContainer: vi.fn(), getContainerOverview: vi.fn(), getContainerClearance: vi.fn(),
|
||||
uploadContainerClearance: vi.fn(), completeContainerClearance: vi.fn(), uploadContainerDocument: vi.fn(),
|
||||
downloadContainerDocument: vi.fn(), downloadContainerPdaPhoto: vi.fn(), markContainerException: vi.fn(), cancelContainerException: vi.fn(),
|
||||
downloadContainerClearance: vi.fn(),
|
||||
}))
|
||||
|
||||
const detail = { id: '2092438843630391297', planId: '2092438843630391401', planNo: 'LP-260825-0181', bookingNo: 'CB-1', cabinetNo: 'CAB-1', containerNo: 'C11111111', warehouseId: '193000000000200002', warehouseName: '东莞仓', containerType: '45HC', planStatus: 'COMPLETED', version: 8 } as never
|
||||
@@ -59,6 +60,8 @@ describe('ContainerDetailView', () => {
|
||||
vi.mocked(uploadLoadingDeclaration).mockResolvedValue({ id: '2092438843630391501', documentType: 'CUSTOMS_DECLARATION', fileId: '2092438843630391502', fileName: 'customs-new.pdf', fileHash: 'hash', uploadedBy: '1', uploadedByName: '操作员', uploadedAt: '2026-08-26T12:00:00', completedBy: null, completedByName: null, completedAt: null, version: 6 } as never)
|
||||
vi.mocked(completeLoadingDeclaration).mockResolvedValue({} as never)
|
||||
vi.mocked(downloadLoadingDeclaration).mockResolvedValue(new Blob(['customs']))
|
||||
vi.mocked(downloadContainerClearance).mockResolvedValue(new Blob(['clearance']))
|
||||
vi.mocked(getLoadingDeclaration).mockResolvedValue(null)
|
||||
vi.mocked(uploadContainerDocument).mockResolvedValue({} as never)
|
||||
vi.mocked(uploadContainerClearance).mockResolvedValue({} as never)
|
||||
vi.mocked(getContainerOutboundDispatch).mockResolvedValue(outboundDetail)
|
||||
@@ -139,6 +142,74 @@ describe('ContainerDetailView', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('refreshes a hidden customs version after A0409 and preserves error evidence without auto retry', async () => {
|
||||
const hidden = { id: '2092438843630392501', documentType: 'CUSTOMS_DECLARATION', fileId: '2092438843630392502', fileName: 'server-customs.pdf', fileHash: 'server-hash', uploadedBy: '2', uploadedByName: '服务端操作员', uploadedAt: '2026-08-26T12:30:00', completedBy: null, completedByName: null, completedAt: null, version: 7 }
|
||||
const withoutCustoms = { ...overview, documents: [] }
|
||||
vi.mocked(getContainerOverview).mockResolvedValue(withoutCustoms as never)
|
||||
vi.mocked(getLoadingDeclaration).mockResolvedValueOnce(hidden as never)
|
||||
vi.mocked(uploadLoadingDeclaration).mockRejectedValueOnce(new ApiResponseError('版本冲突', 'A0409', undefined, undefined, 'request-upload-409', 'trace-upload-409'))
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
await documentButton(wrapper, '国内出口报关附件', '上传附件').trigger('click'); await flushPromises()
|
||||
const file = new File(['next'], 'next.pdf', { type: 'application/pdf' })
|
||||
;(wrapper.vm as unknown as { selectDocumentFile: (file: { raw: File }) => void }).selectDocumentFile({ raw: file })
|
||||
await (wrapper.vm as unknown as { saveDocument: () => Promise<void> }).saveDocument(); await flushPromises()
|
||||
expect(uploadLoadingDeclaration).toHaveBeenCalledOnce()
|
||||
expect(uploadLoadingDeclaration).toHaveBeenCalledWith('2092438843630391401', '193000000000200002', file, 0, '上传国内出口报关附件')
|
||||
expect(getLoadingDeclaration).toHaveBeenCalledWith('2092438843630391401', '193000000000200002')
|
||||
expect(documentArticle(wrapper, '国内出口报关附件').text()).toContain('server-customs.pdf')
|
||||
expect(documentArticle(wrapper, '国内出口报关附件').text()).toContain('服务端操作员')
|
||||
expect(wrapper.text()).toContain('A0409')
|
||||
expect(wrapper.text()).toContain('request-upload-409')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('uses the independent destination document version and immediately replaces the table row after update', async () => {
|
||||
const existing = { id: '301', documentType: 'DESTINATION_CLEARANCE', fileId: '302', fileName: 'clearance-v1.pdf', fileHash: null, uploadedBy: '3', uploadedByName: '清关员', uploadedAt: '2026-08-26T11:00:00', completedBy: null, completedByName: null, completedAt: null, version: 1 }
|
||||
const updated = { ...existing, fileId: '303', fileName: 'clearance-v2.pdf', uploadedAt: '2026-08-26T13:00:00', version: 2 }
|
||||
vi.mocked(getContainerClearance).mockResolvedValueOnce(existing as never)
|
||||
vi.mocked(uploadContainerClearance).mockResolvedValueOnce(updated as never)
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
await documentButton(wrapper, '目的港清关资料', '上传附件').trigger('click'); await flushPromises()
|
||||
const file = new File(['clearance'], 'clearance-v2.pdf', { type: 'application/pdf' })
|
||||
;(wrapper.vm as unknown as { selectClearanceFile: (file: { raw: File }) => void }).selectClearanceFile({ raw: file })
|
||||
await (wrapper.vm as unknown as { uploadClearance: () => Promise<void> }).uploadClearance(); await flushPromises()
|
||||
expect(uploadContainerClearance).toHaveBeenCalledWith('2092438843630391297', file, 1, '上传目的港清关资料')
|
||||
expect(documentArticle(wrapper, '目的港清关资料').text()).toContain('clearance-v2.pdf')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('downloads destination clearance through its dedicated endpoint and keeps completed rows download-only', async () => {
|
||||
const completed = { id: '351', documentType: 'DESTINATION_CLEARANCE', fileId: '2092438843630393502', fileName: 'destination-clearance.pdf', fileHash: null, uploadedBy: '3', uploadedByName: '清关员', uploadedAt: '2026-08-26T11:00:00', version: 2, completed: true }
|
||||
vi.mocked(getContainerOverview).mockResolvedValueOnce({ ...overview, documents: [...overview.documents, completed] } as never)
|
||||
const click = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
const row = documentArticle(wrapper, '目的港清关资料')
|
||||
expect(row.findAll('button').map((button) => button.text())).toEqual(['下载'])
|
||||
await documentButton(wrapper, '目的港清关资料', '下载').trigger('click'); await flushPromises()
|
||||
expect(downloadContainerClearance).toHaveBeenCalledWith('2092438843630391297', '2092438843630393502')
|
||||
expect(downloadLoadingDeclaration).not.toHaveBeenCalled()
|
||||
expect(downloadContainerDocument).not.toHaveBeenCalled()
|
||||
expect(click).toHaveBeenCalledOnce()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('refreshes destination clearance after A0409 and preserves the server file and error metadata', async () => {
|
||||
const existing = { id: '401', documentType: 'DESTINATION_CLEARANCE', fileId: '402', fileName: 'clearance-v1.pdf', fileHash: null, uploadedBy: '3', uploadedByName: '清关员', uploadedAt: '2026-08-26T11:00:00', completedBy: null, completedByName: null, completedAt: null, version: 1 }
|
||||
const server = { ...existing, fileId: '403', fileName: 'server-clearance-v2.pdf', uploadedByName: '服务端清关员', version: 2 }
|
||||
vi.mocked(getContainerClearance).mockResolvedValueOnce(existing as never).mockResolvedValueOnce(server as never)
|
||||
vi.mocked(uploadContainerClearance).mockRejectedValueOnce(new ApiResponseError('版本冲突', 'A0409', undefined, undefined, 'request-clearance-409', 'trace-clearance-409'))
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
await documentButton(wrapper, '目的港清关资料', '上传附件').trigger('click'); await flushPromises()
|
||||
const file = new File(['clearance'], 'clearance-next.pdf', { type: 'application/pdf' })
|
||||
;(wrapper.vm as unknown as { selectClearanceFile: (file: { raw: File }) => void }).selectClearanceFile({ raw: file })
|
||||
await (wrapper.vm as unknown as { uploadClearance: () => Promise<void> }).uploadClearance(); await flushPromises()
|
||||
expect(uploadContainerClearance).toHaveBeenCalledOnce()
|
||||
expect(documentArticle(wrapper, '目的港清关资料').text()).toContain('server-clearance-v2.pdf')
|
||||
expect(wrapper.text()).toContain('A0409')
|
||||
expect(wrapper.text()).toContain('request-clearance-409')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('keeps customs attachment upload and completion as two explicit steps with exact payload', async () => {
|
||||
const completedOverview = { ...overview, documents: overview.documents.map((item) => ({ ...item, completed: true })) }
|
||||
vi.mocked(getContainerOverview).mockResolvedValueOnce(overview as never).mockResolvedValue(completedOverview as never)
|
||||
|
||||
Reference in New Issue
Block a user