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 @@
+
+
+
+
+ 按目的 FBA 仓自动分页。点击打印后可选择打印机,也可另存为 PDF。
+
+
+
+
+ SHIP FROM
{{ value(detail?.overseasWarehouseName) }}Address: —
+ CARRIER
Carrier Name: {{ value(detail?.carrierName) }}ISA #: {{ value(detail?.fbaAppointmentNo) }}Appointment time: {{ value(detail?.estimatedArrivalAt) }}Trailer #: {{ value(detail?.vehiclePlate) }}Seal #: —
+ SHIP TO
Amazon FBA WarehouseFC Code: {{ group.fbaWarehouseCode }}Name: {{ group.destinationWarehouseName }}Address: —
+ REFERENCE
BOL / Dispatch #: {{ value(detail?.dispatchNo) }}FBA Shipment ID: {{ group.shipmentIds }}PO / Reference #: {{ group.poReferenceNos }}
+
+
+ | Container # | Pallet # | FBA Warehouse # | Warehouse No # | FBA Shipment ID | PO / Reference # | CTNS |
+
+ | {{ value(line.containerNo) }} | {{ value(line.palletNo) }} | {{ group.fbaWarehouseCode }} | {{ value(line.warehouseNo) }} | {{ value(line.fbaShipmentId) }} | {{ value(line.poReferenceNo) }} | {{ line.packageCount }} |
+ | | | | | | | |
+
+
+
+
+
+ 关闭打印 / 另存 PDF
+
+
+
+
+
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 @@