From 6210286518d1f6f8a3bb3e22c010ba0b17f6783c Mon Sep 17 00:00:00 2001 From: zhouz <> Date: Wed, 22 Jul 2026 15:23:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E5=96=84=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E8=B7=A8=E9=A1=B5=E9=80=89=E6=8B=A9=E7=A8=B3=E5=AE=9A=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/AvailableInventoryRespVO.java | 2 + .../iostorinvdtl/IostorinvDtlMapper.xml | 1 + .../iostorinvdtl/IostorinvDtlMapperTest.java | 2 + .../src/api/base/materialbase/index.ts | 2 +- .../src/api/wms/iostorinv/index.ts | 1 + .../iostorinv/modules/detail-builders.test.ts | 18 +++++ .../wms/iostorinv/modules/detail-builders.ts | 21 +++-- .../iostorinv/modules/inventory-select.vue | 76 ++++++++++++++----- 8 files changed, 98 insertions(+), 25 deletions(-) diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/AvailableInventoryRespVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/AvailableInventoryRespVO.java index b6ba82e4..9046b1cb 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/AvailableInventoryRespVO.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/iostorinv/vo/AvailableInventoryRespVO.java @@ -9,6 +9,8 @@ import java.math.BigDecimal; @Data public class AvailableInventoryRespVO { + @Schema(description = "组盘记录标识") + private Long groupId; @Schema(description = "箱号") private String vehicleCode; @Schema(description = "批次") diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml index 6b2947f3..1612604d 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/iostorinvdtl/IostorinvDtlMapper.xml @@ -3,6 +3,7 @@ + gp.group_id AS group_id, gp.vehicle_code AS vehicle_code, gp.pcsn AS pcsn, gp.material_id AS material_id, diff --git a/nl-module-wms/nl-module-wms-server/src/test/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapperTest.java b/nl-module-wms/nl-module-wms-server/src/test/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapperTest.java index 8b673002..fc783563 100644 --- a/nl-module-wms/nl-module-wms-server/src/test/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapperTest.java +++ b/nl-module-wms/nl-module-wms-server/src/test/java/cn/code/nl/module/wms/dal/mysql/iostorinvdtl/IostorinvDtlMapperTest.java @@ -52,6 +52,7 @@ class IostorinvDtlMapperTest { List expanded = service.expandAvailableInventory(expandReqVO); assertEquals(2, expanded.size()); + assertEquals(List.of(1L, 2L), expanded.stream().map(AvailableInventoryRespVO::getGroupId).toList()); assertTrue(expanded.stream().allMatch(item -> "BOX-001".equals(item.getVehicleCode()))); assertTrue(expanded.stream().allMatch(item -> "STOR-001".equals(item.getStorId()))); assertEquals(2, expanded.stream().map(AvailableInventoryRespVO::getPcsn).distinct().count()); @@ -75,6 +76,7 @@ class IostorinvDtlMapperTest { assertEquals(1L, page.getTotal()); assertEquals("PCSN-002", page.getList().get(0).getPcsn()); + assertEquals(2L, page.getList().get(0).getGroupId()); assertEquals("材料一", page.getList().get(0).getMaterialName()); } diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/base/materialbase/index.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/base/materialbase/index.ts index 4c6091e1..80a2090a 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/base/materialbase/index.ts +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/base/materialbase/index.ts @@ -1,11 +1,11 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { Dayjs } from 'dayjs'; import { requestClient } from '#/api/request'; export namespace BaseMaterialBaseApi { /** 物料基本信息信息 */ export interface MaterialBase { + materialId: number; // 物料标识 materialCode?: string; // 物料编码 materialName?: string; // 物料名称 materialSpec: string; // 规格 diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts index d29c44f7..dd930de2 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/iostorinv/index.ts @@ -67,6 +67,7 @@ export namespace WmsIostorInvApi { /** 可用库存 */ export interface AvailableInventory { + groupId: string; // 组盘记录标识 vehicleCode: string; // 箱号 pcsn?: null | string; // 批次 materialId: string; // 物料标识 diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.test.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.test.ts index 2c8ac410..6c52b5da 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.test.ts +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.test.ts @@ -4,6 +4,7 @@ import { buildManualDetail, collectVehicleCodes, mapExpandedInventory, + mergeInventorySelections, } from './detail-builders'; describe('mapExpandedInventory', () => { @@ -11,6 +12,7 @@ describe('mapExpandedInventory', () => { const rows = mapExpandedInventory([ { availableQty: 12.5, + groupId: '101', extCode: 'OUT-1', extDtlCode: 'DTL-1', extType: 'SALE', @@ -25,6 +27,7 @@ describe('mapExpandedInventory', () => { }, { availableQty: 8, + groupId: '102', materialCode: 'M-1', materialId: '1', pcsn: 'P-2', @@ -46,6 +49,21 @@ describe('mapExpandedInventory', () => { }); }); +describe('mergeInventorySelections', () => { + it('合并当前页和跨页保留记录,并按 groupId 去重', () => { + const reserved = [{ groupId: '1', vehicleCode: 'BOX-1' }]; + const current = [ + { groupId: '1', vehicleCode: 'BOX-1' }, + { groupId: '2', vehicleCode: 'BOX-2' }, + ]; + + expect(mergeInventorySelections(current, reserved)).toEqual([ + reserved[0], + current[1], + ]); + }); +}); + describe('buildManualDetail', () => { it('构造手工汇总行并显式清空箱号、子卷号和 SAP 批次', () => { expect( diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.ts index 1d8b2450..b35b5cd4 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.ts +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.ts @@ -1,12 +1,11 @@ +import type { BaseMaterialBaseApi } from '#/api/base/materialbase'; import type { WmsIostorInvApi } from '#/api/wms/iostorinv'; /** 物料选择弹窗返回行中,手工明细所需的真实字段 */ -export interface MaterialSelection { - baseUnitId?: number; - materialCode?: string; - materialId: number | string; - materialName?: string; -} +export type MaterialSelection = Pick< + BaseMaterialBaseApi.MaterialBase, + 'baseUnitId' | 'materialCode' | 'materialId' | 'materialName' +>; export function mapExpandedInventory( inventory: WmsIostorInvApi.AvailableInventory[], @@ -40,6 +39,16 @@ export function collectVehicleCodes(rows: Array<{ vehicleCode?: string }>) { return vehicleCodes; } +/** 合并当前页与 VXE 跨页保留记录,以组盘记录标识去重 */ +export function mergeInventorySelections( + current: T[], + reserved: T[], +) { + const selected = new Map(); + [...reserved, ...current].forEach((row) => selected.set(row.groupId, row)); + return [...selected.values()]; +} + export function buildManualDetail( material: MaterialSelection, planQty: number, diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/inventory-select.vue b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/inventory-select.vue index 207815ed..c402c769 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/inventory-select.vue +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/inventory-select.vue @@ -14,16 +14,26 @@ import { getAvailableInventoryPage, } from '#/api/wms/iostorinv'; -import { collectVehicleCodes, mapExpandedInventory } from './detail-builders'; +import { + collectVehicleCodes, + mapExpandedInventory, + mergeInventorySelections, +} from './detail-builders'; -interface InventoryGridRow extends WmsIostorInvApi.AvailableInventory { - selectionKey: string; -} +type InventoryGridRow = WmsIostorInvApi.AvailableInventory; const emit = defineEmits<{ select: [rows: WmsIostorInvApi.OutboundDisplayDetail[]]; }>(); const storId = ref(''); +const modalOpen = ref(false); +let openToken = 0; + +async function clearGridState() { + await gridApi.grid.clearCheckboxRow(); + await gridApi.grid.clearCheckboxReserve(); + await gridApi.grid.loadData([]); +} const [Grid, gridApi] = useVbenVxeGrid({ formOptions: { @@ -60,33 +70,44 @@ const [Grid, gridApi] = useVbenVxeGrid({ ], height: 'auto', keepSource: true, + checkboxConfig: { highlight: true, reserve: true }, proxyConfig: { ajax: { query: async ({ page }, formValues) => { + const requestToken = openToken; + const requestStorId = storId.value; + if (!modalOpen.value || !requestStorId) return { list: [], total: 0 }; const result = await getAvailableInventoryPage({ ...formValues, pageNo: page.currentPage, pageSize: page.pageSize, - storId: storId.value, + storId: requestStorId, }); - return { - ...result, - list: result.list.map((row) => ({ - ...row, - selectionKey: `${row.vehicleCode}\u0000${row.pcsn ?? ''}\u0000${row.materialId}`, - })), - }; + if ( + requestToken !== openToken || + !modalOpen.value || + requestStorId !== storId.value + ) { + return { list: [], total: 0 }; + } + return result; }, }, }, - rowConfig: { isHover: true, keyField: 'selectionKey' }, + rowConfig: { isHover: true, keyField: 'groupId' }, toolbarConfig: { refresh: true, search: true }, } as VxeTableGridOptions, }); const [Modal, modalApi] = useVbenModal({ async onConfirm() { - const selected = gridApi.grid.getCheckboxRecords() as InventoryGridRow[]; + const confirmToken = openToken; + const confirmStorId = storId.value; + if (!modalOpen.value || !confirmStorId) return; + const selected = mergeInventorySelections( + gridApi.grid.getCheckboxRecords() as InventoryGridRow[], + gridApi.grid.getCheckboxReserveRecords() as InventoryGridRow[], + ); if (selected.length === 0) { message.warning('请至少选择一条库存信息'); return; @@ -101,22 +122,41 @@ const [Modal, modalApi] = useVbenModal({ modalApi.lock(); try { const expanded = await expandAvailableInventory({ - storId: storId.value, + storId: confirmStorId, vehicleCodes, }); + if ( + confirmToken !== openToken || + !modalOpen.value || + confirmStorId !== storId.value + ) { + return; + } emit('select', mapExpandedInventory(expanded)); await modalApi.close(); } finally { modalApi.unlock(); } }, - async onOpenChange(isOpen) { - if (!isOpen) { + async onOpenChange(open) { + const token = ++openToken; + if (!open) { + modalOpen.value = false; storId.value = ''; + await clearGridState(); return; } + modalOpen.value = true; + storId.value = ''; + await clearGridState(); + if (token !== openToken || !modalOpen.value) return; const data = modalApi.getData<{ storId: string }>(); - storId.value = data?.storId ?? ''; + if (!data?.storId) { + message.warning('请先选择仓库'); + await modalApi.close(); + return; + } + storId.value = data.storId; await gridApi.query(); }, });