fix: 完善库存跨页选择稳定性

This commit is contained in:
zhouz
2026-07-22 15:23:13 +08:00
parent d4a28480f1
commit 6210286518
8 changed files with 98 additions and 25 deletions

View File

@@ -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; // 规格

View File

@@ -67,6 +67,7 @@ export namespace WmsIostorInvApi {
/** 可用库存 */
export interface AvailableInventory {
groupId: string; // 组盘记录标识
vehicleCode: string; // 箱号
pcsn?: null | string; // 批次
materialId: string; // 物料标识

View File

@@ -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(

View File

@@ -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<T extends { groupId: string }>(
current: T[],
reserved: T[],
) {
const selected = new Map<string, T>();
[...reserved, ...current].forEach((row) => selected.set(row.groupId, row));
return [...selected.values()];
}
export function buildManualDetail(
material: MaterialSelection,
planQty: number,

View File

@@ -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<InventoryGridRow>,
});
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();
},
});