257 lines
8.4 KiB
Markdown
257 lines
8.4 KiB
Markdown
|
|
# 出库单新增页面设计
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
将出入库单主表(`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<String> 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<DetailVO> 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
|
|||
|
|
<select id="selectAvailableInventory"
|
|||
|
|
resultType="cn.code.nl.module.wms.dal.dataobject.iostorinvdtl.AvailableInventoryVO">
|
|||
|
|
SELECT
|
|||
|
|
gp.vehicle_code,
|
|||
|
|
gp.pcsn,
|
|||
|
|
gp.material_code,
|
|||
|
|
gp.material_id,
|
|||
|
|
gp.qty,
|
|||
|
|
gp.frozen_qty,
|
|||
|
|
(gp.qty - gp.frozen_qty) AS available_qty,
|
|||
|
|
gp.qty_unit_id,
|
|||
|
|
gp.qty_unit_name,
|
|||
|
|
gp.ext_code,
|
|||
|
|
gp.ext_type,
|
|||
|
|
gp.ext_dtl_code,
|
|||
|
|
sa.stor_id,
|
|||
|
|
sa.stor_code,
|
|||
|
|
sa.stor_name
|
|||
|
|
FROM wms_group_plate gp
|
|||
|
|
INNER JOIN wms_structattr sa
|
|||
|
|
ON gp.vehicle_code = sa.storagevehicle_code
|
|||
|
|
WHERE sa.stor_id = #{storId}
|
|||
|
|
AND gp.status = '可用'
|
|||
|
|
AND (gp.qty - gp.frozen_qty) > 0
|
|||
|
|
<if test="materialCode != null">
|
|||
|
|
AND gp.material_code = #{materialCode}
|
|||
|
|
</if>
|
|||
|
|
<if test="pcsn != null">
|
|||
|
|
AND gp.pcsn = #{pcsn}
|
|||
|
|
</if>
|
|||
|
|
<if test="vehicleCode != null">
|
|||
|
|
AND gp.vehicle_code = #{vehicleCode}
|
|||
|
|
</if>
|
|||
|
|
</select>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
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 调用超时 | 兜底:打印日志 + 抛出业务异常 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 不考虑的内容
|
|||
|
|
|
|||
|
|
- 入库单场景(本次只做出库)
|
|||
|
|
- 编辑/修改出库单明细(本次只做新增)
|
|||
|
|
- 明细的分配/确认流程
|
|||
|
|
- 回传/上传逻辑
|