fix: open dispatch BOL as PDF
This commit is contained in:
@@ -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, getDispatch, listDispatches, transferDispatchToDelivery, updateDispatch } from '../api/dispatches'
|
||||
import { cancelDispatch, getDispatchBolPdf, 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(), getDispatch: vi.fn(), updateDispatch: vi.fn(), cancelDispatch: vi.fn(), transferDispatchToDelivery: vi.fn() }))
|
||||
vi.mock('../api/dispatches', () => ({ listDispatches: vi.fn(), getDispatchBolPdf: vi.fn(), updateDispatch: vi.fn(), cancelDispatch: vi.fn(), transferDispatchToDelivery: vi.fn() }))
|
||||
|
||||
class ResizeObserverStub implements ResizeObserver {
|
||||
observe() {}
|
||||
@@ -47,7 +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(getDispatchBolPdf).mockReset().mockResolvedValue(new Blob(['%PDF-test'], { type: 'application/pdf' }))
|
||||
vi.mocked(cancelDispatch).mockReset()
|
||||
vi.mocked(transferDispatchToDelivery).mockReset()
|
||||
canPermission.mockReset().mockReturnValue(true)
|
||||
@@ -85,17 +85,20 @@ describe('DispatchesView', () => {
|
||||
expect(wrapper.html()).toContain('190000000000000099')
|
||||
})
|
||||
|
||||
it('loads one detail request only when BOL preview is opened', async () => {
|
||||
it('opens the server-generated BOL PDF with one request', async () => {
|
||||
const pdfWindow = { location: { href: '' }, close: vi.fn() }
|
||||
vi.spyOn(window, 'open').mockReturnValue(pdfWindow as unknown as Window)
|
||||
vi.stubGlobal('URL', { createObjectURL: vi.fn(() => 'blob:dispatch-bol'), revokeObjectURL: vi.fn() })
|
||||
const wrapper = mount(DispatchesView, { global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
expect(getDispatch).not.toHaveBeenCalled()
|
||||
expect(getDispatchBolPdf).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 打印预览')
|
||||
expect(getDispatchBolPdf).toHaveBeenCalledOnce()
|
||||
expect(getDispatchBolPdf).toHaveBeenCalledWith('PC-1')
|
||||
expect(pdfWindow.location.href).toBe('blob:dispatch-bol')
|
||||
})
|
||||
|
||||
it('documents the backend feature flag required by a missing endpoint', () => {
|
||||
|
||||
@@ -13,9 +13,8 @@ import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/compone
|
||||
import StatusBadge from '@/shared/components/StatusBadge.vue'
|
||||
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import { cancelDispatch, getDispatch, listDispatches, transferDispatchToDelivery, updateDispatch, type DispatchSummaryDto } from '../api/dispatches'
|
||||
import { cancelDispatch, getDispatchBolPdf, listDispatches, transferDispatchToDelivery, updateDispatch, type DispatchSummaryDto } from '../api/dispatches'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import DispatchBolPrintDialog from '../components/DispatchBolPrintDialog.vue'
|
||||
import type { DispatchListItem } from '../types'
|
||||
import { formatOverseasDateTime } from '../lastMilePresentation'
|
||||
import { overseasWarehousePermissions as permission } from '../permissions'
|
||||
@@ -32,7 +31,7 @@ const filters = ref<QueryModel>({ dispatchNo: '', vehiclePlate: '', status: '',
|
||||
const transferOpen = ref(false); const transferRow = ref<DispatchListItem | null>(null); const transferSaving = ref(false)
|
||||
const mutationError = ref<ApiErrorDetails | null>(null)
|
||||
const editOpen = ref(false); const editRow = ref<DispatchListItem | null>(null); const editSaving = ref(false)
|
||||
const bolOpen = ref(false); const bolDetail = ref<DispatchSummaryDto | null>(null); const bolLoadingNo = ref('')
|
||||
const bolLoadingNo = ref('')
|
||||
const dispatchSnapshots = new Map<string, DispatchSummaryDto>()
|
||||
const editForm = reactive({ vehiclePlate: '', driverName: '', driverPhone: '', carrierName: '', route: '', departAt: '', fbaAppointmentNo: '', fbaAppointmentStatus: '', vehicleAppointmentStatus: '', estimatedArrivalAt: '' })
|
||||
const transferForm = reactive({ plannedDeliveryAt: '', deliveryMethod: '', waybillNo: '', carrierName: '', pickupMethod: '', loadingType: '', vehicleType: '', vehicleSize: '', driverName: '', driverPhone: '', vehiclePlate: '', appointmentAccount: '', sellerName: '', deliveryFee: undefined as number | undefined, loadingFee: undefined as number | undefined, appointmentFee: undefined as number | undefined, cartonMarksJson: '', remark: '' })
|
||||
@@ -189,13 +188,18 @@ function reset(model: QueryModel) { page.value = 1; void load(model) }
|
||||
function openDetail(row: DispatchListItem) { void router.push({ name: 'overseas-dispatch-detail', params: { dispatchNo: row.dispatchNo } }) }
|
||||
async function openBol(row: DispatchListItem) {
|
||||
if (bolLoadingNo.value) return
|
||||
const pdfWindow = window.open('', '_blank')
|
||||
if (!pdfWindow) { ElMessage.warning('浏览器拦截了 BOL PDF 窗口,请允许本站打开新窗口'); return }
|
||||
bolLoadingNo.value = row.dispatchNo
|
||||
mutationError.value = null
|
||||
try {
|
||||
bolDetail.value = await getDispatch(row.dispatchNo)
|
||||
bolOpen.value = true
|
||||
const pdf = await getDispatchBolPdf(row.dispatchNo)
|
||||
const url = URL.createObjectURL(pdf)
|
||||
pdfWindow.location.href = url
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 60_000)
|
||||
} catch (cause) {
|
||||
mutationError.value = getApiErrorDetails(cause, 'BOL 打印数据加载失败,请重试')
|
||||
pdfWindow.close()
|
||||
mutationError.value = getApiErrorDetails(cause, 'BOL PDF 生成失败,请重试')
|
||||
} finally {
|
||||
bolLoadingNo.value = ''
|
||||
}
|
||||
@@ -238,7 +242,6 @@ onMounted(load)
|
||||
<template #status="{ row }"><StatusBadge :status="String((row as DispatchListItem).status || '—')" /></template>
|
||||
<template #pagination-left>共 {{ total }} 条排车记录</template>
|
||||
</DataTable></section>
|
||||
<DispatchBolPrintDialog v-model="bolOpen" :detail="bolDetail" />
|
||||
</ListPageLayout></AdminPage><AppDialog v-model="transferOpen" title="转派送" width="min(1120px, calc(100vw - 28px))" confirm-text="确认提交" :confirm-loading="transferSaving" @confirm="submitTransfer"><el-form label-position="top"><h3>派送信息</h3><div class="transfer-grid"><el-form-item label="排车单号"><el-input :model-value="transferRow?.dispatchNo" readonly /></el-form-item><el-form-item label="预计派送时间" required><el-date-picker v-model="transferForm.plannedDeliveryAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" placeholder="请选择预计派送时间" /></el-form-item><el-form-item label="派送方式" required><el-select v-model="transferForm.deliveryMethod" placeholder="请选择"><el-option v-for="option in deliveryMethodOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="快递/车队"><el-input v-model="transferForm.carrierName" placeholder="请输入快递或车队名称" /></el-form-item><el-form-item label="运单号"><el-input v-model="transferForm.waybillNo" placeholder="请输入运单号" /></el-form-item><el-form-item label="提货方式" required><el-select v-model="transferForm.pickupMethod" placeholder="请选择"><el-option v-for="option in pickupMethodOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="装车类型" required><el-select v-model="transferForm.loadingType" placeholder="请选择"><el-option v-for="option in loadingTypeOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="司机/车队名称"><el-input v-model="transferForm.driverName" placeholder="请输入司机或车队名称" /></el-form-item><el-form-item label="联系电话"><el-input v-model="transferForm.driverPhone" placeholder="请输入联系电话" /></el-form-item><el-form-item label="车牌号"><el-input v-model="transferForm.vehiclePlate" placeholder="请输入车牌号" /></el-form-item><el-form-item label="车辆类型"><el-select v-model="transferForm.vehicleType" clearable placeholder="请选择"><el-option v-for="option in vehicleTypeOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="车辆大小"><el-select v-model="transferForm.vehicleSize" clearable placeholder="请选择"><el-option v-for="option in vehicleSizeOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item label="预约账号"><el-select v-model="transferForm.appointmentAccount" clearable placeholder="请选择"><el-option v-for="option in appointmentAccountOptions" :key="option.value" v-bind="option" /></el-select></el-form-item><el-form-item v-if="isBuyAppointment" label="卖家姓名" required><el-input v-model="transferForm.sellerName" placeholder="请输入卖家姓名" /></el-form-item></div><el-form-item label="托盘箱唛确认"><el-input v-model="transferForm.cartonMarksJson" type="textarea" :rows="3" placeholder="按实际托盘填写 JSON,例如 {"PALLET-001":["箱唛1","箱唛2"]}" /><div class="form-hint">入库未逐箱绑定时,在出库前确认箱唛属于哪个实际托盘。</div></el-form-item><h3>费用信息(USD)</h3><div class="transfer-grid"><el-form-item label="派送费用" required><el-input-number v-model="transferForm.deliveryFee" :min="0" :precision="2" controls-position="right" placeholder="请输入派送费用" /></el-form-item><el-form-item v-if="needsLoadingFee" label="装车费" required><el-input-number v-model="transferForm.loadingFee" :min="0" :precision="2" controls-position="right" placeholder="请输入装车费" /></el-form-item><el-form-item v-if="isBuyAppointment" label="买约费用" required><el-input-number v-model="transferForm.appointmentFee" :min="0" :precision="2" controls-position="right" placeholder="请输入买约费用" /></el-form-item></div><el-form-item label="备注"><el-input v-model="transferForm.remark" type="textarea" :rows="3" placeholder="请输入派送备注(选填)" /></el-form-item><el-alert title="费用不会自动带出,请手工填写后提交;提交后将生成待出库单、待派送单,并保存费用明细" type="info" :closable="false" /></el-form></AppDialog>
|
||||
<AppDialog v-model="editOpen" title="编辑排车" width="min(900px, calc(100vw - 48px))" confirm-text="保存" :confirm-loading="editSaving" @confirm="submitEdit"><el-alert :title="`当前状态:${editRow?.status || '—'};普通编辑不会修改排车状态。`" type="info" :closable="false" /><el-form label-position="top"><div class="edit-grid"><el-form-item label="车牌号"><el-input v-model="editForm.vehiclePlate" placeholder="请输入车牌号" /></el-form-item><el-form-item label="司机"><el-input v-model="editForm.driverName" placeholder="请输入司机姓名" /></el-form-item><el-form-item label="联系电话"><el-input v-model="editForm.driverPhone" placeholder="请输入联系电话" /></el-form-item><el-form-item label="拖车公司名称"><el-input v-model="editForm.carrierName" placeholder="请输入拖车公司名称" /></el-form-item><el-form-item label="路线"><el-input v-model="editForm.route" placeholder="请输入运输路线" /></el-form-item><el-form-item label="发车时间"><el-date-picker v-model="editForm.departAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" /></el-form-item><el-form-item label="预计到达时间"><el-date-picker v-model="editForm.estimatedArrivalAt" type="datetime" value-format="YYYY-MM-DDTHH:mm:ss" /></el-form-item><el-form-item label="FBA预约号"><el-input v-model="editForm.fbaAppointmentNo" placeholder="请输入 FBA 预约号" /></el-form-item><el-form-item label="FBA预约状态"><el-select v-model="editForm.fbaAppointmentStatus" clearable><el-option label="待预约" value="PENDING" /><el-option label="已预约" value="BOOKED" /><el-option label="已确认" value="CONFIRMED" /><el-option label="已取消" value="CANCELLED" /></el-select></el-form-item><el-form-item label="车辆预约状态"><el-select v-model="editForm.vehicleAppointmentStatus" clearable><el-option label="待预约" value="PENDING" /><el-option label="已预约" value="BOOKED" /><el-option label="已确认" value="CONFIRMED" /><el-option label="已取消" value="CANCELLED" /></el-select></el-form-item></div></el-form></AppDialog>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user