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 fd888ccf..d29c44f7 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 @@ -49,6 +49,13 @@ export namespace WmsIostorInvApi { remark?: string; // 备注 } + /** 出库明细页面展示模型(提交时应转换为 OutboundDetail) */ + export interface OutboundDisplayDetail extends OutboundDetail { + materialName?: string; + sapBatchNo?: string; + vehicleCode?: string; + } + /** 出库单新增请求 */ export interface OutboundCreateReq { billType: 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 new file mode 100644 index 00000000..2c8ac410 --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, it } from 'vitest'; + +import { + buildManualDetail, + collectVehicleCodes, + mapExpandedInventory, +} from './detail-builders'; + +describe('mapExpandedInventory', () => { + it('逐条映射展开后的全部子卷,且不把 pcsn 当作 SAP 批次', () => { + const rows = mapExpandedInventory([ + { + availableQty: 12.5, + extCode: 'OUT-1', + extDtlCode: 'DTL-1', + extType: 'SALE', + materialCode: 'M-1', + materialId: '1', + materialName: '物料一', + pcsn: 'P-1', + qtyUnitId: 'kg', + qtyUnitName: '千克', + storId: 'S-1', + vehicleCode: 'BOX-1', + }, + { + availableQty: 8, + materialCode: 'M-1', + materialId: '1', + pcsn: 'P-2', + storId: 'S-1', + vehicleCode: 'BOX-1', + }, + ]); + + expect(rows).toHaveLength(2); + expect(rows[0]).toMatchObject({ + pcsn: 'P-1', + planQty: 12.5, + sapBatchNo: undefined, + sourceBillCode: 'OUT-1', + sourceBillType: 'SALE', + sourceBilldtlId: 'DTL-1', + }); + expect(rows[1]?.pcsn).toBe('P-2'); + }); +}); + +describe('buildManualDetail', () => { + it('构造手工汇总行并显式清空箱号、子卷号和 SAP 批次', () => { + expect( + buildManualDetail( + { + baseUnitId: 7, + materialCode: 'M-1', + materialId: 1, + materialName: '物料一', + }, + 20, + '备注', + ), + ).toMatchObject({ + materialCode: 'M-1', + materialId: '1', + materialName: '物料一', + pcsn: undefined, + planQty: 20, + qtyUnitId: '7', + qtyUnitName: undefined, + remark: '备注', + sapBatchNo: undefined, + vehicleCode: undefined, + }); + }); +}); + +describe('collectVehicleCodes', () => { + it('去重并过滤空箱号', () => { + expect( + collectVehicleCodes([ + { vehicleCode: ' BOX-1 ' }, + { vehicleCode: '' }, + { vehicleCode: 'BOX-1' }, + { vehicleCode: 'BOX-2' }, + ]), + ).toEqual(['BOX-1', 'BOX-2']); + }); + + it('允许 500 个箱号并拒绝 501 个', () => { + expect( + collectVehicleCodes( + Array.from({ length: 500 }, (_, index) => ({ + vehicleCode: `BOX-${index}`, + })), + ), + ).toHaveLength(500); + expect(() => + collectVehicleCodes( + Array.from({ length: 501 }, (_, index) => ({ + vehicleCode: `BOX-${index}`, + })), + ), + ).toThrow('箱号数量不能超过 500 个'); + }); + + it('拒绝没有有效箱号的选择', () => { + expect(() => collectVehicleCodes([{ vehicleCode: ' ' }])).toThrow( + '请选择包含有效箱号的库存', + ); + }); +}); 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 new file mode 100644 index 00000000..1d8b2450 --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/detail-builders.ts @@ -0,0 +1,61 @@ +import type { WmsIostorInvApi } from '#/api/wms/iostorinv'; + +/** 物料选择弹窗返回行中,手工明细所需的真实字段 */ +export interface MaterialSelection { + baseUnitId?: number; + materialCode?: string; + materialId: number | string; + materialName?: string; +} + +export function mapExpandedInventory( + inventory: WmsIostorInvApi.AvailableInventory[], +): WmsIostorInvApi.OutboundDisplayDetail[] { + return inventory.map((row) => ({ + materialCode: row.materialCode, + materialId: row.materialId, + materialName: row.materialName ?? undefined, + pcsn: row.pcsn ?? undefined, + planQty: row.availableQty, + qtyUnitId: row.qtyUnitId ?? undefined, + qtyUnitName: row.qtyUnitName ?? undefined, + sapBatchNo: undefined, + sourceBillCode: row.extCode ?? undefined, + sourceBillType: row.extType ?? undefined, + sourceBilldtlId: row.extDtlCode ?? undefined, + vehicleCode: row.vehicleCode, + })); +} + +export function collectVehicleCodes(rows: Array<{ vehicleCode?: string }>) { + const vehicleCodes = [ + ...new Set(rows.map(({ vehicleCode }) => vehicleCode?.trim()).filter(Boolean)), + ] as string[]; + if (vehicleCodes.length === 0) { + throw new Error('请选择包含有效箱号的库存'); + } + if (vehicleCodes.length > 500) { + throw new Error('箱号数量不能超过 500 个'); + } + return vehicleCodes; +} + +export function buildManualDetail( + material: MaterialSelection, + planQty: number, + remark?: string, +): WmsIostorInvApi.OutboundDisplayDetail { + return { + materialCode: material.materialCode ?? '', + materialId: String(material.materialId), + materialName: material.materialName, + pcsn: undefined, + planQty, + qtyUnitId: + material.baseUnitId === undefined ? undefined : String(material.baseUnitId), + qtyUnitName: undefined, + remark: remark || undefined, + sapBatchNo: undefined, + vehicleCode: undefined, + }; +} 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 new file mode 100644 index 00000000..207815ed --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/inventory-select.vue @@ -0,0 +1,129 @@ + + + diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/manual-detail.vue b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/manual-detail.vue new file mode 100644 index 00000000..b9da5bd1 --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/iostorinv/modules/manual-detail.vue @@ -0,0 +1,110 @@ + + +