# 出库单新增页面设计 ## 概述 将出入库单主表(`iostorinv`)的新增弹窗从简单表单重构为出库单新增页面,包含主表表单 + 出库明细 table。参考原型图 `.image/出库新增.png`。 ### 范围 - 仅涉及**出库**场景(`ioType = "OUT"`) - 后端:新增 `createWithDetails` 接口,新增库存查询 Mapper SQL - 前端:重构 `form.vue`,新增库存选择弹窗和手动新增汇总弹窗 - **不新增数据库字段** --- ## 主表字段映射 | 页面字段 | 数据来源/逻辑 | 对应 DO 字段 | 必填 | 可编辑 | |---------|-------------|-------------|------|-------| | 仓库 | `wms_bsrealstorattr` 启用列表 | `storId / storCode / storName` | 是 | 是 | | 业务类型 | 字典 `out_bill_type` | `billType` | 是 | 是 | | 单据号 | `CodeGenApi.generate("IO_CODE")` | `billCode` | 是 | 否(自动生成) | | 业务日期 | 默认当天 | `bizDate` | 是 | 是 | | 单据状态 | 固定"生成" | `billStatus` | 是 | 否 | | 明细数 | `detailTable.length` | `detailCount` | 是 | 否(自动联动) | | 总重量 | `sum(detail.planQty)` | `totalWeight` | 是 | 否(自动联动) | | 备注 | 手动输入 | `remark` | 否 | 是 | | 出入类型 | 固定 "OUT" | `ioType` | 是 | 否 | --- ## 出库明细字段映射(仅使用现有 DO 字段) | 列 | 对应 DO 字段 | 数据来源 | 说明 | |----|------------|---------|------| | 序号 | `seqNo` | 自动编号 1,2,3... | | | 箱号 | *(不存)* | `wms_group_plate.vehicle_code` | 仅页面展示,不入库 | | 物料编码 | `materialCode` | 库存带入 / 手动选择 | | | 物料名称 | *(不存)* | `MaterialBaseApi` 实时反查 | 仅页面展示 | | 子卷号 | `pcsn` | `wms_group_plate.pcsn` 或手动输入 | 方式 B 时为空 | | 出库重量 | `planQty` | 库存 `qty - frozenQty`(可编辑)或手动输入 | | | 未分配数量 | `unassignQty` | 初始 = `planQty` | | | 已分配数量 | `assignQty` | 初始 = 0 | | | 单位 | `qtyUnitName` | 库存带入 / 物料默认单位 | | | 批次 | `pcsn` | 同子卷号 | | | 备注 | `remark` | 可选输入 | | | 来源单据号 | `sourceBillCode` | `group_plate.extCode` | 库存带入时有值 | | 来源单据类型 | `sourceBillType` | `group_plate.extType` | 库存带入时有值 | | 来源单据明细 | `sourceBilldtlId` | `group_plate.extDtlCode` | 库存带入时有值 | --- ## 明细新增的两种方式 ### 方式 A:从库存选择带入 1. 查询逻辑: - 通过 `wms_structattr.storagevehicle_code = wms_group_plate.vehicle_code` 关联仓库 - 过滤条件:`storId` = 主表所选仓库、`status` = 可用、`qty - frozenQty > 0` - 支持物料编码、批次、载具编码等可选筛选条件 2. **一对多联动**:一个 `vehicle_code` 对应多个 `pcsn`,勾选其中一条时,同一 `vehicle_code` 的所有记录自动勾选并带入明细 3. 出库重量默认带入可用数量(`qty - frozenQty`),用户可编辑 ### 方式 B:手动新增汇总 1. 弹出简单表单:选择物料、输入出库重量、批次(可选)、备注(可选) 2. 单位自动带出物料的基本计量单位 3. 箱号、子卷号为空(仅页面不展示,不存 `pcsn`) --- ## 后端设计 ### 新增接口 **`POST /wms/iostor-inv/create-with-details`** ```java @PostMapping("/create-with-details") @Operation(summary = "创建出库单(含明细)") @PreAuthorize("@ss.hasPermission('wms:iostor-inv:create')") public CommonResult createIostorInvWithDetails( @Valid @RequestBody IostorInvWithDetailsSaveReqVO reqVO) ``` ### 请求 VO ```java public class IostorInvWithDetailsSaveReqVO { // 主表字段 private String billType; // 业务类型 private String storId; // 仓库标识 private String storCode; // 仓库编码 private String storName; // 仓库名称 private LocalDateTime bizDate; // 业务日期 private String remark; // 备注 // 明细列表 private List details; } public class DetailVO { private String materialCode; private String materialId; private String pcsn; // 子卷号/批次 private BigDecimal planQty; // 出库重量 private String qtyUnitId; private String qtyUnitName; private String remark; private String sourceBillCode; // 来源单据号 private String sourceBillType; // 来源单据类型 private String sourceBilldtlId; // 来源单据明细标识 } ``` ### Service 层逻辑 1. 调用 `CodeGenApi.generate("IO_CODE")` 生成单据号 2. 设置 `ioType = "OUT"`、`billStatus = "生成"` 3. 计算 `detailCount = details.size()`、`totalWeight = sum(details.planQty)` 4. 插入主表 `wms_iostorinv` 5. 批量插入明细 `wms_iostorinvdtl`: - `planQty` = 出库重量 - `unassignQty` = `planQty`(初始等于计划数量) - `assignQty` = 0 - `seqNo` 自动编号 6. `@Transactional` 保证原子性 ### 库存查询(Mapper XML) 在 `IostorinvDtlMapper.xml` 中新增通用查询 SQL,方便后续扩展: ```xml ``` Controller 通过 Service → Mapper 调用,不新增独立接口。 ### 数据关联图 ``` wms_bsrealstorattr (仓库) │ │ storId ▼ wms_structattr (仓位) │ storagevehicle_code │ ▼ wms_group_plate (库存/组盘) │ │ 选择带入 ▼ wms_iostorinvdtl (出库明细) │ │ iostorinvId ▼ wms_iostorinv (出库单主表) ``` --- ## 前端设计 ### 文件结构 ``` nl-ui/.../views/wms/iostorinv/ ├── index.vue # 列表页 (不改) ├── data.ts # 列表 schema (不改) ├── modules/ │ ├── form.vue # [重构] 大弹窗:主表表单 + 明细 table │ ├── inventory-select.vue # [新增] 库存选择弹窗 │ └── manual-detail.vue # [新增] 手动新增汇总弹窗 nl-ui/.../api/wms/iostorinv/ └── index.ts # [扩展] 新增 createIostorInvWithDetails ``` ### form.vue 重构 - 弹窗宽度 1200px 或全屏 - 主表区域 `useVbenForm`,水平布局,字段排列参照原型图 - 明细 table 内嵌在表单下方,使用 vxe-table - 联动: - 仓库切换 → 清空明细 - 明细增删 → 自动更新明细数、总重量 - 单据号在弹窗打开时通过预览接口展示,提交时由后端生成 ### inventory-select.vue(库存选择弹窗) - 搜索区:物料编码、批次、载具编码 - 表格多选 + 同 vehicle_code 联动勾选 - 可编辑出库重量列(默认可用数量) - 确认后返回选中数据 ### manual-detail.vue(手动新增弹窗) - 物料选择器(可复用 MaterialSelectModal) - 出库重量输入 - 批次输入(可选) - 备注输入(可选) --- ## 错误处理 | 场景 | 处理方式 | |------|---------| | 单据号生成失败(CodeGenApi 不可用) | 抛出异常,事务回滚,前端提示"单据号生成失败" | | 未选择仓库时点"新增明细(方式A)" | 前端拦截,提示"请先选择仓库" | | 提交时明细为空 | 后端校验,返回"请至少添加一条明细" | | 库存已被其他单据消耗(并发) | 提交时校验可用数量,不足则回滚提示 | | Feign 调用超时 | 兜底:打印日志 + 抛出业务异常 | --- ## 不考虑的内容 - 入库单场景(本次只做出库) - 编辑/修改出库单明细(本次只做新增) - 明细的分配/确认流程 - 回传/上传逻辑