From a4a0b41ac75be2fa7da3c12e43cd5f6233c6bbb4 Mon Sep 17 00:00:00 2001 From: zhouz <> Date: Wed, 22 Jul 2026 09:19:46 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=87=BA=E5=BA=93=E5=8D=95=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E9=A1=B5=E9=9D=A2=E8=AE=BE=E8=AE=A1=E8=A7=84=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-07-22-outbound-create-design.md | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-22-outbound-create-design.md diff --git a/docs/superpowers/specs/2026-07-22-outbound-create-design.md b/docs/superpowers/specs/2026-07-22-outbound-create-design.md new file mode 100644 index 00000000..b41f0264 --- /dev/null +++ b/docs/superpowers/specs/2026-07-22-outbound-create-design.md @@ -0,0 +1,256 @@ +# 出库单新增页面设计 + +## 概述 + +将出入库单主表(`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 调用超时 | 兜底:打印日志 + 抛出业务异常 | + +--- + +## 不考虑的内容 + +- 入库单场景(本次只做出库) +- 编辑/修改出库单明细(本次只做新增) +- 明细的分配/确认流程 +- 回传/上传逻辑