diff --git a/src/modules/overseas-warehouse/pages/InboundOrderDetailView.vue b/src/modules/overseas-warehouse/pages/InboundOrderDetailView.vue index b01e5da1..570b5ab5 100644 --- a/src/modules/overseas-warehouse/pages/InboundOrderDetailView.vue +++ b/src/modules/overseas-warehouse/pages/InboundOrderDetailView.vue @@ -16,7 +16,7 @@ function text(value: unknown) { return String(value ?? '').trim() || '—' } function status(value: unknown) { const labels: Record = { PENDING_ASSIGNMENT: '待入库', PDA_PENDING: '待入库', ASSIGNED: '待入库', PICKING: '拣货中', PARTIAL_INBOUND: '部分入库', COMPLETED: '全部入库' }; return labels[String(value || '')] || text(value) } const firstLine = computed(() => detail.value?.lines[0]) const fieldValues = computed>(() => ({ - 入库单号: text(detail.value?.planNo), 入仓码: text(firstLine.value?.inboundCode), 类型: '—', 货主: '—', 关联订单: text(detail.value?.sourceOrderId), + 入库单号: text(detail.value?.planNo), 入仓码: text(firstLine.value?.inboundCode), 类型: '—', 货主: '—', 关联订单: '—', 海外仓: text(firstLine.value?.destinationWarehouseName), FBA仓库: text(firstLine.value?.fbaWarehouseCode), 总箱数: detail.value?.expectedPackageCount ?? detail.value?.lines.reduce((sum, line) => sum + line.requiredPackageCount, 0) ?? 0, 应拣货数: detail.value?.expectedPackageCount ?? detail.value?.lines.reduce((sum, line) => sum + line.requiredPackageCount, 0) ?? 0, 实际拣货数: detail.value?.pickedPackageCount ?? detail.value?.allocations.reduce((sum, item) => sum + item.pickedPackageCount, 0) ?? 0, '计费体积(m³)': Number(detail.value?.totalVolumeCbm || 0), 总托盘数: Number(detail.value?.palletCount || 0), 状态: status(detail.value?.status), diff --git a/src/modules/overseas-warehouse/pages/InboundOrdersView.vue b/src/modules/overseas-warehouse/pages/InboundOrdersView.vue index bd6f8bd3..b7e046f2 100644 --- a/src/modules/overseas-warehouse/pages/InboundOrdersView.vue +++ b/src/modules/overseas-warehouse/pages/InboundOrdersView.vue @@ -60,7 +60,7 @@ function selectableRow(source: object) { const row = source as InboundOrderListItem if (!row.fbaWarehouse || row.fbaWarehouse === '—') return false const anchor = selectedRows.value[0] - return !anchor || anchor.fbaWarehouse === row.fbaWarehouse + return !anchor || (anchor.customerId === row.customerId && anchor.fbaWarehouse === row.fbaWarehouse) } const columns: DataTableColumn[] = [ diff --git a/src/modules/overseas-warehouse/pages/LastMileOrdersView.behavior.test.ts b/src/modules/overseas-warehouse/pages/LastMileOrdersView.behavior.test.ts index 68a4e37a..32912921 100644 --- a/src/modules/overseas-warehouse/pages/LastMileOrdersView.behavior.test.ts +++ b/src/modules/overseas-warehouse/pages/LastMileOrdersView.behavior.test.ts @@ -234,8 +234,7 @@ describe('LastMileOrdersView pending order and container conversion', () => { it('renders pending cabinets as an arrival-ordered workbench with the remaining inbound-code count', async () => { const nullArrival = { ...summary, sourceContainerBookingId: '2092873463568597002', containerNo: 'CONT-NULL', plannedArrivalAt: null, planCount: 0, pendingPlanCount: 15 } - const completed = { ...summary, sourceContainerBookingId: '2092873463568597003', containerNo: 'CONT-DONE', plannedInboundCodeCount: 15, pendingPlanCount: 0, allPlansReady: true } - vi.mocked(listContainerPlanProgress).mockResolvedValueOnce({ records: [completed, summary, nullArrival], page: 1, size: 10, total: 3, pages: 1 }) + vi.mocked(listContainerPlanProgress).mockResolvedValueOnce({ records: [summary, nullArrival], page: 1, size: 10, total: 2, pages: 1 }) const wrapper = await mountView() const containerTab = wrapper.findAll('.el-radio-button').find((item) => item.text().startsWith('柜子')) await containerTab!.trigger('click') @@ -250,7 +249,6 @@ describe('LastMileOrdersView pending order and container conversion', () => { expect(wrapper.text()).not.toContain('缺入仓码') expect(wrapper.text()).not.toContain('已生成 10 个计划') expect(wrapper.text()).not.toContain('存在5票尚未生成有效入库计划') - expect(wrapper.text()).not.toContain('CONT-DONE') expect(wrapper.text()).toContain('2 个柜待处理') expect(wrapper.text().indexOf('CONT-15')).toBeLessThan(wrapper.text().indexOf('CONT-NULL')) }) @@ -272,6 +270,10 @@ describe('LastMileOrdersView pending order and container conversion', () => { expect(document.body.textContent).toContain('LMO-1') expect(document.body.textContent).toContain('IN-1') expect(document.body.textContent).toContain('LIP-1') + expect(document.body.textContent).toContain('已生成入库计划') + expect(document.body.textContent).toContain('待收货') + expect(document.body.textContent).not.toContain('INBOUND_PLANNED') + expect(document.body.textContent).not.toContain('PENDING_RECEIVING') expect(document.body.querySelector('.el-dialog')).not.toBeNull() expect(document.body.querySelector('.el-drawer')).toBeNull() }) diff --git a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue index d5a11868..aa419c68 100644 --- a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue +++ b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue @@ -17,7 +17,7 @@ import { changeContainerReceivingWarehouse, convertLastMileContainerToInboundPla import OrderReferenceText from '../components/OrderReferenceText.vue' import { useAccessPreviewStore } from '@/core/access/accessPreview' import { overseasWarehousePermissions as permission } from '../permissions' -import { toLastMileOrderListItem } from '../lastMilePresentation' +import { lastMileStatusLabel, toLastMileOrderListItem } from '../lastMilePresentation' import type { LastMileOrderListItem } from '../types' import { saveCsv } from '@/shared/downloadCsv' @@ -175,15 +175,28 @@ const columns: DataTableColumn[] = [ const containerDetailColumns: DataTableColumn[] = [ { key: 'orderNo', prop: 'orderNo', label: '尾程单号', minWidth: 170 }, { key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 150 }, - { key: 'orderStatus', prop: 'orderStatus', label: '订单状态', width: 130 }, + { key: 'orderStatus', prop: 'orderStatus', label: '订单状态', width: 130, slotName: 'orderStatus' }, { key: 'planNo', prop: 'planNo', label: '独立入库计划', minWidth: 170 }, - { key: 'planStatus', prop: 'planStatus', label: '计划状态', width: 130 }, + { key: 'planStatus', prop: 'planStatus', label: '计划状态', width: 130, slotName: 'planStatus' }, { key: 'plannedArrivalAt', prop: 'plannedArrivalAt', label: '计划到仓时间', minWidth: 170, slotName: 'plannedArrivalAt' }, ] function formatDateTime(value: string | null | undefined) { return value ? value.replace('T', ' ').slice(0, 19) : '—' } +function inboundPlanStatusLabel(value: unknown) { + const code = String(value ?? '').trim() + return ({ + PENDING_ASSIGNMENT: '待入库', + ASSIGNED: '待入库', + PDA_PENDING: '待入库', + PENDING_RECEIVING: '待收货', + PICKING: '拣货中', + PARTIAL_INBOUND: '部分入库', + COMPLETED: '全部入库', + CANCELLED: '已取消', + } as Record)[code] || (code ? '未知状态' : '—') +} function canGenerateContainerPlans(container: ContainerPlanProgressSummaryDto) { return Boolean(container.containerNo && container.overseasWarehouseId @@ -226,9 +239,8 @@ async function load(model: QueryModel = filters.value) { createdFrom: dateRange[0] ? `${dateRange[0]}T00:00:00` : undefined, createdTo: dateRange[1] ? `${dateRange[1]}T23:59:59` : undefined, }) - const pendingRecords = result.records.filter((container) => pendingInboundCodeCount(container) > 0) - containerRows.value = pendingRecords - containerTotal.value = Math.max(0, result.total - (result.records.length - pendingRecords.length)) + containerRows.value = result.records + containerTotal.value = result.total } catch (error) { containerRows.value = [] containerTotal.value = 0 @@ -589,6 +601,8 @@ onMounted(() => { void load() }) 修改状态:{{ containerDetail.summary.receivingWarehouseLockReason || '当前已锁定' }} + + diff --git a/src/modules/overseas-warehouse/pages/prototypeContract.test.ts b/src/modules/overseas-warehouse/pages/prototypeContract.test.ts index 6a37fe8d..38883e24 100644 --- a/src/modules/overseas-warehouse/pages/prototypeContract.test.ts +++ b/src/modules/overseas-warehouse/pages/prototypeContract.test.ts @@ -151,7 +151,7 @@ describe('overseas warehouse first-batch prototype contract', () => { expect(tailLeg).toContain('') const inbound = source('InboundOrderDetailView.vue') expect(inbound).toContain('getInboundPlan') - expect(inbound).toContain('sourceOrderId') + expect(inbound).not.toContain('detail.value?.sourceOrderId') expect(inbound).toContain(' { expect(value).toContain('createFbaAppointment') expect(value).toContain('预计装车时间') expect(value).toContain('所选入库单必须属于同一 FBA 仓库') + expect(value).toContain('anchor.customerId === row.customerId') expect(value).toContain('预约') expect(value).toContain('智能排车') expect(value).toContain('type="primary" @click="openSmartDispatch"') diff --git a/src/modules/overseas-warehouse/pages/realApiMount.test.ts b/src/modules/overseas-warehouse/pages/realApiMount.test.ts index 0b3c45a1..f1d993e1 100644 --- a/src/modules/overseas-warehouse/pages/realApiMount.test.ts +++ b/src/modules/overseas-warehouse/pages/realApiMount.test.ts @@ -28,7 +28,7 @@ describe('overseas warehouse mounted real API pages', () => { vi.mocked(listInventoryLedger).mockReset().mockResolvedValue({ records: [{ id: '9007199254740997', customerId: '10001', customerName: '客户甲', destinationWarehouseCode: 'LAX', inboundCode: 'NYEXX2608300001', inboundOrderNo: 'IP-1', businessType: 'STOCK_IN', beforePackages: 0, deltaPackages: 10, afterPackages: 10, occurredAt: '2026-08-20T10:00:00' }], page: 1, size: 20, total: 1, pages: 1 }) vi.mocked(getLastMileOrder).mockReset().mockResolvedValue({ order: { id: '9007199254740999', customerId: '10001', customerName: '华远贸易', orderNo: 'LMO-260827-0001', currentStatus: 'PENDING_ASSIGNMENT' }, cargo: [], statusHistory: [], fees: [], operationHistory: [] }) }) - it('mounts inbound detail with a string-safe source order id', async () => { routing.route.params = { orderId: 'IP-1' }; routing.route.query = { customerId: '10001' }; const wrapper = mount(InboundOrderDetailView, { global: { plugins: [ElementPlus] } }); await flushPromises(); expect(getInboundPlan).toHaveBeenCalledWith('IP-1', '10001'); expect(wrapper.text()).toContain('9007199254740995'); expect(wrapper.text()).toContain('洛杉矶仓') }) + it('mounts inbound detail with business references without exposing the internal source order id', async () => { routing.route.params = { orderId: 'IP-1' }; routing.route.query = { customerId: '10001' }; const wrapper = mount(InboundOrderDetailView, { global: { plugins: [ElementPlus] } }); await flushPromises(); expect(getInboundPlan).toHaveBeenCalledWith('IP-1', '10001'); expect(wrapper.text()).toContain('IP-1'); expect(wrapper.text()).toContain('IN-1'); expect(wrapper.text()).not.toContain('9007199254740995'); expect(wrapper.text()).toContain('洛杉矶仓') }) it('mounts inventory ledger as localized read-only business facts without exposing internal ids', async () => { const wrapper = mount(InventoryTransactionsView, { global: { plugins: [createPinia(), ElementPlus] } }); await flushPromises(); expect(listInventoryLedger).toHaveBeenCalledWith(expect.objectContaining({ page: 1, size: 10 })); expect(listInventoryLedger).not.toHaveBeenCalledWith(expect.objectContaining({ customerId: expect.anything() })); expect(wrapper.text()).toContain('客户甲'); expect(wrapper.text()).toContain('NYEXX2608300001'); expect(wrapper.text()).not.toContain('10001'); expect(wrapper.text()).toContain('IP-1'); expect(wrapper.text()).toContain('10'); expect(wrapper.text()).toContain('入库'); expect(wrapper.text()).not.toContain('STOCK_IN'); expect(wrapper.text()).toContain('不提供出库单跳转'); expect(wrapper.text()).not.toContain('当前流水 DTO'); expect(wrapper.text()).not.toContain('操作人名称待补充') }) it('renders the backend owner and complete short order number in last-mile detail', async () => { routing.route.params = { orderId: '9007199254740999' }; const wrapper = mount(LastMileOrderDetailView, { global: { plugins: [createPinia(), ElementPlus] } }); await flushPromises(); expect(getLastMileOrder).toHaveBeenCalledWith('9007199254740999'); expect(wrapper.text()).toContain('华远贸易'); expect(wrapper.text()).toContain('LMO-260827-0001'); expect(wrapper.get('.order-reference-text').attributes('title')).toBe('LMO-260827-0001') }) })