From b20c3ac104b7179356b245e4ad2da899baab9cba Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Sat, 10 Oct 2026 14:42:41 +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 --- .../overseas-warehouse/api/lastMileOrders.ts | 3 ++- .../pages/LastMileOrderDetailView.test.ts | 25 +++++++++++++++++++ .../pages/LastMileOrderDetailView.vue | 22 ++++++++++++---- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/modules/overseas-warehouse/api/lastMileOrders.ts b/src/modules/overseas-warehouse/api/lastMileOrders.ts index 5a207c50..23ba0e59 100644 --- a/src/modules/overseas-warehouse/api/lastMileOrders.ts +++ b/src/modules/overseas-warehouse/api/lastMileOrders.ts @@ -356,7 +356,8 @@ export interface ChangeContainerReceivingWarehousePayload { export interface LastMileOrderDetailDto { returnReceipt?: { - order: { returnNo: string; receivedQuantity: number; expectedQuantity: number } + order: { returnNo: string; status?: string; receivedQuantity: number; expectedQuantity: number } + items?: Array<{ skuCode: string; productNameZh?: string; expectedQuantity: number; receivedQuantity: number }> receipts: Array<{ receiptNo: string; unitType: string; unitCode?: string; palletCode?: string; receivedQuantity: number; boxCount: number; signedAt: string }> cartons?: Array<{ receiptNo: string; palletCode?: string; cartonMark: string; countStatus: string; countedAt?: string; items: Array<{ skuCode: string; productNameZh?: string; quantity: number }> }> } | null diff --git a/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.test.ts b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.test.ts index 517da0dc..c66388cc 100644 --- a/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.test.ts +++ b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.test.ts @@ -13,6 +13,31 @@ vi.mock('../api/warehouseTransfers', () => ({ getWarehouseTransfer: vi.fn() })) enableAutoUnmount(afterEach) describe('LastMileOrderDetailView domestic detail alignment', () => { + it.each(['PENDING_REVIEW', 'PARTIALLY_RECEIVED'])('shows SKU forecast before receipt completion (%s) without forecast box counts', async status => { + vi.mocked(getLastMileOrder).mockResolvedValue({ order: { id: 'return-tail', customerId: 'C1', orderType: 'RETURN_RELABEL', orderNo: 'RT-1', currentStatus: status, packageCount: 30 }, + cargo: [], fees: [], statusHistory: [], operationHistory: [], + returnReceipt: { order: { returnNo: 'RT-1', status, expectedQuantity: 30, receivedQuantity: 5 }, receipts: [], + items: [{ skuCode: 'SKU-FORECAST', productNameZh: '预报商品', expectedQuantity: 30, receivedQuantity: 5 }] } }) + const wrapper = mount(LastMileOrderDetailView, { global: { plugins: [ElementPlus] } }); await flushPromises() + expect(wrapper.get('[aria-label="基本信息"]').text()).not.toContain('箱数') + await wrapper.findAll('[role="tab"]').find(tab => tab.text().includes('货物信息'))!.trigger('click'); await flushPromises() + const panel = wrapper.get('.table-panel') + expect(panel.text()).toContain('SKU-FORECAST') + expect(panel.text()).toContain('预报商品') + expect(panel.text()).toContain('预报件数') + expect(panel.text()).toContain('30') + expect(panel.text()).not.toContain('箱数') + }) + it('uses actual cargo after return receipt completion', async () => { + vi.mocked(getLastMileOrder).mockResolvedValue({ order: { id: 'return-tail', customerId: 'C1', orderType: 'RETURN_RELABEL', orderNo: 'RT-1', currentStatus: 'INBOUND_COMPLETED' }, + cargo: [{ id: 'actual', skuCode: 'SKU-ACTUAL', productNameZh: '实收商品', productQuantity: 25, packageCount: 2 }], fees: [], statusHistory: [], operationHistory: [], + returnReceipt: { order: { returnNo: 'RT-1', status: 'COMPLETED', expectedQuantity: 30, receivedQuantity: 25 }, receipts: [], items: [{ skuCode: 'SKU-FORECAST', expectedQuantity: 30, receivedQuantity: 25 }] } }) + const wrapper = mount(LastMileOrderDetailView, { global: { plugins: [ElementPlus] } }); await flushPromises() + await wrapper.findAll('[role="tab"]').find(tab => tab.text().includes('货物信息'))!.trigger('click'); await flushPromises() + expect(wrapper.get('.table-panel').text()).toContain('SKU-ACTUAL') + expect(wrapper.get('.table-panel').text()).not.toContain('SKU-FORECAST') + expect(wrapper.get('.table-panel').text()).toContain('25') + }) it('shows signed box contents and actual quantities on return orders', async () => { vi.mocked(getLastMileOrder).mockResolvedValue({ order: { id: 'tail-return', customerId: 'customer-1', orderNo: 'RETU-1', orderType: 'RETURN_RELABEL', currentStatus: 'INBOUND_COMPLETED' }, cargo: [], fees: [], statusHistory: [], operationHistory: [], returnReceipt: { order: { returnNo: 'RT-1', receivedQuantity: 10, expectedQuantity: 10 }, receipts: [{ receiptNo: 'RR-1', unitType: 'PALLET', unitCode: 'PLT-1', receivedQuantity: 10, boxCount: 2, signedAt: '' }], diff --git a/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue index dcad996c..31a9c22f 100644 --- a/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue +++ b/src/modules/overseas-warehouse/pages/LastMileOrderDetailView.vue @@ -56,6 +56,10 @@ function feeStatusLabel(value: string) { return ({ PENDING_REVIEW: '待审核', function isSingleLineBusinessField(field: DetailField) { return field.label === '入仓码' || field.label === '集装箱号' } const referenceValue = computed(() => lastMileOrderReference(detail.value?.order.orderNo, '订单编码待补充')) +const isReturnOrder = computed(() => detail.value?.order.orderType === 'RETURN_RELABEL') +const showReturnForecast = computed(() => isReturnOrder.value && (detail.value?.returnReceipt?.order.status + ? detail.value.returnReceipt.order.status !== 'COMPLETED' + : ['PENDING_REVIEW', 'PENDING_RECEIPT', 'PARTIALLY_RECEIVED', 'REJECTED', 'CANCELLED'].includes(detail.value?.order.currentStatus || ''))) const orderFields = computed(() => { const order = detail.value?.order const fields: DetailField[] = [ @@ -93,7 +97,7 @@ const containerFields = computed(() => { { label: '实际到港时间', value: formatOverseasDateTime(order?.actualArrivalAt) }, { label: '预计到仓时间', value: formatOverseasDateTime(order?.estimatedWarehouseAt) }, { label: '货柜节点', value: containerNodeLabel(order?.containerNode) }, - ] + ].filter(field => !isReturnOrder.value || field.label !== '箱数') }) const commercialLink = computed(() => businessOrderLink(detail.value?.order.businessSource?.businessType, detail.value?.order.businessSource?.businessOrderNo, access.can)) const basicFields = computed(() => [...orderFields.value, ...containerFields.value]) @@ -104,7 +108,8 @@ const overviewFields = computed(() => { return [ { label: '客户', value: text(order?.customerName) }, { label: '海外仓', value: text(order?.overseasWarehouseName) }, - { label: warehouseTransfer ? (hasReceivedPackages ? '实收总箱数' : '客户预报总箱数') : '总箱数', value: (hasReceivedPackages ? order?.receivedPackageCount : order?.packageCount) == null ? '—' : `${hasReceivedPackages ? order?.receivedPackageCount : order?.packageCount} 箱` }, + ...(isReturnOrder.value ? [{ label: showReturnForecast.value ? '预报总件数' : '实收总件数', value: `${showReturnForecast.value ? detail.value?.returnReceipt?.order.expectedQuantity ?? order?.productQuantity ?? 0 : detail.value?.returnReceipt?.order.receivedQuantity ?? order?.productQuantity ?? 0} 件` }] + : [{ label: warehouseTransfer ? (hasReceivedPackages ? '实收总箱数' : '客户预报总箱数') : '总箱数', value: (hasReceivedPackages ? order?.receivedPackageCount : order?.packageCount) == null ? '—' : `${hasReceivedPackages ? order?.receivedPackageCount : order?.packageCount} 箱` }]), { label: '总托数', value: order?.palletCount == null ? '—' : order.palletCount + ' 托' }, { label: '货物毛重', value: order?.grossWeightKg == null ? '—' : order.grossWeightKg + ' kg' }, { label: '货物净重', value: order?.netWeightKg == null ? '—' : order.netWeightKg + ' kg' }, @@ -130,7 +135,10 @@ const documents = computed(() => { .filter((item) => item[1]).map(([type, number]) => ({ type: String(type), number: String(number), createdAt: '—', quote: '—' } as DocumentRow)) .concat((order.relabelApplications || []).map(application => ({ type: '换标单', number: application.relabelNo, createdAt: formatOverseasDateTime(application.createdAt), quote: '—', status: relabelApplicationStatusLabel(application.status), quantity: application.quantity }))) }) -const cargo = computed(() => (detail.value?.cargo || []).map((item) => ({ +const cargo = computed(() => showReturnForecast.value && detail.value?.returnReceipt?.items?.length + ? detail.value.returnReceipt.items.map(item => ({ id: item.skuCode, sku: item.skuCode, productName: text(item.productNameZh), + quantity: item.expectedQuantity, pallets: 0, cartons: 0, weight: 0, volume: 0 })) + : (detail.value?.cargo || []).map((item) => ({ id: String(item.id), sku: text(item.skuCode), productName: text(item.productNameZh), pallets: Number(item.palletCount || 0), cartons: Number(item.packageCount || 0), quantity: Number(item.productQuantity || 0), weight: Number(item.grossWeightKg || 0), volume: Number(item.volumeCbm || 0), }))) @@ -149,12 +157,16 @@ const logs = computed(() => { return [...statusRows, ...operationRows].sort((left, right) => right.sortAt.localeCompare(left.sortAt)) }) -const cargoColumns: DataTableColumn[] = [ +const cargoColumns = computed(() => showReturnForecast.value ? [ + { key: 'sku', prop: 'sku', label: 'SKU', minWidth: 150 }, + { key: 'productName', prop: 'productName', label: '中文品名', minWidth: 180 }, + { key: 'quantity', prop: 'quantity', label: '预报件数', width: 120, align: 'right' }, +] : [ { key: 'sku', prop: 'sku', label: 'SKU', minWidth: 150 }, { key: 'productName', prop: 'productName', label: '中文品名', minWidth: 180 }, { key: 'pallets', prop: 'pallets', label: '托数', width: 100, align: 'right' }, { key: 'cartons', prop: 'cartons', label: '箱数', width: 100, align: 'right' }, { key: 'quantity', prop: 'quantity', label: '件数', width: 100, align: 'right' }, { key: 'weight', prop: 'weight', label: '重量(kg)', width: 120, align: 'right' }, { key: 'volume', prop: 'volume', label: '体积(m³)', width: 120, align: 'right' }, -] +]) const documentColumns: DataTableColumn[] = [ { key: 'status', prop: 'status', label: '状态', minWidth: 130 }, { key: 'quantity', prop: 'quantity', label: '换标数量(件)', minWidth: 130 }, { key: 'type', prop: 'type', label: '单据类型', minWidth: 150 }, { key: 'number', prop: 'number', label: '单据编号', minWidth: 200 },