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

282 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 空木箱出入库实现计划
> **面向 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 绑定、完成和取消状态迁移,以及出库响应目的点全部在代码中形成闭环。