diff --git a/docs/superpowers/specs/2026-08-05-empty-box-transport-design.md b/docs/superpowers/specs/2026-08-05-empty-box-transport-design.md new file mode 100644 index 00000000..5b5f2f7a --- /dev/null +++ b/docs/superpowers/specs/2026-08-05-empty-box-transport-design.md @@ -0,0 +1,101 @@ +# 空木箱出入库设计规格 + +## 目标 + +在 WMS 中增加空木箱入库、出库及任务回调能力。木箱规格统一读取 `wms_boxtype`,实际木箱保存在 `wmw_boxinfo`,仓位通过锁类型避免并发重复分配。 + +## 方案比较 + +1. **SQL 完成全部选位与锁定**:数据库往返少,但同规格同排、新排回退和并发重试会形成复杂 SQL,后续调整策略困难。 +2. **加载全部仓位后由 Java 选位**:规则直观,但仓位量增长后内存和数据库传输成本较高。 +3. **SQL 筛选有限候选排,Java 执行优先级,条件更新抢锁**:兼顾性能、并发安全和规则可维护性。 + +采用方案 3。Mapper XML 只返回少量候选排或候选木箱,Service 按优先级逐个尝试条件更新;更新行数为 0 时继续下一个候选。 + +## 数据模型 + +### 木箱规格表 `wms_boxtype` + +字段为: + +- `id`:主键。 +- `material_code`:木箱规格编码,业务唯一。 +- `material_name`:规格名称。 +- `lash_num`:捆扎次数。 +- `box_length`、`box_width`、`box_height`:长、宽、高。 + +空木箱业务只通过 `material_code` 接收规格,名称、尺寸和捆扎次数均从该表读取,不信任调用方传入的规格属性。 + +### 木箱实例表 `wmw_boxinfo` + +保留现有字段,并增加: + +- `status`:`0` 待入库、`1` 在库、`2` 已出库。 +- `out_time`:实际出库完成时间,未出库时为空。 + +`insert_time` 表示实际入库完成时间。创建入库任务时先生成待入库记录;任务完成后写入实际入库时间并改为在库。出库完成后保留记录,更新状态和出库时间。 + +### 仓位锁 + +在字典 `wms_lock_type` 增加: + +- `8`:木箱入库锁。 +- `9`:木箱出库锁。 + +锁定时将任务预占码写入 `task_code`,任务创建成功后替换为任务 ID。完成或取消时必须同时清空 `task_code` 并恢复未锁定状态。 + +## 模块边界 + +- `nl-module-wms-api`:提供其他模块可调用的 `EmptyBoxApi`、请求对象和响应对象。 +- `nl-module-wms-server`:提供控制器、规格和木箱 Mapper、仓位候选 SQL、空木箱领域服务及 `EmptyBoxTask`。 +- `nl-module-task-api`:复用现有任务创建、下发和目的点未完成任务数接口,不修改任务模块内部业务。 +- `nl-module-base-api`:复用编码生成接口,通过规则编码 `BOX_CODE` 生成木箱编码。 + +任务回调类只转发 `taskId`,完成与取消的事务逻辑封装在空木箱 Service 中。 + +## 入库流程 + +1. 接口接收 `deviceCode` 和 `materialCode`,校验木箱规格存在。 +2. 查询候选排:第一优先级为已经存放同规格空木箱、没有其他规格木箱和实载载具且仍有空仓位的排;第二优先级为整排没有载具的排。 +3. 每个候选排只查询一个可用仓位,使用 `lock_type = 1`、载具为空作为条件更新为木箱入库锁。并发抢锁失败时尝试下一候选。 +4. 调用 `BOX_CODE` 生成木箱编码,根据规格表属性插入状态为待入库的 `wmw_boxinfo`。 +5. 创建并下发任务:起点为 `deviceCode`,终点为目标仓位编码,载具编码为新木箱编码,业务处理器为 `EMPTYBOXTASK`。 +6. 任务完成后,将木箱编码绑定至仓位,数量置为 1,仓位解锁;木箱状态改为在库并写入实际入库时间。 +7. 任务取消后解锁仓位,并物理删除对应的待入库木箱记录。 + +任一步骤异常都回滚本地事务;远程任务创建或下发失败时抛出业务异常,依赖现有任务回滚接口处理已创建任务。 + +## 出库流程 + +1. 接口接收 `materialCode`,只查询状态为在库、已绑定可用仓位且仓位未锁定的同规格木箱。 +2. 按 `insert_time` 升序、`box_id` 升序取得有限候选,逐条通过条件更新增加木箱出库锁;并发抢锁失败时继续下一条。 +3. 当前临时出库点为 `ZXQ_01` 和 `ZXQ_02`。负载计算为“对应点位未完成任务数 + 点位当前占用库存数”,选择负载较小的点位,相同则选择 `ZXQ_01`。 +4. 点位占用库存数暂由 WMS 内部扩展方法获取。代码保留清晰扩展标记,后续替换为 LMS 接口同时返回候选出库点及其占用情况,不改变空木箱业务主流程。 +5. 创建并下发任务:起点为木箱所在仓位,终点为选定出库点,载具编码为木箱编码。 +6. 任务完成后解锁仓位并清空载具编码和数量;木箱状态改为已出库并写入 `out_time`,历史记录保留。 +7. 任务取消后只解除仓位锁,木箱记录仍为在库且继续绑定原仓位。 + +## 对外接口 + +- 入库创建:请求包含 `deviceCode`、`materialCode`;响应包含 `taskId`、`boxNo`、`targetStructCode`。 +- 出库创建:请求包含 `materialCode`;响应包含 `taskId`、`boxNo`、`destinationPoint`。 + +服务端管理接口复用相同请求和响应语义。所有参数使用注解校验,对外 API 类型放在 WMS API 模块。 + +## 一致性与异常 + +- 未找到规格、候选排、空仓位或可出库木箱时抛出明确的 `ServiceException`。 +- 仓位锁使用带原状态条件的更新保证并发安全,不使用“先查后无条件更新”。 +- 木箱完成或取消回调必须按任务 ID 精确匹配一个锁定仓位;匹配数量异常时拒绝静默成功。 +- 重复完成或重复取消属于幂等冲突,抛出业务异常以暴露任务状态不一致。 +- 所有查询和更新 SQL 写入 Mapper XML,不使用 `@Select`、`@Update`。 + +## 验收范围 + +- 同规格排存在空位时优先入同排;不存在时选择整排为空的排。 +- 不同规格空木箱和带组盘记录的载具不会被安排在同一排。 +- 入库创建、完成、取消均正确维护木箱状态和仓位锁。 +- 出库严格按实际入库时间先进先出,并在并发条件下不会重复选择木箱。 +- 出库点按任务数和占用库存数综合负载选择,并在响应中返回目的点。 +- 出库完成后木箱历史记录仍可查询,仓位已清空。 +- WMS 模块编译通过,Mapper XML 可正常加载。