From 08a1f1e5e27a346de5374f2bdc2f1d2999dc84de Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 9 Sep 2026 13:51:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/ContainersView.test.ts | 26 +++++++++++++++++++ .../pages/ContainersView.vue | 5 +++- .../api/deliveryOrders.test.ts | 14 +++++++++- .../overseas-warehouse/api/deliveryOrders.ts | 10 ++++++- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/modules/container-loading/pages/ContainersView.test.ts b/src/modules/container-loading/pages/ContainersView.test.ts index b61f729e..cb36f1ae 100644 --- a/src/modules/container-loading/pages/ContainersView.test.ts +++ b/src/modules/container-loading/pages/ContainersView.test.ts @@ -485,6 +485,32 @@ describe('ContainersView', () => { expect(source).toContain("void router.replace({ name: 'domestic-container-workbench' })") }) + it.each(['EDITING', 'LOADING', 'COMPLETED'] as const)('keeps released-booking linking reachable alongside the %s process action', async (planStatus) => { + const page = await listContainers({ page: 1, size: 20 }, 'DOMESTIC') + vi.mocked(listContainers).mockResolvedValue({ ...page, records: page.records.map((row) => ({ + ...row, planStatus, containerNo: null, allowedActions: ['EDIT_CONTAINER'], + })) }) + const router = createTestRouter() + await router.push('/'); await router.isReady() + const wrapper = mount(ContainersView, { attachTo: document.body, global: { plugins: [ElementPlus, router] } }) + try { + await flushPromises() + const actions = wrapper.find('.el-table__body .row-actions') + expect(actions.findAll('button').map((button) => button.text())).toEqual(['查看', '编辑运输信息', ({ EDITING: '智能装柜', LOADING: '预装清单', COMPLETED: '装柜清单' })[planStatus], '']) + await actions.find('button[aria-label="更多货柜操作"]').trigger('click') + await flushPromises() + const link = Array.from(document.body.querySelectorAll('.el-dropdown-menu__item')) + .filter((item) => item.textContent?.includes('关联已放舱订舱')).at(-1)! + expect(link).toBeDefined() + link.click() + await flushPromises() + expect(wrapper.findAllComponents(AppDialog).find((dialog) => dialog.props('title') === '关联已放舱订舱')?.props('modelValue')).toBe(true) + expect(listOceanBookingLinkCandidates).toHaveBeenCalledWith({ warehouseId: '8', channelCode: 'ZBYHK', containerType: '40HC', page: 1, size: 200 }) + } finally { + wrapper.unmount() + } + }) + it('normalizes an optional booking-link container number', async () => { vi.mocked(linkContainerOceanBooking).mockResolvedValue({} as never) const router = createTestRouter() diff --git a/src/modules/container-loading/pages/ContainersView.vue b/src/modules/container-loading/pages/ContainersView.vue index c8fc56c1..853b3e89 100644 --- a/src/modules/container-loading/pages/ContainersView.vue +++ b/src/modules/container-loading/pages/ContainersView.vue @@ -210,7 +210,10 @@ const allColumns: DataTableColumn[] = [ ] const domesticColumnKeys = new Set(['actions', 'containerNo', 'planNo', 'planStatus', 'exceptionActive', 'cabinetNo', 'oceanBooking', 'warehouseName', 'billOfLadingNo', 'route', 'containerType', 'capacity', 'vessel', 'shippingCompanyName', 'loadingAt', 'estimatedSailingAt']) const overseasColumnKeys = new Set(['actions', 'emptyReturnWarning', 'containerNo', 'cabinetNo', 'destinationClearanceDocumentStatus', 'deliveryOrderDocumentStatus', 'pickupCode', 'terminalTruckingCompany', 'pickupAppointmentAt', 'actualPickupAt', 'estimatedEmptyReturnAt', 'actualEmptyReturnAt', 'billOfLadingNo', 'route', 'vessel', 'actualSailingAt', 'estimatedPortArrivalAt', 'actualPortArrivalAt']) -const columns = computed(() => allColumns.filter((column) => (isDomestic.value ? domesticColumnKeys : overseasColumnKeys).has(String(column.key)))) +const columns = computed(() => allColumns + .filter((column) => (isDomestic.value ? domesticColumnKeys : overseasColumnKeys).has(String(column.key))) + // 国内同时展示查看、运输信息、装柜流程和更多菜单,须为四个入口预留空间。 + .map((column) => isDomestic.value && column.key === 'actions' ? { ...column, width: 380, minWidth: 380 } : column)) const drayageHistoryColumns: DataTableColumn[] = [ { key: 'action', label: '动作', width: 130, formatter: (row: TerminalDrayageHistory) => drayageActionText(row.action) }, { key: 'status', label: '状态变化', minWidth: 180, formatter: (row: TerminalDrayageHistory) => `${drayageStatusText(row.fromStatus)} → ${drayageStatusText(row.toStatus)}` }, diff --git a/src/modules/overseas-warehouse/api/deliveryOrders.test.ts b/src/modules/overseas-warehouse/api/deliveryOrders.test.ts index 3d073756..2aa59f70 100644 --- a/src/modules/overseas-warehouse/api/deliveryOrders.test.ts +++ b/src/modules/overseas-warehouse/api/deliveryOrders.test.ts @@ -18,11 +18,23 @@ describe('delivery order API', () => { }) it('loads an encoded detail in its explicit customer scope', async () => { - vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: { order: { deliveryNo: 'DEL/1' }, skus: [], fees: [], operations: [] } }) + vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: { order: { deliveryNo: 'DEL/1', version: '0' }, skus: [], fees: [], operations: [] } }) await getDeliveryOrder('DEL/1', '1001') expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/delivery-orders/DEL%2F1', { params: { customerId: '1001', page: 1, size: 200 } }) }) + it.each(['0', '7', '9007199254740991'])('reads Java Long version %s for status editing', async version => { + vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: { order: { version, id: '2097515131537313794' } } }) + const detail = await getDeliveryOrder('DEL/1', '1001') + expect(detail.order.version).toBe(Number(version)) + expect(detail.order.id).toBe('2097515131537313794') + }) + + it.each([null, undefined, '', ' ', '-1', '1.5', '9007199254740992', -1])('rejects invalid version %s', async version => { + vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: { order: { version } } }) + await expect(getDeliveryOrder('DEL/1', '1001')).rejects.toThrow('派送单缺少有效版本') + }) + it('loads return options and marks a return in the explicit customer scope', async () => { vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: [{ inboundOrderId: '9', inboundCode: 'IN-1' }] }) await listDeliveryReturnInboundOptions('DEL/1', '1001') diff --git a/src/modules/overseas-warehouse/api/deliveryOrders.ts b/src/modules/overseas-warehouse/api/deliveryOrders.ts index 4e2bd314..9aa54e71 100644 --- a/src/modules/overseas-warehouse/api/deliveryOrders.ts +++ b/src/modules/overseas-warehouse/api/deliveryOrders.ts @@ -97,7 +97,15 @@ export async function getDeliveryOrder(deliveryNo: string, customerId: string) { const response = await overseasHttp.get, ApiEnvelope>( `/platform/v1/last-leg/delivery-orders/${encodeURIComponent(deliveryNo)}`, { params: { customerId, page: 1, size: 200 } }, ) - return unwrapApiResponse(response) + const detail = unwrapApiResponse(response) + // Java Long values are serialized as decimal strings, including the version. + const rawVersion: unknown = detail.order.version + const version = typeof rawVersion === 'string' && /^(0|[1-9]\d*)$/.test(rawVersion) + ? Number(rawVersion) : rawVersion + if (typeof version !== 'number' || !Number.isSafeInteger(version) || version < 0) { + throw new Error('派送单缺少有效版本,请刷新后重试') + } + return { ...detail, order: { ...detail.order, version } } } export async function listDeliveryReturnInboundOptions(deliveryNo: string, customerId: string) {