From 781d25de08c3695944f5904c7731e9146dcabd48 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Thu, 10 Sep 2026 21:22:06 +0800 Subject: [PATCH] feat: add dispatch BOL printing --- .../overseas-warehouse/api/dispatches.ts | 12 ++ .../components/DispatchBolPrintDialog.test.ts | 51 +++++++++ .../components/DispatchBolPrintDialog.vue | 108 ++++++++++++++++++ .../pages/DispatchesView.test.ts | 18 ++- .../pages/DispatchesView.vue | 24 +++- 5 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 src/modules/overseas-warehouse/components/DispatchBolPrintDialog.test.ts create mode 100644 src/modules/overseas-warehouse/components/DispatchBolPrintDialog.vue diff --git a/src/modules/overseas-warehouse/api/dispatches.ts b/src/modules/overseas-warehouse/api/dispatches.ts index 75382404..fc653ccf 100644 --- a/src/modules/overseas-warehouse/api/dispatches.ts +++ b/src/modules/overseas-warehouse/api/dispatches.ts @@ -85,6 +85,18 @@ export interface DispatchSummaryDto { partialCompletionReason?: string | null createdAt?: string | null items?: DispatchItemDto[] + bolLines?: DispatchBolLineDto[] +} + +export interface DispatchBolLineDto { + containerNo?: string | null + palletNo?: string | null + fbaWarehouseCode?: string | null + destinationWarehouseName?: string | null + warehouseNo?: string | null + fbaShipmentId?: string | null + poReferenceNo?: string | null + packageCount: number } export interface UpdateDispatchPayload { diff --git a/src/modules/overseas-warehouse/components/DispatchBolPrintDialog.test.ts b/src/modules/overseas-warehouse/components/DispatchBolPrintDialog.test.ts new file mode 100644 index 00000000..0074cc37 --- /dev/null +++ b/src/modules/overseas-warehouse/components/DispatchBolPrintDialog.test.ts @@ -0,0 +1,51 @@ +import { mount } from '@vue/test-utils' +import { afterEach, describe, expect, it, vi } from 'vitest' +import DispatchBolPrintDialog from './DispatchBolPrintDialog.vue' + +afterEach(() => { + document.body.classList.remove('printing-dispatch-bol') + vi.restoreAllMocks() +}) + +describe('DispatchBolPrintDialog', () => { + it('groups print sheets by FBA warehouse and totals each A4 sheet', () => { + const wrapper = mount(DispatchBolPrintDialog, { + props: { + modelValue: true, + detail: { + id: '1', dispatchNo: 'DSP-1', overseasWarehouseName: 'NOBLE LOGISTICS INC', carrierName: 'Harbor', + bolLines: [ + { containerNo: 'D2144', palletNo: 'D2144-112-1', fbaWarehouseCode: 'LBE1', warehouseNo: 'NPHK1', fbaShipmentId: 'FBA1', poReferenceNo: 'PO1', packageCount: 138 }, + { containerNo: 'D2144', palletNo: 'D2144-112-2', fbaWarehouseCode: 'LBE1', warehouseNo: 'NPHK2', fbaShipmentId: 'FBA2', poReferenceNo: 'PO2', packageCount: 2 }, + { containerNo: 'D2145', palletNo: 'D2145-113-1', fbaWarehouseCode: 'ONT8', warehouseNo: 'NPHK3', packageCount: 5 }, + ], + }, + }, + global: { + stubs: { + ElDialog: { template: '
' }, + ElButton: { template: '' }, + }, + }, + }) + + const sheets = wrapper.findAll('.dispatch-bol-sheet') + expect(sheets).toHaveLength(2) + expect(sheets[0]!.text()).toContain('LBE1') + expect(sheets[0]!.find('.bol-total').text()).toContain('140') + expect(sheets[1]!.find('.bol-total').text()).toContain('5') + }) + + it('opens the native browser print flow', async () => { + vi.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => { callback(0); return 1 }) + const print = vi.spyOn(window, 'print').mockImplementation(() => {}) + const wrapper = mount(DispatchBolPrintDialog, { + props: { modelValue: true, detail: { id: '1', dispatchNo: 'DSP-1' } }, + global: { stubs: { ElDialog: { template: '
' }, ElButton: { template: '' } } }, + }) + + await wrapper.findAll('button').at(-1)!.trigger('click') + expect(print).toHaveBeenCalled() + expect(document.body.classList.contains('printing-dispatch-bol')).toBe(true) + }) +}) diff --git a/src/modules/overseas-warehouse/components/DispatchBolPrintDialog.vue b/src/modules/overseas-warehouse/components/DispatchBolPrintDialog.vue new file mode 100644 index 00000000..82429173 --- /dev/null +++ b/src/modules/overseas-warehouse/components/DispatchBolPrintDialog.vue @@ -0,0 +1,108 @@ + + + + + + diff --git a/src/modules/overseas-warehouse/pages/DispatchesView.test.ts b/src/modules/overseas-warehouse/pages/DispatchesView.test.ts index 025d7d2e..8e61c52a 100644 --- a/src/modules/overseas-warehouse/pages/DispatchesView.test.ts +++ b/src/modules/overseas-warehouse/pages/DispatchesView.test.ts @@ -4,12 +4,12 @@ import { resolve } from 'node:path' import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import QueryToolbar, { type QueryModel } from '@/shared/components/QueryToolbar.vue' -import { cancelDispatch, listDispatches, transferDispatchToDelivery, updateDispatch } from '../api/dispatches' +import { cancelDispatch, getDispatch, listDispatches, transferDispatchToDelivery, updateDispatch } from '../api/dispatches' import DispatchesView from './DispatchesView.vue' const canPermission = vi.hoisted(() => vi.fn(() => true)) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) })) -vi.mock('../api/dispatches', () => ({ listDispatches: vi.fn(), updateDispatch: vi.fn(), cancelDispatch: vi.fn(), transferDispatchToDelivery: vi.fn() })) +vi.mock('../api/dispatches', () => ({ listDispatches: vi.fn(), getDispatch: vi.fn(), updateDispatch: vi.fn(), cancelDispatch: vi.fn(), transferDispatchToDelivery: vi.fn() })) class ResizeObserverStub implements ResizeObserver { observe() {} @@ -47,6 +47,7 @@ describe('DispatchesView', () => { page: 1, size: 20, total: 1, pages: 1, }) vi.mocked(updateDispatch).mockReset() + vi.mocked(getDispatch).mockReset().mockResolvedValue({ id: '190000000000000099', dispatchNo: 'PC-1', bolLines: [] }) vi.mocked(cancelDispatch).mockReset() vi.mocked(transferDispatchToDelivery).mockReset() canPermission.mockReset().mockReturnValue(true) @@ -84,6 +85,19 @@ describe('DispatchesView', () => { expect(wrapper.html()).toContain('190000000000000099') }) + it('loads one detail request only when BOL preview is opened', async () => { + const wrapper = mount(DispatchesView, { global: { plugins: [ElementPlus] } }) + await flushPromises() + expect(getDispatch).not.toHaveBeenCalled() + + await wrapper.findAll('.el-table__body-wrapper button').find((button) => button.text().trim() === '打印BOL')!.trigger('click') + await flushPromises() + + expect(getDispatch).toHaveBeenCalledOnce() + expect(getDispatch).toHaveBeenCalledWith('PC-1') + expect(document.body.textContent).toContain('BOL 打印预览') + }) + it('documents the backend feature flag required by a missing endpoint', () => { const source = readFileSync(resolve(process.cwd(), 'src/modules/overseas-warehouse/pages/DispatchesView.vue'), 'utf8') expect(source).toContain('请确认海外尾程排车接口已发布') diff --git a/src/modules/overseas-warehouse/pages/DispatchesView.vue b/src/modules/overseas-warehouse/pages/DispatchesView.vue index 84b49946..32bdbf41 100644 --- a/src/modules/overseas-warehouse/pages/DispatchesView.vue +++ b/src/modules/overseas-warehouse/pages/DispatchesView.vue @@ -1,6 +1,6 @@