Files
huachuang/docs/superpowers/plans/2026-08-05-empty-box-transport.md

282 lines
14 KiB
Markdown
Raw Permalink Normal View History

# 空木箱出入库实现计划
> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development推荐或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
**目标:** 实现基于木箱规格、仓位优先级和任务回调的空木箱入库与先进先出出库能力,并向其他模块开放创建接口。
**架构:** WMS API 模块定义跨模块契约WMS Server 的 `EmptyBoxService` 统一负责选位、条件抢锁、木箱状态和任务生命周期;`EmptyBoxTask` 只将完成、取消事件转发给 Service。候选排与候选木箱由 Mapper XML 限量筛选,优先级和并发重试由 Java 控制。
**技术栈:** Java 17、Spring Boot、OpenFeign、MyBatis/MyBatis-Plus、MySQL、现有 TransportTaskApi 与 CodeGenApi。
---
## 文件结构
- 创建 `sql/mysql/wms_empty_box.sql`:木箱规格表、木箱实例状态字段、锁字典和 `BOX_CODE` 编码规则变更。
- 创建 `nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApi.java`:跨模块创建接口。
- 创建 `nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/*.java`:入库、出库请求与响应对象。
- 创建 `nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxtype/BoxTypeDO.java`:木箱规格数据对象。
- 创建 `nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxinfo/BoxInfoDO.java`:木箱实例数据对象。
- 创建 `nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxtype/BoxTypeMapper.java` 与对应 XML按规格编码查询。
- 扩展 `nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxinfo/BoxInfoMapper.java` 与对应 XML插入、状态更新、先进先出候选查询。
- 扩展 `StrucAttrMapper.java``StrucAttrMapper.xml`:同规格候选排、空排、条件锁定、任务绑定、完成和取消。
- 创建 `nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/emptybox/EmptyBoxService.java``EmptyBoxServiceImpl.java`:领域业务。
- 创建 `nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApiImpl.java`:跨模块接口实现。
- 创建 `nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/emptybox/EmptyBoxController.java`:管理端接口。
- 创建 `nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/task/EmptyBoxTask.java`:任务事件转发。
- 修改木箱查询 VO/XML展示状态、入库时间和出库时间。
### 任务 1数据库与枚举基础
**文件:**
- 创建:`sql/mysql/wms_empty_box.sql`
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/emptybox/EmptyBoxStatusEnum.java`
- [ ] **步骤 1编写幂等数据库脚本**
脚本创建 `wms_boxtype`,向 `wmw_boxinfo` 增加 `status varchar(1)``out_time varchar(25)`,向 `wms_lock_type` 增加值 `8/9`,并幂等创建 `BOX_CODE` 编码规则。`material_code` 建唯一索引;`wmw_boxinfo` 增加 `(material_code, status, insert_time)` 普通索引以支持 FIFO。
- [ ] **步骤 2增加木箱状态枚举**
```java
public enum EmptyBoxStatusEnum {
PENDING_INBOUND("0"), IN_STOCK("1"), OUTBOUND("2");
private final String code;
}
```
- [ ] **步骤 3校验脚本和 Java 格式**
运行:`git diff --check -- sql/mysql/wms_empty_box.sql nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/emptybox/EmptyBoxStatusEnum.java`
预期:无输出,退出码 0。
### 任务 2跨模块 API 契约
**文件:**
- 创建:`nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApi.java`
- 创建:`nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxInboundCreateReq.java`
- 创建:`nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxInboundCreateResp.java`
- 创建:`nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxOutboundCreateReq.java`
- 创建:`nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxOutboundCreateResp.java`
- [ ] **步骤 1定义带注解校验的请求对象**
入库请求包含必填 `deviceCode``materialCode`;出库请求包含必填 `materialCode`
- [ ] **步骤 2定义响应对象**
入库响应包含 `taskId``boxNo``targetStructCode`;出库响应包含 `taskId``boxNo``destinationPoint`
- [ ] **步骤 3定义 Feign API**
```java
@PostMapping(PREFIX + "/createInbound")
CommonResult<EmptyBoxInboundCreateResp> createInbound(
@Valid @RequestBody EmptyBoxInboundCreateReq req);
@PostMapping(PREFIX + "/createOutbound")
CommonResult<EmptyBoxOutboundCreateResp> createOutbound(
@Valid @RequestBody EmptyBoxOutboundCreateReq req);
```
- [ ] **步骤 4编译 API 模块**
运行:`./mvnw -pl nl-module-wms/nl-module-wms-api -am -DskipTests compile`
预期:`BUILD SUCCESS`
### 任务 3规格与木箱持久化
**文件:**
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxtype/BoxTypeDO.java`
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxinfo/BoxInfoDO.java`
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxtype/BoxTypeMapper.java`
- 创建:`nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxtype/BoxTypeMapper.xml`
- 修改:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxinfo/BoxInfoMapper.java`
- 修改:`nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxinfo/BoxInfoMapper.xml`
- [ ] **步骤 1映射规格表和木箱实例表**
`BoxTypeDO` 精确对应 `wms_boxtype``BoxInfoDO` 对应 `wmw_boxinfo`,包含新增状态和出库时间。
- [ ] **步骤 2在 XML 中实现规格查询**
```sql
SELECT id, material_code, material_name, lash_num,
box_length, box_width, box_height
FROM wms_boxtype
WHERE material_code = #{materialCode}
```
- [ ] **步骤 3在 BoxInfo XML 实现实例写入和状态迁移**
增加 `insertPendingInbound``completeInbound``deletePendingInbound``completeOutbound`;完成更新均同时校验原状态,避免重复回调静默成功。
- [ ] **步骤 4实现 FIFO 候选查询**
关联 `wms_structattr.storagevehicle_code = wmw_boxinfo.box_no`,筛选在库、仓位可用且未锁定的数据,按 `insert_time ASC, box_id ASC` 排序并限制候选数量。
- [ ] **步骤 5检查 XML 可解析性**
运行:`xmllint --noout nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxtype/BoxTypeMapper.xml nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxinfo/BoxInfoMapper.xml`
预期:无输出,退出码 0。
### 任务 4仓位候选与条件锁
**文件:**
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/structAttr/EmptyBoxRowCandidate.java`
- 修改:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java`
- 修改:`nl-module-wms/nl-module-wms-server/src/main/resources/mapper/structAttr/StrucAttrMapper.xml`
- [ ] **步骤 1增加同规格候选排查询**
按仓库、库区、块、排、层分组。候选排必须至少存在一个同规格在库空木箱,不存在其他规格木箱或组盘载具,并至少存在一个未锁定空仓位;结果限制 32 排。
- [ ] **步骤 2增加整排为空候选查询**
复用现有排维度,要求整排载具为空且至少一个可用未锁定仓位;结果限制 32 排。
- [ ] **步骤 3增加入库条件锁和任务绑定**
`struct_id`、未锁定、载具为空为条件将锁类型改为 `8` 并写预占码;创建任务后仅在预占码匹配时替换为任务 ID。
- [ ] **步骤 4增加出库条件锁**
`struct_id`、未锁定、指定木箱编码仍绑定、木箱仍为在库为条件改为锁类型 `9` 并写预占码。
- [ ] **步骤 5增加完成和取消 SQL**
入库完成绑定木箱并解锁;出库完成清空载具并解锁;取消只恢复锁和任务码。所有更新按 `task_code` 与锁类型精确匹配。
- [ ] **步骤 6检查 Mapper XML**
运行:`xmllint --noout nl-module-wms/nl-module-wms-server/src/main/resources/mapper/structAttr/StrucAttrMapper.xml`
预期:无输出,退出码 0。
### 任务 5空木箱领域服务
**文件:**
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/emptybox/EmptyBoxService.java`
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/emptybox/EmptyBoxServiceImpl.java`
- [ ] **步骤 1实现入库选位和预占**
先遍历同规格候选排,再遍历空排;每排查询一个空位并条件锁定。全部失败时抛出“未找到符合条件的空木箱入库仓位”。
- [ ] **步骤 2生成木箱编码并创建待入库记录**
调用 `CodeGenApi.generate(new CodeGenerateReqDTO().setRuleCode("BOX_CODE"))`,检查远程调用结果;复制 `wms_boxtype` 规格属性到 `wmw_boxinfo`,状态设为待入库。
- [ ] **步骤 3创建并下发入库任务**
任务起点为设备号,终点为仓位,`handleCode``EMPTYBOXTASK``bizType``EMPTY_BOX_IN`。绑定任务 ID 后调用任务下发回滚接口。
- [ ] **步骤 4实现 FIFO 出库预占**
遍历 FIFO 候选并条件抢锁;全部失败时抛出“没有可出库的该规格空木箱”。
- [ ] **步骤 5实现临时出库点负载选择**
定义 `ZXQ_01``ZXQ_02` 常量,分别计算未完成任务数并加点位库存数。增加中文扩展注释,说明后续由 LMS 一次性返回出库点及占用库存;当前库存数查询方法返回 WMS 可获取的点位占用数量。相同负载固定选择 `ZXQ_01`
- [ ] **步骤 6创建并下发出库任务**
任务起点为仓位,终点为选定出库点,`bizType``EMPTY_BOX_OUT`;响应返回任务 ID、木箱号和目的点。
- [ ] **步骤 7实现完成与取消事务**
完成时先根据任务锁确定方向:入库绑定仓位并把木箱改为在库,出库清空仓位并把木箱改为已出库;取消入库时删除待入库记录,取消出库时保留在库记录。每项更新必须恰好影响一行。
### 任务 6接口与任务回调接入
**文件:**
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApiImpl.java`
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/emptybox/EmptyBoxController.java`
- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/task/EmptyBoxTask.java`
- [ ] **步骤 1实现内部 API**
使用 `@Resource` 注入 `EmptyBoxService`,将创建结果包装为 `CommonResult.success`
- [ ] **步骤 2实现管理端接口**
提供 `/wms/empty-box/createInbound``/wms/empty-box/createOutbound`,返回 `CommonResult`,请求使用 `@Valid`
- [ ] **步骤 3实现任务处理器**
```java
@Component("EMPTYBOXTASK")
public class EmptyBoxTask extends AbstractTask {
@Resource
private EmptyBoxService emptyBoxService;
@Override
public void doHandleFinish(TaskExecuteDTO dto) {
emptyBoxService.completeTask(dto.getTaskId());
}
@Override
public void doHandleCancel(TaskExecuteDTO dto) {
emptyBoxService.cancelTask(dto.getTaskId());
}
}
```
- [ ] **步骤 4确认任务工厂自动发现处理器**
检查 `WmsTaskStatusChangeConsumer` 仍通过现有 `TaskFactory``handleCode` 分发,无需加入业务分支。
### 任务 7木箱查询展示状态
**文件:**
- 修改:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/vo/BoxInfoRespVO.java`
- 修改:`nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxinfo/BoxInfoMapper.xml`
- 修改:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/boxinfo/model.ts`
- 修改:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/boxinfo/data.ts`
- [ ] **步骤 1后端返回状态和出库时间**
分页 SQL 增加 `status``out_time`,响应对象增加对应字段。
- [ ] **步骤 2前端增加只读列**
列表展示状态、入库时间、出库时间;状态使用明确文本“待入库/在库/已出库”。
- [ ] **步骤 3运行前端静态检查**
运行项目现有 ESLint 命令,仅检查本次修改的木箱信息文件。
预期:退出码 0。
### 任务 8整体验证
**文件:**
- 检查本计划涉及的全部文件。
- [ ] **步骤 1检查注解 SQL 和格式问题**
运行:`rg -n "@(Select|Update|Insert|Delete)" nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/{service/emptybox,dal/mysql/boxtype,dal/mysql/boxinfo,dal/mysql/structAttr}`
预期:没有本功能新增的注解 SQL。
- [ ] **步骤 2编译 WMS 模块**
运行:`./mvnw -pl nl-module-wms/nl-module-wms-server -am -DskipTests clean compile`
预期:`BUILD SUCCESS`
- [ ] **步骤 3检查 XML 与差异**
运行:`xmllint --noout nl-module-wms/nl-module-wms-server/src/main/resources/mapper/{boxtype/BoxTypeMapper.xml,boxinfo/BoxInfoMapper.xml,structAttr/StrucAttrMapper.xml}`
运行:`git diff --check`
预期:两条命令均退出码 0。
- [ ] **步骤 4人工核对关键路径**
确认同规格同排优先、空排回退、FIFO、条件抢锁、任务 ID 绑定、完成和取消状态迁移,以及出库响应目的点全部在代码中形成闭环。