From 6f8dc920ca917c55bd80a49750549f62a50fcc4d Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 28 Oct 2026 11:06:42 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AE=A1=E8=B4=B9=E9=A2=84?= =?UTF-8?q?=E8=A7=88=E4=B8=8E=E6=B5=8B=E9=87=8F=E7=B2=BE=E5=BA=A6=E5=B9=B6?= =?UTF-8?q?=E7=B2=BE=E7=AE=80=E4=BD=9C=E4=B8=9A=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/business-index.md | 2 +- .../components/ContainerCustomsPanel.test.ts | 13 +- .../components/ContainerCustomsPanel.vue | 7 +- src/modules/inbounds/fee/api.test.ts | 10 + src/modules/inbounds/fee/api.ts | 11 +- src/modules/inbounds/fee/types.ts | 26 ++ src/modules/inbounds/index.ts | 3 +- .../review/MeasurementReviewPanel.vue | 6 +- .../DispatchDocumentsDialog.test.ts | 10 +- .../components/DispatchDocumentsDialog.vue | 2 +- .../DispatchWorkflowDialog.layout.test.ts | 6 +- .../components/DispatchWorkflowDialog.vue | 4 +- .../dispatchPresentation.ts | 2 +- .../pages/DispatchesView.test.ts | 5 +- .../pages/DispatchesView.vue | 6 +- .../pages/LastMileOrdersView.behavior.test.ts | 3 +- .../pages/LastMileOrdersView.vue | 4 +- .../UnifiedOrdersView.fulfillment.test.ts | 341 ++++++++++++++++-- .../pages/UnifiedOrdersView.test.ts | 19 +- .../pages/UnifiedOrdersView.vue | 189 ++++++---- src/styles/adminDensity.test.ts | 3 +- 21 files changed, 522 insertions(+), 150 deletions(-) diff --git a/docs/business-index.md b/docs/business-index.md index 0153d172..b687483a 100644 --- a/docs/business-index.md +++ b/docs/business-index.md @@ -29,7 +29,7 @@ | 订单列表、批量审核预览、详情、商品图片与入库费用 | `modules/unified-orders/`、`modules/first-leg-orders/`;平台商品图片由 `modules/unified-orders/api/unifiedOrders.ts` 按客户与 fileId 下载 | `order/`、`firstleg/`、Finance;旧系统导入固定归属“IT测试公司”,前端不提交或选择客户,逐行结果展示入仓码与失败原因。图片读取归 `file/` 的 `PlatformProductImageController`。Order 状态为 `SUBMITTED` 时统一列表必须保持 `PENDING_REVIEW/APPROVE_ORDER`,不得被已存在的 Firstleg 准备中入库投影覆盖。批量审核弹框通过 `unifiedOrders.ts` 的 `/orders/first-leg/batch-review-preview` 一次取回全部预览,按服务端 `canApprove/blockReason` 展示,不逐票请求详情或用内部 ID 作标题 | | 客户原始申报资料编辑与渠道变更 | `modules/unified-orders/components/OrderDeclarationEditorDialog.vue`、`api/unifiedOrders.ts`、`pages/UnifiedOrdersView.vue` | 使用订单版本和货物 `itemNo` 整体提交,不允许增删货物行;只编辑客户申报快照,不覆盖仓库/PDA 实测、测量审核或计费事实。渠道变化需二次确认并由 Backend `order/` 联查 `firstleg/` 的分柜、同柜、装柜与重新计价门禁;国内收货仓或中转仓变化必须重新下单 | | 入库扣货、解除与凭证历史 | `modules/unified-orders/pages/UnifiedOrdersView.vue`、`modules/inbounds/core/api.ts`、`types.ts` | 页面只消费服务端 `holdFlag/currentHoldReason/canHold/canRelease/actionBlockReason/history`;扣货与解除提交当前入库版本、幂等键、必填原因和可选凭证,凭证下载只走 Firstleg 授权入口。Backend `firstleg/` 的 `InboundHoldService`、`InboundHoldGuard`、`InboundHoldEvidenceService`,文件归 `file/` | -| 入库、测量、称重库位状态、客户箱规策略与计价主数据 | 测量弹框/工作区 `modules/inbounds/measurement/InboundMeasurementDialog.vue`、`InboundMeasurementPanel.vue`,称重 `modules/inbounds/weight/components/WeightOcrPanel.vue`;主数据 `modules/fba-pricing/`、`modules/dimension-config/`、`modules/customers/components/CustomerBillingRulesPanel.vue`、`modules/fee-items/` | 客户固定四档箱规,默认计算方式为 `ACTUAL/ADD/MULTIPLY`;未启用默认调整规则时按实测原尺寸计算,仓库修正值替换对应实测边。测量行可选实际尺寸、箱规尺寸策略或客户默认尺寸系数,保存后以 Backend `firstleg/` 返回的外箱尺寸、体积重和计费基数为准,不在页面独立重算。费用确认的箱数、毛重汇总当前 `actualMeasurementRows`,计费体积默认取已保存的 `outerMeasurementRows` 汇总,最终按用户确认值提交,体积来源标记为 `OUTER`;不得用可能滞后的工作区顶层合计覆盖明细。称重页在每张图片卡片只展示服务端 `locationCode/slotStatus/sharedOverflow`,不渲染或自行推导整票预留引导;删除图片须提示槽位仍归本票保留,不得按图片数组下标推导托盘或库位 | +| 入库、测量、称重库位状态、客户箱规策略与计价主数据 | 测量弹框/工作区 `modules/inbounds/measurement/InboundMeasurementDialog.vue`、`InboundMeasurementPanel.vue`,称重 `modules/inbounds/weight/components/WeightOcrPanel.vue`;主数据 `modules/fba-pricing/`、`modules/dimension-config/`、`modules/customers/components/CustomerBillingRulesPanel.vue`、`modules/fee-items/` | 客户固定四档箱规,默认计算方式为 `ACTUAL/ADD/MULTIPLY`;未启用默认调整规则时按实测原尺寸计算,仓库修正值替换对应实测边。测量行可选实际尺寸、箱规尺寸策略或客户默认尺寸系数,保存后以 Backend `firstleg/` 返回的外箱尺寸、体积重和计费基数为准,不在页面独立重算。费用确认的箱数、毛重汇总当前 `actualMeasurementRows`,计费体积默认取服务端已保存的 `outerMeasurementRows` 汇总;通过只读 `/fees/preview` 刷新当前重量、体积和人工单价的计费量与金额,预览未完成或失败时不得发送;最终按用户确认值提交,体积来源标记为 `OUTER`;不得用可能滞后的工作区顶层合计覆盖明细。称重页在每张图片卡片只展示服务端 `locationCode/slotStatus/sharedOverflow`,不渲染或自行推导整票预留引导;删除图片须提示槽位仍归本票保留,不得按图片数组下标推导托盘或库位 | | 一件代发托盘打印 | `modules/unified-orders/pages/UnifiedOrdersView.vue` 的“托盘打印”页签、`modules/inbounds/core/DropshipPalletPrintPanel.vue`、`core/api.ts` | 仅一件代发订单展示;测量审核预生成托盘码后由 Admin 电脑端打印,支持本票全部托盘合并打印和单托补打。PDA 只扫码组托,不承担 PDF 下载或打印机连接 | | 入库单扣货、解除、凭证与历史 | `modules/inbounds/pages/InboundOrdersView.vue`、详情工作区、`modules/inbounds/core/api.ts` | 列表展示服务端 `holdFlag/currentHoldReason` 并支持筛选;详情只按 `canHold/canRelease/actionBlockReason` 开放操作。扣货与解除提交当前 `version`、原因和独立幂等键,可选凭证先按明确客户与入库单上传;历史文件只调用专用下载入口,不把上传成功但未提交命令的文件冒充扣货证据 | | 客户专属价格簿、柜级阶梯运费与受控调价 | 列表/上传 `modules/fba-pricing/carrier-rates/pages/CarrierRatesView.vue`,版本详情/仓库映射/确认 `modules/fba-pricing/carrier-rates/pages/CarrierRateDetailView.vue`,路由 `modules/fba-pricing/routes.ts`;`modules/inbounds/fee/`、`modules/unified-orders/components/InboundFeeDetailManager.vue` | 列表与上传后按 `versionId` 进入 `fba-carrier-rate-detail` 独立详情页,价格行只读;回归入口 `modules/fba-pricing/carrier-rates/pages/CarrierRatesView.test.ts`。价格簿页面显式选择公共报价或具体客户;列表、上传、确认只作用于该 `customerId`,不得混合版本。计价优先消费客户专属 ACTIVE 版本,无专属版本才回退公共 ACTIVE;柜级阶梯金额和各客户分摊只展示服务端结果。调整计费量只能在服务端返回的上下 10% 范围内并提交当前费用版本、原因与二次确认;私人仓尾程派送费使用稳定 `PRIVATE_LAST_MILE_DELIVERY` 代码且只对 `PRIVATE` 目的地开放;一件代发须在预装确认前完成试算,计价后保持服务端 `WAIT_PALLETIZE` 状态 | diff --git a/src/modules/container-loading/components/ContainerCustomsPanel.test.ts b/src/modules/container-loading/components/ContainerCustomsPanel.test.ts index 2fe4daf0..4ec3d45e 100644 --- a/src/modules/container-loading/components/ContainerCustomsPanel.test.ts +++ b/src/modules/container-loading/components/ContainerCustomsPanel.test.ts @@ -215,7 +215,7 @@ describe('ContainerCustomsPanel', () => { expect(wrapper.text()).toContain('覆盖预录单') expect(wrapper.text()).toContain('报关行回执.pdf') expect(wrapper.text()).toContain('真实报关完税文件.pdf') - expect(wrapper.text()).toContain('确认 · 2026/8/29 11:15:00 · 客户一') + expect(wrapper.text()).not.toContain('确认 · 2026/8/29 11:15:00 · 客户一') wrapper.unmount() wrapper = await render('DECLARING', false, true) @@ -237,10 +237,12 @@ describe('ContainerCustomsPanel', () => { wrapper.unmount() }) - it('隐藏资料版本但保留预录单历史、原文件名和上传并发版本', async () => { + it('隐藏预录单历史和资料版本,保留当前确认状态、原文件名和上传并发版本', async () => { const value = customs('DECLARING', true) const document = value.documents[0]! document.receipt!.version = 7 + document.receiptStatus = 'CONFIRMED' + document.confirmationHistory.unshift({ id: '2092438843630391413', receiptVersion: 6, action: 'REJECT', reason: '请更新预录单', operatorName: '客户一', createdAt: '2026-08-29T10:55:00' }) document.receiptHistory = [{ version: 6, file: { ...document.receipt!, version: 6 }, operatorName: '报关员', createdAt: '2026-08-29T10:50:00' }] api.get.mockResolvedValue(value) const wrapper = mount(ContainerCustomsPanel, { @@ -250,8 +252,11 @@ describe('ContainerCustomsPanel', () => { await flushPromises() expect(wrapper.findAll('th').map(header => header.text())).toEqual(['操作', '关联入仓码', '报关行状态', '客户退税资料', '预录单', '真实报关完税文件', '客户', '预录单情况']) expect(wrapper.text()).not.toMatch(/版本|退税资料 v|回执 v|完税 v/) - expect(wrapper.text()).toContain('预录单历史:2026/8/29 10:50:00 · 报关员') - expect(wrapper.text()).toContain('确认 · 2026/8/29 11:15:00 · 客户一') + expect(wrapper.findAll('.confirmation-history-note, .receipt-history-note')).toHaveLength(0) + expect(wrapper.text()).not.toContain('预录单历史') + expect(wrapper.text()).not.toContain('确认 · 2026/8/29 11:15:00 · 客户一') + expect(wrapper.text()).not.toContain('请更新预录单') + expect(wrapper.text()).toContain('客户已确认预录单') expect(wrapper.find('[aria-label="下载预录单 报关行回执.pdf"]').exists()).toBe(true) const selected = new File(['receipt'], '新预录单.pdf', { type: 'application/pdf' }) const onChange = wrapper.findAllComponents({ name: 'ElUpload' })[0]!.props('onChange') as (upload: { raw: File }) => void diff --git a/src/modules/container-loading/components/ContainerCustomsPanel.vue b/src/modules/container-loading/components/ContainerCustomsPanel.vue index d3540e54..c4cc64e6 100644 --- a/src/modules/container-loading/components/ContainerCustomsPanel.vue +++ b/src/modules/container-loading/components/ContainerCustomsPanel.vue @@ -79,9 +79,6 @@ const statusText: Record = { function documentCustomers(document: ContainerCustomsDocument) { return [...new Set((customs.value?.inbounds || []).filter((item) => document.inboundIds.includes(item.inboundId)).map((item) => item.customerName))].join('、') || '—' } -function confirmationActionLabel(action: ContainerCustomsDocument['confirmationHistory'][number]['action']) { - return action === 'CONFIRM' ? '确认' : '打回' -} function formatDateTime(value: string | null | undefined) { if (!value) return '—' const date = new Date(value) @@ -327,7 +324,7 @@ onMounted(load) {{ document.inboundCodes.join('、') || '—' }} {{ brokerSubmitted ? '已发送报关行执行' : brokerSubmission ? '已首次发送,等待预录单确认' : '待首次发送报关行' }}
{{ document.source.fileName }}下载
-
{{ document.receipt.fileName }}下载
未上传
{{ document.finalDocument.fileName }}下载
未上传{{ documentCustomers(document) }}{{ confirmationActionLabel(history.action) }} · {{ formatDateTime(history.createdAt) }} · {{ history.operatorName || '—' }} · {{ history.reason }}预录单历史:{{ formatDateTime(history.createdAt) }} · {{ history.operatorName || '—' }}{{ document.receipt ? (document.receiptStatus === 'CONFIRMED' ? '客户已确认预录单' : document.receiptStatus === 'REJECTED' ? '客户已打回预录单' : '待客户确认预录单') : '待上传预录单' }} +
{{ document.receipt.fileName }}下载
未上传
{{ document.finalDocument.fileName }}下载
未上传{{ documentCustomers(document) }}{{ document.receipt ? (document.receiptStatus === 'CONFIRMED' ? '客户已确认预录单' : document.receiptStatus === 'REJECTED' ? '客户已打回预录单' : '待客户确认预录单') : '待上传预录单' }} @@ -363,5 +360,5 @@ onMounted(load) diff --git a/src/modules/inbounds/fee/api.test.ts b/src/modules/inbounds/fee/api.test.ts index 0725148d..096dc7fa 100644 --- a/src/modules/inbounds/fee/api.test.ts +++ b/src/modules/inbounds/fee/api.test.ts @@ -7,6 +7,7 @@ import { createFeeActionIdempotencyKey, createFeeAdjustmentIdempotencyKey, getLatestInboundFee, + previewInboundFee, sendInboundFee, } from './api' @@ -32,6 +33,15 @@ describe('inbound fee API', () => { expect(http.get).not.toHaveBeenCalled() }) + it('previews selected outer billing values without a write idempotency header', async () => { + const preview = { actualWeightKg: 570.5, actualVolumeCbm: 3.894, chargeableQuantity: 650.298, priceMatched: true } + vi.mocked(http.post).mockResolvedValue({ code: '200', message: 'success', data: preview }) + const payload = { selectedWeightKg: 570.5, selectedVolumeCbm: 3.894, selectedUnitPrice: 13.6, weightSource: 'ACTUAL', volumeSource: 'OUTER' } + + await expect(previewInboundFee('10001', '88', payload)).resolves.toEqual(preview) + expect(http.post).toHaveBeenCalledWith('/platform/v1/inbounds/88/fees/preview', payload, { params: { tenantId: '10001' } }) + }) + it('loads the latest immutable fee snapshot with string IDs', async () => { vi.mocked(http.get).mockResolvedValue({ code: '200', message: 'success', data: snapshot }) diff --git a/src/modules/inbounds/fee/api.ts b/src/modules/inbounds/fee/api.ts index cebe6bf9..2a85c0ac 100644 --- a/src/modules/inbounds/fee/api.ts +++ b/src/modules/inbounds/fee/api.ts @@ -1,5 +1,5 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http' -import type { AdjustPublishedInboundFeeRequest, InboundFeeSnapshot } from './types' +import type { AdjustPublishedInboundFeeRequest, InboundFeePreview, InboundFeePreviewRequest, InboundFeeSelectionRequest, InboundFeeSnapshot } from './types' function feeBasePath(inboundId: string) { const identifier = String(inboundId ?? '').trim() @@ -15,7 +15,14 @@ export async function getLatestInboundFee(tenantId: string, inboundId: string) { return unwrapApiResponse(response) } -export async function calculateInboundFee(tenantId: string, inboundId: string, payload: { selectedWeightKg: number; selectedVolumeCbm: number; weightSource: string; volumeSource: string }, idempotencyKey: string) { +export async function previewInboundFee(tenantId: string, inboundId: string, payload: InboundFeePreviewRequest) { + const response = await http.post>( + `${feeBasePath(inboundId)}/preview`, payload, { params: { tenantId } }, + ) + return unwrapApiResponse(response) +} + +export async function calculateInboundFee(tenantId: string, inboundId: string, payload: InboundFeeSelectionRequest, idempotencyKey: string) { const response = await http.post>( `${feeBasePath(inboundId)}/calculate`, payload, { params: { tenantId }, headers: { 'Idempotency-Key': idempotencyKey } }, diff --git a/src/modules/inbounds/fee/types.ts b/src/modules/inbounds/fee/types.ts index f658cd07..017aaf70 100644 --- a/src/modules/inbounds/fee/types.ts +++ b/src/modules/inbounds/fee/types.ts @@ -1,6 +1,32 @@ export type InboundFeeStatus = 'CALCULATED' | 'CONFIRMED' | 'SENT' | 'SUPERSEDED' | string export type InboundChargeMode = 'KG' | 'CBM' | string +export interface InboundFeeSelectionRequest { + selectedWeightKg: number + selectedVolumeCbm: number + weightSource: string + volumeSource: string +} + +export interface InboundFeePreview { + actualWeightKg: number + actualVolumeCbm: number + chargeableQuantity: number + chargeUnit: string + conversionFactor: number + roundingScale: number + roundingMode: string + unitPrice: number | null + additionalAmount: number + totalAmount: number | null + currency: string + priceMatched: boolean +} + +export interface InboundFeePreviewRequest extends InboundFeeSelectionRequest { + selectedUnitPrice: number | null +} + export interface InboundFeeSnapshot { feeId: string feeNo: string diff --git a/src/modules/inbounds/index.ts b/src/modules/inbounds/index.ts index acc914b2..2467f2ed 100644 --- a/src/modules/inbounds/index.ts +++ b/src/modules/inbounds/index.ts @@ -14,9 +14,10 @@ export { createFeeAdjustmentIdempotencyKey, createFeeActionIdempotencyKey, getLatestInboundFee, + previewInboundFee, sendInboundFee, } from './fee/api' -export type { AdjustPublishedInboundFeeRequest, InboundFeeSnapshot } from './fee/types' +export type { AdjustPublishedInboundFeeRequest, InboundFeePreview, InboundFeeSnapshot } from './fee/types' export { downloadWeightImage } from './weight/api/weightOcr' export type { InboundMeasurementDetail } from './measurement/types' export type { InboundHoldCommand, InboundHoldDetail, InboundHoldEvidenceUpload, InboundHoldHistoryEntry } from './core/types' diff --git a/src/modules/inbounds/review/MeasurementReviewPanel.vue b/src/modules/inbounds/review/MeasurementReviewPanel.vue index 67f6046f..39105005 100644 --- a/src/modules/inbounds/review/MeasurementReviewPanel.vue +++ b/src/modules/inbounds/review/MeasurementReviewPanel.vue @@ -213,7 +213,7 @@ defineExpose({ confirmApprove, approvalButtonLabel, isPending, canReview, action 箱数{{ formatNumber(detail.originalPackageCount, 0) }}{{ formatNumber(detail.actualPackageCount, 0) }}{{ formatDifference(detail.actualPackageCount, detail.originalPackageCount, 0) }} 毛重(kg){{ formatNumber(detail.originalGrossWeightKg) }}{{ formatNumber(detail.actualGrossWeightKg) }}{{ formatDifference(detail.actualGrossWeightKg, detail.originalGrossWeightKg, 3, ' kg') }} - 体积(m³){{ formatNumber(detail.originalVolumeCbm, 4) }}{{ formatNumber(detail.actualVolumeCbm, 4) }}{{ formatDifference(detail.actualVolumeCbm, detail.originalVolumeCbm, 4, ' m³') }} + 体积(m³){{ formatNumber(detail.originalVolumeCbm, 3) }}{{ formatNumber(detail.actualVolumeCbm, 3) }}{{ formatDifference(detail.actualVolumeCbm, detail.originalVolumeCbm, 3, ' m³') }}
货物净重 {{ formatNumber(detail.actualNetWeightKg) }} kg体积重 {{ formatNumber(detail.volumeWeightKg) }} kg
@@ -249,10 +249,10 @@ defineExpose({ confirmApprove, approvalButtonLabel, isPending, canReview, action - + - + diff --git a/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.test.ts b/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.test.ts index 0e50e3cf..593f344c 100644 --- a/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.test.ts +++ b/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.test.ts @@ -4,7 +4,7 @@ import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, expect, it, vi } from 'vitest' import DispatchDocumentsDialog from './DispatchDocumentsDialog.vue' import DeliveryDocumentsDialog from './DeliveryDocumentsDialog.vue' -import { detail } from '../dispatchTestFixtures' +import { destination, detail } from '../dispatchTestFixtures' import { getDispatch } from '../api/dispatches' import { deliveryDocumentCategories, getDeliveryDocuments, saveDeliveryDocuments, type DeliveryDocuments } from '../api/deliveryDocuments' const can = vi.hoisted(() => vi.fn(() => true)) @@ -37,8 +37,13 @@ it.each(['LOADING', 'AFTER_LOADING'] as const)('opens loading documents in %s be expect(wrapper.findAll('[role="tab"]').find(tab => tab.text() === '装车资料')!.attributes('aria-selected')).toBe('true') expect(getDeliveryDocuments).not.toHaveBeenCalled(); wrapper.unmount() }) -it('reads existing delivery files in one dialog and saves receipt without requiring loading or BOL files', async () => { +it('hides the single delivery selector and saves receipt files to that delivery without requiring loading or BOL files', async () => { + const value = detail({ stage: 'DELIVERING' }) + value.documents.deliveries = [{ deliveryNo: 'DL-1', customerId: '101', customerName: '客户甲', groupKey: 'A', outboundNo: 'OB-1', destination: destination(), status: 'DELIVERING', packageCount: 1, palletCount: 1 }] + vi.mocked(getDispatch).mockResolvedValueOnce(value) const wrapper = setup(); await flushPromises() + expect(wrapper.findComponent({ name: 'ElSelect' }).exists()).toBe(false) + expect(wrapper.find('[aria-label="选择派送单"]').exists()).toBe(false) expect(getDeliveryDocuments).toHaveBeenCalledWith('DL-1', '101') expect(wrapper.text()).toContain('existing-pod.pdf') expect(wrapper.text()).not.toContain('资料核对') @@ -52,6 +57,7 @@ it('reads existing delivery files in one dialog and saves receipt without requir }) it('switches customer scope exactly and keeps unsaved delivery edits when switching tabs', async () => { const wrapper = setup(); await flushPromises() + expect(wrapper.findComponent({ name: 'ElSelect' }).exists()).toBe(true) const receipt = wrapper.getComponent(DeliveryDocumentsDialog) await receipt.findAll('button').find(button => button.text() === '删除')!.trigger('click') expect(receipt.text()).not.toContain('existing-pod.pdf') diff --git a/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.vue b/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.vue index 37eb1fe7..18bd5e07 100644 --- a/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.vue +++ b/src/modules/overseas-warehouse/components/DispatchDocumentsDialog.vue @@ -63,7 +63,7 @@ watch(() => [open.value, props.dispatchNo] as const, ([visible]) => {
- +

{{ access.can(permission.deliveryList) ? '暂无关联派送单' : '当前账号无派送资料查看权限' }}

diff --git a/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.layout.test.ts b/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.layout.test.ts index b8b9e9ca..76cf5b18 100644 --- a/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.layout.test.ts +++ b/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.layout.test.ts @@ -199,7 +199,7 @@ describe('dispatch workflow native layout', () => { expect(dialog.get('[aria-label="本车审核依据"]').text()).toContain('联系电话') expect(dialog.get('[aria-label="排车与预约"]').findAll('input')).toHaveLength(0) const photos = dialog.findAllComponents(DispatchEvidenceFiles) - expect(photos.map(photo => photo.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片']) + expect(photos.map(photo => photo.props('label'))).toEqual(['车牌图片', '司机照片', '驾驶证照片']) expect(photos.every(photo => photo.props('readonly'))).toBe(true) expect(dialog.get('[aria-label="操作原因与意见"] h3').text()).toBe('审核意见') expect(dialog.text()).not.toContain('请核对本车资源与本趟证据') @@ -246,7 +246,7 @@ describe('dispatch workflow native layout', () => { vi.mocked(api.updateDispatch).mockRejectedValueOnce(new Error('unknown edit')).mockImplementationOnce(async (_, body) => command('EDIT', body.idempotencyKey)) const { wrapper, dialog } = await render('EDIT', undefined, true) const fields = dialog.findAllComponents(DispatchEvidenceFiles) - expect(fields.map(item => item.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片']) + expect(fields.map(item => item.props('label'))).toEqual(['车牌图片', '司机照片', '驾驶证照片']) expect(fields[0]!.props('modelValue')).toEqual(['301']) expect(dialog.get('.workflow-photo-grid').findAll('figure')).toHaveLength(3) for (const [index, files] of fields.entries()) { @@ -280,7 +280,7 @@ describe('dispatch workflow native layout', () => { vi.mocked(api.getDispatch).mockResolvedValueOnce(value) await wrapper.setProps({ modelValue: false }); await wrapper.setProps({ modelValue: true }); await flushPromises() const fields = dialog.findAllComponents(DispatchEvidenceFiles) - expect(fields.map(item => item.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片']) + expect(fields.map(item => item.props('label'))).toEqual(['车牌图片', '司机照片', '驾驶证照片']) expect(fields.every(item => item.props('readonly') && item.props('photoCard'))).toBe(true) expect(fields[1]!.props('modelValue')).toEqual(['402', '403']) expect(dialog.findAll('input[type="file"]')).toHaveLength(0) diff --git a/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.vue b/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.vue index a3f74bbb..f26827b3 100644 --- a/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.vue +++ b/src/modules/overseas-warehouse/components/DispatchWorkflowDialog.vue @@ -137,7 +137,7 @@ function payload(): object { if (!exception.exceptionType) throw new DispatchInputError('请选择异常类型') return { ...common, exceptionType: exception.exceptionType, returnDirection: exception.returnDirection || null, description: exception.description.trim() || null, imageFileIds: [...exception.imageFileIds] } satisfies MarkDispatchException case 'EDIT': - if (missingVehicle.value && vehicle.value.evidenceFileIds.length) throw new DispatchInputError('请先选择车辆,再保存车辆图片;当前图片尚未保存') + if (missingVehicle.value && vehicle.value.evidenceFileIds.length) throw new DispatchInputError('请先选择车辆,再保存车牌图片;当前图片尚未保存') if (missingDriver.value && (driver.value.evidence?.fileIds.length || driverPhotos.value.length)) throw new DispatchInputError('请先选择司机,再保存司机照片和驾驶证照片;当前图片尚未保存') return { ...common, vehicle: missingVehicle.value ? null : vehicle.value, driver: missingDriver.value ? null : driver.value, maintenance: vehicleMaintenancePayload(form.maintenance), plannedDepartureAt: form.plannedDepartureAt || null, estimatedArrivalAt: form.estimatedArrivalAt || null, remark: form.remark || null } satisfies UpdateDispatch case 'DISPATCH_REVIEW': case 'LOADING_REVIEW': @@ -268,7 +268,7 @@ watch([open, () => props.dispatchNo, () => props.action, () => props.operation],