diff --git a/.image/立库仓位分块图.png b/.image/立库仓位分块图.png new file mode 100644 index 00000000..a9189fce Binary files /dev/null and b/.image/立库仓位分块图.png differ diff --git a/docs/superpowers/plans/2026-08-05-box-info-query.md b/docs/superpowers/plans/2026-08-05-box-info-query.md new file mode 100644 index 00000000..8f90532a --- /dev/null +++ b/docs/superpowers/plans/2026-08-05-box-info-query.md @@ -0,0 +1,57 @@ +# 木箱信息只读查询实现计划 + +> **面向 AI 代理的工作者:** 在当前会话内按步骤实现并逐项验证。 + +**目标:** 在WMS模块的“基础模块”下提供 `wmw_boxinfo` 的只读分页菜单和查询页面。 + +**架构:** 后端使用 Controller → Service → MyBatis XML Mapper 的只读链路;前端使用独立API和VXE分页表格;菜单由幂等SQL配置。所有层均不暴露写能力。 + +**技术栈:** Java 17、Spring Boot、MyBatis-Plus分页、MyBatis XML、Vue 3、TypeScript、Vben Admin、VXE Table、MySQL。 + +--- + +### 任务1:后端只读分页 + +**文件:** +- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/vo/BoxInfoPageReqVO.java` +- 创建:`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/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` +- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/boxinfo/BoxInfoService.java` +- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/boxinfo/BoxInfoServiceImpl.java` +- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/BoxInfoController.java` + +- [ ] 定义与 `wmw_boxinfo` 字段一一对应的只读响应对象。 +- [ ] 定义继承 `PageParam` 的查询参数,支持木箱号、物料和状态筛选。 +- [ ] 在Mapper XML中实现动态条件、`box_id DESC`排序和分页查询。 +- [ ] Service接收请求对象并返回 `PageResult`。 +- [ ] Controller仅暴露 `GET /wms/box-info/page`,权限为 `wms:box-info:query`。 +- [ ] 运行模块编译并确认成功。 + +### 任务2:前端只读列表 + +**文件:** +- 创建:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/boxinfo/index.ts` +- 创建:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/boxinfo/data.ts` +- 创建:`nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/boxinfo/index.vue` + +- [ ] 定义木箱信息类型和分页API。 +- [ ] 定义五个查询条件和全部基础信息列表列。 +- [ ] 创建仅含查询、刷新和分页的页面,不配置复选框及操作按钮。 +- [ ] 运行前端类型检查并确认新增文件无错误。 + +### 任务3:菜单权限 + +**文件:** +- 创建:`sql/mysql/wms_box_info_menu.sql` + +- [ ] 查询基础管理菜单ID和项目菜单字段结构。 +- [ ] 编写“木箱信息”页面菜单及 `wms:box-info:query` 按钮权限的幂等SQL。 +- [ ] 执行SQL并查询确认菜单记录唯一。 + +### 任务4:联调验证 + +- [ ] 确认后端编译成功且Mapper XML无语法错误。 +- [ ] 确认前端API路径与Controller一致。 +- [ ] 确认菜单组件路径与页面文件一致。 +- [ ] 确认代码中不存在木箱信息写接口或写按钮。 diff --git a/docs/superpowers/plans/2026-08-05-empty-box-transport.md b/docs/superpowers/plans/2026-08-05-empty-box-transport.md new file mode 100644 index 00000000..d2ce22ac --- /dev/null +++ b/docs/superpowers/plans/2026-08-05-empty-box-transport.md @@ -0,0 +1,281 @@ +# 空木箱出入库实现计划 + +> **面向 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 createInbound( + @Valid @RequestBody EmptyBoxInboundCreateReq req); + +@PostMapping(PREFIX + "/createOutbound") +CommonResult 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 绑定、完成和取消状态迁移,以及出库响应目的点全部在代码中形成闭环。 diff --git a/docs/superpowers/plans/2026-08-05-struct-inventory-type.md b/docs/superpowers/plans/2026-08-05-struct-inventory-type.md new file mode 100644 index 00000000..02280878 --- /dev/null +++ b/docs/superpowers/plans/2026-08-05-struct-inventory-type.md @@ -0,0 +1,63 @@ +# 仓位库存类型实现计划 + +> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 + +**目标:** 新增仓位库存类型字段,并在成品、空载具、空木箱和移库完成时保持字段与实际库存一致。 + +**架构:** 库存类型使用 WMS 枚举表达,仓位完成 SQL 与载具号在同一语句中更新;移库服务读取来源类型并传给目标完成 SQL。现有选位逻辑不依赖该字段,历史数据不回填。 + +**技术栈:** Java 17、Spring Boot、MyBatis XML、MySQL、Vue/TypeScript。 + +--- + +### 任务 1:数据库字段和枚举 + +**文件:** +- 创建:`sql/mysql/wms_structattr_inventory_type.sql` +- 创建:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/structattr/StructInventoryTypeEnum.java` +- 修改:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/structAttr/StrucAttrDO.java` + +- [ ] 增加 `inventory_type varchar(1) NOT NULL DEFAULT '0'`,不包含回填 SQL。 +- [ ] 增加 `EMPTY/FINISHED_PRODUCT/EMPTY_VEHICLE/EMPTY_BOX` 枚举,编码为 `0/1/2/3`。 +- [ ] 给仓位 DO 增加 `inventoryType`。 +- [ ] 运行 `git diff --check`,预期退出码 0。 + +### 任务 2:完成 SQL 维护库存类型 + +**文件:** +- 修改:`nl-module-wms/nl-module-wms-server/src/main/resources/mapper/structAttr/StrucAttrMapper.xml` +- 修改:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java` + +- [ ] 成品入库/出库完成分别写入 `1/0`。 +- [ ] 空载具入库/出库完成分别写入 `2/0`。 +- [ ] 空木箱入库/出库完成分别写入 `3/0`。 +- [ ] 移库来源完成写入 `0`;目标完成方法增加 `inventoryType` 参数并写入来源类型。 +- [ ] 运行 `xmllint --noout StrucAttrMapper.xml`,预期退出码 0。 + +### 任务 3:移库动态转移类型 + +**文件:** +- 修改:`nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvServiceImpl.java` + +- [ ] 完成每条移库明细前按来源仓位编码查询仓位。 +- [ ] 来源不存在或类型为 `0` 时抛出来源仓位状态异常。 +- [ ] 将来源 `inventoryType` 传给目标仓位完成更新,然后清空来源。 +- [ ] 取消逻辑不修改库存类型。 + +### 任务 4:仓位管理展示 + +**文件:** +- 修改:仓位保存、响应、分页查询 VO。 +- 修改:`nl-ui/.../src/api/wms/structAttr/index.ts` +- 修改:`nl-ui/.../src/views/wms/structAttr/data.ts` + +- [ ] 后端 VO 增加库存类型字段。 +- [ ] 前端模型、查询条件和列表列增加库存类型,显示“空/成品/空载具/空木箱”。 +- [ ] 运行目标文件 ESLint,预期退出码 0。 + +### 任务 5:验证 + +- [ ] 应用本地数据库字段脚本,不执行历史回填。 +- [ ] 运行 WMS Server 及依赖模块编译,预期 `BUILD SUCCESS`。 +- [ ] 运行 Mapper XML、前端 ESLint 和 `git diff --check`,预期全部退出码 0。 +- [ ] 核对所有完成入口与移库动态转移路径。 diff --git a/docs/superpowers/specs/2026-08-05-box-info-query-design.md b/docs/superpowers/specs/2026-08-05-box-info-query-design.md new file mode 100644 index 00000000..d7f7da59 --- /dev/null +++ b/docs/superpowers/specs/2026-08-05-box-info-query-design.md @@ -0,0 +1,35 @@ +# 木箱信息只读查询设计 + +## 目标 + +在WMS模块的“基础模块”下新增“木箱信息”菜单,读取 `wmw_boxinfo` 表并提供分页查询。该功能只用于查看木箱基础信息,不提供新增、修改、删除、批量操作或导出能力。 + +## 后端设计 + +- 接口地址:`GET /wms/box-info/page`。 +- 权限标识:`wms:box-info:query`。 +- 查询条件:木箱号、物料编码、物料名称、是否包装、载具类型。 +- 默认按 `box_id` 倒序。 +- `wmw_boxinfo` 没有通用审计和逻辑删除字段,Mapper直接返回只读响应对象,不继承通用数据对象,也不暴露通用写方法。 +- 查询SQL写入 `BoxInfoMapper.xml`,Controller、Service 和 Mapper 均不提供写方法。 + +## 前端设计 + +- 页面路径:`views/wms/boxinfo/index.vue`。 +- 页面仅包含查询表单、刷新按钮、分页表格。 +- 不显示复选框、操作列及新增、修改、删除、导出按钮。 +- 表格展示木箱号、物料、数量、尺寸、重量、插入时间、捆扎数量、载具类型和包装状态。 + +## 菜单设计 + +- 菜单名称:木箱信息。 +- 上级菜单:WMS模块 / 基础模块。 +- 组件:`wms/boxinfo/index`。 +- 菜单权限:`wms:box-info:query`。 +- SQL采用 `NOT EXISTS` 幂等写法,同时创建页面菜单和查询按钮权限。 + +## 验证 + +- 编译 `nl-module-base`,确认Java和MyBatis XML可加载。 +- 对前端相关文件执行类型检查或项目既有检查命令。 +- 执行菜单SQL后核对菜单、权限和接口分页响应。 diff --git a/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApi.java b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApi.java new file mode 100644 index 00000000..9317567d --- /dev/null +++ b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApi.java @@ -0,0 +1,33 @@ +package cn.code.nl.module.wms.api.emptybox; + +import cn.code.nl.framework.common.pojo.CommonResult; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateResp; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateResp; +import cn.code.nl.module.wms.enums.ApiConstants; +import jakarta.validation.Valid; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * 空木箱出入库内部接口。 + */ +@FeignClient(name = ApiConstants.NAME) +public interface EmptyBoxApi { + + String PREFIX = ApiConstants.PREFIX + "/empty-box"; + + /** + * 创建空木箱入库任务。 + */ + @PostMapping(PREFIX + "/createInbound") + CommonResult createInbound(@Valid @RequestBody EmptyBoxInboundCreateReq req); + + /** + * 创建空木箱出库任务。 + */ + @PostMapping(PREFIX + "/createOutbound") + CommonResult createOutbound(@Valid @RequestBody EmptyBoxOutboundCreateReq req); +} diff --git a/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxInboundCreateReq.java b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxInboundCreateReq.java new file mode 100644 index 00000000..6e443e5d --- /dev/null +++ b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxInboundCreateReq.java @@ -0,0 +1,17 @@ +package cn.code.nl.module.wms.api.emptybox.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 空木箱入库创建请求。 + */ +@Data +public class EmptyBoxInboundCreateReq { + + @NotBlank(message = "设备号不能为空") + private String deviceCode; + + @NotBlank(message = "木箱规格编码不能为空") + private String materialCode; +} diff --git a/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxInboundCreateResp.java b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxInboundCreateResp.java new file mode 100644 index 00000000..78e97c16 --- /dev/null +++ b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxInboundCreateResp.java @@ -0,0 +1,18 @@ +package cn.code.nl.module.wms.api.emptybox.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 空木箱入库创建结果。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EmptyBoxInboundCreateResp { + + private Long taskId; + private String boxNo; + private String targetStructCode; +} diff --git a/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxOutboundCreateReq.java b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxOutboundCreateReq.java new file mode 100644 index 00000000..316f15b1 --- /dev/null +++ b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxOutboundCreateReq.java @@ -0,0 +1,14 @@ +package cn.code.nl.module.wms.api.emptybox.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * 空木箱出库创建请求。 + */ +@Data +public class EmptyBoxOutboundCreateReq { + + @NotBlank(message = "木箱规格编码不能为空") + private String materialCode; +} diff --git a/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxOutboundCreateResp.java b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxOutboundCreateResp.java new file mode 100644 index 00000000..2a46b999 --- /dev/null +++ b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/api/emptybox/dto/EmptyBoxOutboundCreateResp.java @@ -0,0 +1,18 @@ +package cn.code.nl.module.wms.api.emptybox.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 空木箱出库创建结果。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EmptyBoxOutboundCreateResp { + + private Long taskId; + private String boxNo; + private String destinationPoint; +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApiImpl.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApiImpl.java new file mode 100644 index 00000000..b43c6852 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/api/emptybox/EmptyBoxApiImpl.java @@ -0,0 +1,36 @@ +package cn.code.nl.module.wms.api.emptybox; + +import cn.code.nl.framework.common.pojo.CommonResult; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateResp; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateResp; +import cn.code.nl.module.wms.service.emptybox.EmptyBoxService; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RestController; + +import static cn.code.nl.framework.common.pojo.CommonResult.success; + +/** + * 空木箱出入库内部接口实现。 + */ +@RestController +@Validated +public class EmptyBoxApiImpl implements EmptyBoxApi { + + @Resource + private EmptyBoxService emptyBoxService; + + /** 创建空木箱入库任务。 */ + @Override + public CommonResult createInbound(EmptyBoxInboundCreateReq req) { + return success(emptyBoxService.createInbound(req)); + } + + /** 创建空木箱出库任务。 */ + @Override + public CommonResult createOutbound(EmptyBoxOutboundCreateReq req) { + return success(emptyBoxService.createOutbound(req)); + } +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/BoxInfoController.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/BoxInfoController.java new file mode 100644 index 00000000..c0e376b4 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/BoxInfoController.java @@ -0,0 +1,45 @@ +package cn.code.nl.module.wms.controller.admin.boxinfo; + +import cn.code.nl.framework.common.pojo.CommonResult; +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.module.wms.controller.admin.boxinfo.vo.BoxInfoPageReqVO; +import cn.code.nl.module.wms.controller.admin.boxinfo.vo.BoxInfoRespVO; +import cn.code.nl.module.wms.service.boxinfo.BoxInfoService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.code.nl.framework.common.pojo.CommonResult.success; + +/** + * 木箱基础信息只读查询控制器。 + */ +@Tag(name = "管理后台 - 木箱信息") +@RestController +@RequestMapping("/wms/box-info") +@Validated +public class BoxInfoController { + + /** 木箱基础信息服务。 */ + @Resource + private BoxInfoService boxInfoService; + + /** + * 分页查询木箱基础信息。 + * + * @param pageReqVO 查询参数 + * @return 木箱基础信息分页结果 + */ + @GetMapping("/page") + @Operation(summary = "获得木箱基础信息分页") + @PreAuthorize("@ss.hasPermission('wms:box-info:query')") + public CommonResult> getBoxInfoPage(@Valid BoxInfoPageReqVO pageReqVO) { + return success(boxInfoService.getBoxInfoPage(pageReqVO)); + } +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/vo/BoxInfoPageReqVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/vo/BoxInfoPageReqVO.java new file mode 100644 index 00000000..412f95a5 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/vo/BoxInfoPageReqVO.java @@ -0,0 +1,28 @@ +package cn.code.nl.module.wms.controller.admin.boxinfo.vo; + +import cn.code.nl.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 木箱信息分页查询参数。 + */ +@Data +@Schema(description = "管理后台 - 木箱信息分页查询参数") +public class BoxInfoPageReqVO extends PageParam { + + @Schema(description = "木箱号") + private String boxNo; + + @Schema(description = "物料编码") + private String materialCode; + + @Schema(description = "物料名称") + private String materialName; + + @Schema(description = "是否包装") + private String isPacking; + + @Schema(description = "载具类型") + private String vehicleType; +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/vo/BoxInfoRespVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/vo/BoxInfoRespVO.java new file mode 100644 index 00000000..b0f2b49e --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/boxinfo/vo/BoxInfoRespVO.java @@ -0,0 +1,57 @@ +package cn.code.nl.module.wms.controller.admin.boxinfo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 木箱基础信息响应对象。 + */ +@Data +@Schema(description = "管理后台 - 木箱基础信息") +public class BoxInfoRespVO { + + @Schema(description = "木箱主键", requiredMode = Schema.RequiredMode.REQUIRED) + private Long boxId; + + @Schema(description = "木箱号", requiredMode = Schema.RequiredMode.REQUIRED) + private String boxNo; + + @Schema(description = "物料编码", requiredMode = Schema.RequiredMode.REQUIRED) + private String materialCode; + + @Schema(description = "物料名称", requiredMode = Schema.RequiredMode.REQUIRED) + private String materialName; + + @Schema(description = "数量") + private String num; + + @Schema(description = "木箱长度", requiredMode = Schema.RequiredMode.REQUIRED) + private String boxLength; + + @Schema(description = "木箱宽度", requiredMode = Schema.RequiredMode.REQUIRED) + private String boxWidth; + + @Schema(description = "木箱高度", requiredMode = Schema.RequiredMode.REQUIRED) + private String boxHigh; + + @Schema(description = "插入时间", requiredMode = Schema.RequiredMode.REQUIRED) + private String insertTime; + + @Schema(description = "捆扎数量", requiredMode = Schema.RequiredMode.REQUIRED) + private String lashNum; + + @Schema(description = "载具类型") + private String vehicleType; + + @Schema(description = "是否包装") + private String isPacking; + + @Schema(description = "木箱重量") + private String boxWeight; + + @Schema(description = "木箱状态") + private String status; + + @Schema(description = "出库时间") + private String outTime; +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/emptybox/EmptyBoxController.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/emptybox/EmptyBoxController.java new file mode 100644 index 00000000..ade89e54 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/emptybox/EmptyBoxController.java @@ -0,0 +1,46 @@ +package cn.code.nl.module.wms.controller.admin.emptybox; + +import cn.code.nl.framework.common.pojo.CommonResult; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateResp; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateResp; +import cn.code.nl.module.wms.service.emptybox.EmptyBoxService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.code.nl.framework.common.pojo.CommonResult.success; + +/** + * 空木箱出入库管理接口。 + */ +@Tag(name = "管理后台 - 空木箱出入库") +@RestController +@RequestMapping("/wms/empty-box") +public class EmptyBoxController { + + @Resource + private EmptyBoxService emptyBoxService; + + /** 创建空木箱入库任务。 */ + @PostMapping("/createInbound") + @Operation(summary = "创建空木箱入库任务") + public CommonResult createInbound( + @Valid @RequestBody EmptyBoxInboundCreateReq req) { + return success(emptyBoxService.createInbound(req)); + } + + /** 创建空木箱出库任务。 */ + @PostMapping("/createOutbound") + @Operation(summary = "创建空木箱出库任务") + public CommonResult createOutbound( + @Valid @RequestBody EmptyBoxOutboundCreateReq req) { + return success(emptyBoxService.createOutbound(req)); + } +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/structAttr/vo/StrucAttrPageReqVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/structAttr/vo/StrucAttrPageReqVO.java index 0206e9c1..d0875e5d 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/structAttr/vo/StrucAttrPageReqVO.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/structAttr/vo/StrucAttrPageReqVO.java @@ -104,6 +104,9 @@ public class StrucAttrPageReqVO extends PageParam { @Schema(description = "载具数量") private Integer storagevehicleQty; + @Schema(description = "库存类型:0空、1成品、2空载具、3空木箱") + private String inventoryType; + @Schema(description = "锁定类型", example = "2") private String lockType; @@ -125,4 +128,4 @@ public class StrucAttrPageReqVO extends PageParam { @Schema(description = "备注", example = "你猜") private String remark; -} \ No newline at end of file +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/structAttr/vo/StrucAttrRespVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/structAttr/vo/StrucAttrRespVO.java index c2247d39..261cfcd0 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/structAttr/vo/StrucAttrRespVO.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/structAttr/vo/StrucAttrRespVO.java @@ -104,6 +104,10 @@ public class StrucAttrRespVO { @ExcelProperty("载具数量") private Integer storagevehicleQty; + @Schema(description = "库存类型:0空、1成品、2空载具、3空木箱") + @ExcelProperty("库存类型") + private String inventoryType; + @Schema(description = "锁定类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") @ExcelProperty("锁定类型") private String lockType; @@ -132,4 +136,4 @@ public class StrucAttrRespVO { @ExcelProperty("备注") private String remark; -} \ No newline at end of file +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxinfo/BoxInfoDO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxinfo/BoxInfoDO.java new file mode 100644 index 00000000..acd33117 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxinfo/BoxInfoDO.java @@ -0,0 +1,37 @@ +package cn.code.nl.module.wms.dal.dataobject.boxinfo; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 木箱实例数据对象。 + */ +@Data +@TableName("wmw_boxinfo") +public class BoxInfoDO { + + @TableId + private Long boxId; + private String boxNo; + private String materialCode; + private String materialName; + private String num; + private String boxLength; + private String boxWidth; + private String boxHigh; + private String insertTime; + private String lashNum; + private String vehicleType; + private String isPacking; + private String boxWeight; + private String status; + private String outTime; + + @TableField(exist = false) + private String structId; + + @TableField(exist = false) + private String structCode; +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxtype/BoxTypeDO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxtype/BoxTypeDO.java new file mode 100644 index 00000000..bdcf134d --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/boxtype/BoxTypeDO.java @@ -0,0 +1,22 @@ +package cn.code.nl.module.wms.dal.dataobject.boxtype; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +/** + * 木箱规格数据对象。 + */ +@Data +@TableName("wms_boxtype") +public class BoxTypeDO { + + @TableId + private Long id; + private String materialCode; + private String materialName; + private String lashNum; + private String boxLength; + private String boxWidth; + private String boxHeight; +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/structAttr/EmptyBoxRowCandidate.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/structAttr/EmptyBoxRowCandidate.java new file mode 100644 index 00000000..633d2d9c --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/structAttr/EmptyBoxRowCandidate.java @@ -0,0 +1,16 @@ +package cn.code.nl.module.wms.dal.dataobject.structAttr; + +import lombok.Data; + +/** + * 空木箱入库候选排。 + */ +@Data +public class EmptyBoxRowCandidate { + + private String storId; + private String sectId; + private Integer blockNum; + private Integer rowNum; + private Integer layerNum; +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/structAttr/StrucAttrDO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/structAttr/StrucAttrDO.java index 6d21b914..9f984bcf 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/structAttr/StrucAttrDO.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/structAttr/StrucAttrDO.java @@ -143,6 +143,10 @@ public class StrucAttrDO extends BaseDO { * 载具数量 */ private Integer storagevehicleQty; + /** + * 库存类型:0空、1成品、2空载具、3空木箱 + */ + private String inventoryType; /** * 锁定类型 */ @@ -173,4 +177,4 @@ public class StrucAttrDO extends BaseDO { private String remark; -} \ No newline at end of file +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxinfo/BoxInfoMapper.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxinfo/BoxInfoMapper.java new file mode 100644 index 00000000..e368ae70 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxinfo/BoxInfoMapper.java @@ -0,0 +1,52 @@ +package cn.code.nl.module.wms.dal.mysql.boxinfo; + +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.framework.mybatis.core.util.MyBatisUtils; +import cn.code.nl.module.wms.controller.admin.boxinfo.vo.BoxInfoPageReqVO; +import cn.code.nl.module.wms.controller.admin.boxinfo.vo.BoxInfoRespVO; +import cn.code.nl.module.wms.dal.dataobject.boxinfo.BoxInfoDO; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 木箱基础信息只读 Mapper。 + */ +@Mapper +public interface BoxInfoMapper { + + /** + * 分页查询木箱基础信息。 + * + * @param reqVO 查询参数 + * @return 木箱基础信息分页结果 + */ + default PageResult selectBoxInfoPage(BoxInfoPageReqVO reqVO) { + Page page = selectBoxInfoPage(MyBatisUtils.buildPage(reqVO), reqVO); + return new PageResult<>(page.getRecords(), page.getTotal()); + } + + /** + * 执行木箱基础信息分页查询SQL。 + * + * @param page 分页对象 + * @param reqVO 查询参数 + * @return 木箱基础信息分页数据 + */ + Page selectBoxInfoPage(Page page, @Param("reqVO") BoxInfoPageReqVO reqVO); + + /** 插入待入库木箱。 */ + int insertPendingInbound(BoxInfoDO boxInfo); + + /** 查询先进先出的可出库木箱候选。 */ + java.util.List selectOutboundCandidates(@Param("materialCode") String materialCode); + + /** 入库任务完成。 */ + int completeInbound(@Param("boxNo") String boxNo, @Param("insertTime") String insertTime); + + /** 物理删除取消入库的待入库木箱。 */ + int deletePendingInbound(@Param("boxNo") String boxNo); + + /** 出库任务完成。 */ + int completeOutbound(@Param("boxNo") String boxNo, @Param("outTime") String outTime); +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxtype/BoxTypeMapper.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxtype/BoxTypeMapper.java new file mode 100644 index 00000000..60a017d9 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/boxtype/BoxTypeMapper.java @@ -0,0 +1,17 @@ +package cn.code.nl.module.wms.dal.mysql.boxtype; + +import cn.code.nl.module.wms.dal.dataobject.boxtype.BoxTypeDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 木箱规格 Mapper。 + */ +@Mapper +public interface BoxTypeMapper { + + /** + * 根据规格编码查询木箱规格。 + */ + BoxTypeDO selectByMaterialCode(@Param("materialCode") String materialCode); +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java index 4f926815..63dd50fc 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/structAttr/StrucAttrMapper.java @@ -7,6 +7,7 @@ import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX; import cn.code.nl.module.wms.dal.dataobject.structAttr.StrucAttrDO; import cn.code.nl.module.wms.dal.dataobject.structAttr.EmptyVehicleRowCandidate; +import cn.code.nl.module.wms.dal.dataobject.structAttr.EmptyBoxRowCandidate; import cn.code.nl.module.wms.controller.admin.moveinv.vo.MoveInvInventoryRespVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -20,6 +21,35 @@ import cn.code.nl.module.wms.controller.admin.structAttr.vo.*; @Mapper public interface StrucAttrMapper extends BaseMapperX { + List selectEmptyBoxPreferredRows(@Param("materialCode") String materialCode); + + List selectEmptyBoxEmptyRows(); + + StrucAttrDO selectFreeStructInEmptyBoxRow(EmptyBoxRowCandidate row); + + int lockEmptyBoxInbound(@Param("structId") String structId, + @Param("reservationCode") String reservationCode, + @Param("boxNo") String boxNo); + + int lockEmptyBoxOutbound(@Param("structId") String structId, + @Param("reservationCode") String reservationCode, + @Param("boxNo") String boxNo); + + int bindEmptyBoxTask(@Param("structId") String structId, + @Param("reservationCode") String reservationCode, + @Param("taskId") String taskId, + @Param("lockType") String lockType); + + StrucAttrDO selectEmptyBoxTaskStruct(@Param("taskId") String taskId); + + int completeEmptyBoxInbound(@Param("taskId") String taskId, @Param("boxNo") String boxNo); + + int completeEmptyBoxOutbound(@Param("taskId") String taskId, @Param("boxNo") String boxNo); + + int cancelEmptyBoxTask(@Param("taskId") String taskId); + + Long countEmptyBoxAtPoint(@Param("pointCode") String pointCode); + List selectEmptyVehiclePreferredRows(); List selectCompletelyEmptyRows(); @@ -71,7 +101,8 @@ public interface StrucAttrMapper extends BaseMapperX { int completeMoveIn(@Param("structCode") String structCode, @Param("invId") String invId, - @Param("vehicleCode") String vehicleCode); + @Param("vehicleCode") String vehicleCode, + @Param("inventoryType") String inventoryType); int lockForOutbound(@Param("structId") String structId, @Param("invType") String invType, @@ -132,6 +163,7 @@ public interface StrucAttrMapper extends BaseMapperX { .eqIfPresent(StrucAttrDO::getStoragevehicleCode, reqVO.getStoragevehicleCode()) .eqIfPresent(StrucAttrDO::getStoragevehicleType, reqVO.getStoragevehicleType()) .eqIfPresent(StrucAttrDO::getStoragevehicleQty, reqVO.getStoragevehicleQty()) + .eqIfPresent(StrucAttrDO::getInventoryType, reqVO.getInventoryType()) .eqIfPresent(StrucAttrDO::getLockType, reqVO.getLockType()) .eqIfPresent(StrucAttrDO::getTaskCode, reqVO.getTaskCode()) .eqIfPresent(StrucAttrDO::getInvType, reqVO.getInvType()) diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/emptybox/EmptyBoxStatusEnum.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/emptybox/EmptyBoxStatusEnum.java new file mode 100644 index 00000000..49dfb2f8 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/emptybox/EmptyBoxStatusEnum.java @@ -0,0 +1,21 @@ +package cn.code.nl.module.wms.enums.emptybox; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 空木箱状态。 + */ +@Getter +@AllArgsConstructor +public enum EmptyBoxStatusEnum { + + PENDING_INBOUND("0"), + IN_STOCK("1"), + OUTBOUND("2"); + + /** + * 状态编码。 + */ + private final String code; +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/structattr/StructInventoryTypeEnum.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/structattr/StructInventoryTypeEnum.java new file mode 100644 index 00000000..146aed85 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/structattr/StructInventoryTypeEnum.java @@ -0,0 +1,20 @@ +package cn.code.nl.module.wms.enums.structattr; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 仓位库存类型。 + */ +@Getter +@AllArgsConstructor +public enum StructInventoryTypeEnum { + + EMPTY("0"), + FINISHED_PRODUCT("1"), + EMPTY_VEHICLE("2"), + EMPTY_BOX("3"); + + /** 类型编码。 */ + private final String code; +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/boxinfo/BoxInfoService.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/boxinfo/BoxInfoService.java new file mode 100644 index 00000000..0aed23a2 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/boxinfo/BoxInfoService.java @@ -0,0 +1,19 @@ +package cn.code.nl.module.wms.service.boxinfo; + +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.module.wms.controller.admin.boxinfo.vo.BoxInfoPageReqVO; +import cn.code.nl.module.wms.controller.admin.boxinfo.vo.BoxInfoRespVO; + +/** + * 木箱基础信息只读服务。 + */ +public interface BoxInfoService { + + /** + * 分页查询木箱基础信息。 + * + * @param pageReqVO 查询参数 + * @return 木箱基础信息分页结果 + */ + PageResult getBoxInfoPage(BoxInfoPageReqVO pageReqVO); +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/boxinfo/BoxInfoServiceImpl.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/boxinfo/BoxInfoServiceImpl.java new file mode 100644 index 00000000..7614c396 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/boxinfo/BoxInfoServiceImpl.java @@ -0,0 +1,32 @@ +package cn.code.nl.module.wms.service.boxinfo; + +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.module.wms.controller.admin.boxinfo.vo.BoxInfoPageReqVO; +import cn.code.nl.module.wms.controller.admin.boxinfo.vo.BoxInfoRespVO; +import cn.code.nl.module.wms.dal.mysql.boxinfo.BoxInfoMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +/** + * 木箱基础信息只读服务实现。 + */ +@Service +@Validated +public class BoxInfoServiceImpl implements BoxInfoService { + + /** 木箱基础信息 Mapper。 */ + @Resource + private BoxInfoMapper boxInfoMapper; + + /** + * 分页查询木箱基础信息。 + * + * @param pageReqVO 查询参数 + * @return 木箱基础信息分页结果 + */ + @Override + public PageResult getBoxInfoPage(BoxInfoPageReqVO pageReqVO) { + return boxInfoMapper.selectBoxInfoPage(pageReqVO); + } +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/emptybox/EmptyBoxService.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/emptybox/EmptyBoxService.java new file mode 100644 index 00000000..e31ccc5d --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/emptybox/EmptyBoxService.java @@ -0,0 +1,24 @@ +package cn.code.nl.module.wms.service.emptybox; + +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateResp; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateResp; + +/** + * 空木箱出入库服务。 + */ +public interface EmptyBoxService { + + /** 创建空木箱入库任务。 */ + EmptyBoxInboundCreateResp createInbound(EmptyBoxInboundCreateReq req); + + /** 创建空木箱出库任务。 */ + EmptyBoxOutboundCreateResp createOutbound(EmptyBoxOutboundCreateReq req); + + /** 完成空木箱任务。 */ + void completeTask(Long taskId); + + /** 取消空木箱任务。 */ + void cancelTask(Long taskId); +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/emptybox/EmptyBoxServiceImpl.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/emptybox/EmptyBoxServiceImpl.java new file mode 100644 index 00000000..c8d3b5ff --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/emptybox/EmptyBoxServiceImpl.java @@ -0,0 +1,264 @@ +package cn.code.nl.module.wms.service.emptybox; + +import cn.code.nl.framework.common.exception.ServiceException; +import cn.code.nl.module.base.api.codegen.CodeGenApi; +import cn.code.nl.module.base.api.codegen.dto.CodeGenerateReqDTO; +import cn.code.nl.module.task.api.TransportTaskApi; +import cn.code.nl.module.task.dto.TransportTaskCreateReqDTO; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxInboundCreateResp; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateReq; +import cn.code.nl.module.wms.api.emptybox.dto.EmptyBoxOutboundCreateResp; +import cn.code.nl.module.wms.dal.dataobject.boxinfo.BoxInfoDO; +import cn.code.nl.module.wms.dal.dataobject.boxtype.BoxTypeDO; +import cn.code.nl.module.wms.dal.dataobject.structAttr.EmptyBoxRowCandidate; +import cn.code.nl.module.wms.dal.dataobject.structAttr.StrucAttrDO; +import cn.code.nl.module.wms.dal.mysql.boxinfo.BoxInfoMapper; +import cn.code.nl.module.wms.dal.mysql.boxtype.BoxTypeMapper; +import cn.code.nl.module.wms.dal.mysql.structAttr.StrucAttrMapper; +import cn.code.nl.module.wms.enums.emptybox.EmptyBoxStatusEnum; +import cn.hutool.core.util.IdUtil; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * 空木箱出入库服务实现。 + */ +@Service +public class EmptyBoxServiceImpl implements EmptyBoxService { + + private static final String OWNER_SERVICE = "WMS"; + private static final String HANDLE_CODE = "EMPTYBOXTASK"; + private static final String INBOUND_LOCK = "8"; + private static final String OUTBOUND_LOCK = "9"; + private static final String OUTBOUND_POINT_1 = "ZXQ_01"; + private static final String OUTBOUND_POINT_2 = "ZXQ_02"; + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Resource + private BoxTypeMapper boxTypeMapper; + @Resource + private BoxInfoMapper boxInfoMapper; + @Resource + private StrucAttrMapper strucAttrMapper; + @Resource + private CodeGenApi codeGenApi; + @Resource + private TransportTaskApi transportTaskApi; + + /** + * 创建空木箱入库任务。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public EmptyBoxInboundCreateResp createInbound(EmptyBoxInboundCreateReq req) { + BoxTypeDO boxType = boxTypeMapper.selectByMaterialCode(req.getMaterialCode()); + if (boxType == null) { + throw new ServiceException(500, "木箱规格不存在"); + } + String boxNo = generateBoxNo(); + String reservationCode = "EMPTY_BOX_" + IdUtil.getSnowflakeNextIdStr(); + StrucAttrDO struct = reserveInboundStruct(req.getMaterialCode(), reservationCode, boxNo); + if (struct == null) { + throw new ServiceException(500, "未找到符合条件的空木箱入库仓位"); + } + insertPendingBox(boxType, boxNo); + Long taskId = createTransportTask("空木箱入库任务", "EMPTY_BOX_IN", struct, req.getDeviceCode(), + struct.getStructCode(), boxNo, req.getMaterialCode()); + bindTask(struct, reservationCode, taskId, INBOUND_LOCK); + transportTaskApi.issueTransportTaskRollBack(taskId).getCheckedData(); + return new EmptyBoxInboundCreateResp(taskId, boxNo, struct.getStructCode()); + } + + /** + * 创建空木箱出库任务。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public EmptyBoxOutboundCreateResp createOutbound(EmptyBoxOutboundCreateReq req) { + if (boxTypeMapper.selectByMaterialCode(req.getMaterialCode()) == null) { + throw new ServiceException(500, "木箱规格不存在"); + } + String reservationCode = "EMPTY_BOX_" + IdUtil.getSnowflakeNextIdStr(); + BoxInfoDO box = reserveOutboundBox(req.getMaterialCode(), reservationCode); + if (box == null) { + throw new ServiceException(500, "没有可出库的该规格空木箱"); + } + String destinationPoint = selectOutboundPoint(); + StrucAttrDO struct = new StrucAttrDO(); + struct.setStructId(box.getStructId()); + struct.setStructCode(box.getStructCode()); + Long taskId = createTransportTask("空木箱出库任务", "EMPTY_BOX_OUT", struct, + box.getStructCode(), destinationPoint, box.getBoxNo(), req.getMaterialCode()); + bindTask(struct, reservationCode, taskId, OUTBOUND_LOCK); + transportTaskApi.issueTransportTaskRollBack(taskId).getCheckedData(); + return new EmptyBoxOutboundCreateResp(taskId, box.getBoxNo(), destinationPoint); + } + + /** + * 完成空木箱任务。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void completeTask(Long taskId) { + String taskCode = String.valueOf(taskId); + StrucAttrDO struct = strucAttrMapper.selectEmptyBoxTaskStruct(taskCode); + if (struct == null) { + throw new ServiceException(500, "未找到空木箱任务锁定的仓位"); + } + String boxNo = struct.getInvCode(); + String time = LocalDateTime.now().format(TIME_FORMATTER); + if (INBOUND_LOCK.equals(struct.getLockType())) { + if (strucAttrMapper.completeEmptyBoxInbound(taskCode, boxNo) != 1 + || boxInfoMapper.completeInbound(boxNo, time) != 1) { + throw new ServiceException(500, "空木箱入库完成状态异常"); + } + return; + } + if (strucAttrMapper.completeEmptyBoxOutbound(taskCode, boxNo) != 1 + || boxInfoMapper.completeOutbound(boxNo, time) != 1) { + throw new ServiceException(500, "空木箱出库完成状态异常"); + } + } + + /** + * 取消空木箱任务。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelTask(Long taskId) { + String taskCode = String.valueOf(taskId); + StrucAttrDO struct = strucAttrMapper.selectEmptyBoxTaskStruct(taskCode); + if (struct == null) { + throw new ServiceException(500, "未找到空木箱任务锁定的仓位"); + } + if (strucAttrMapper.cancelEmptyBoxTask(taskCode) != 1) { + throw new ServiceException(500, "空木箱任务取消状态异常"); + } + if (INBOUND_LOCK.equals(struct.getLockType()) + && boxInfoMapper.deletePendingInbound(struct.getInvCode()) != 1) { + throw new ServiceException(500, "待入库木箱删除失败"); + } + } + + /** + * 生成木箱编码。 + */ + private String generateBoxNo() { + CodeGenerateReqDTO req = new CodeGenerateReqDTO(); + req.setRuleCode("BOX_CODE"); + return codeGenApi.generate(req).getCheckedData(); + } + + /** + * 按同规格排、全空排的顺序预占入库仓位。 + */ + private StrucAttrDO reserveInboundStruct(String materialCode, String reservationCode, String boxNo) { + StrucAttrDO struct = reserveFromRows(strucAttrMapper.selectEmptyBoxPreferredRows(materialCode), + reservationCode, boxNo); + return struct != null ? struct + : reserveFromRows(strucAttrMapper.selectEmptyBoxEmptyRows(), reservationCode, boxNo); + } + + /** + * 从候选排逐排尝试抢占空仓位。 + */ + private StrucAttrDO reserveFromRows(List rows, String reservationCode, String boxNo) { + for (EmptyBoxRowCandidate row : rows) { + StrucAttrDO struct = strucAttrMapper.selectFreeStructInEmptyBoxRow(row); + if (struct != null && strucAttrMapper.lockEmptyBoxInbound( + struct.getStructId(), reservationCode, boxNo) == 1) { + return struct; + } + } + return null; + } + + /** + * 创建待入库木箱实例。 + */ + private void insertPendingBox(BoxTypeDO boxType, String boxNo) { + BoxInfoDO box = new BoxInfoDO(); + box.setBoxId(IdUtil.getSnowflakeNextId()); + box.setBoxNo(boxNo); + box.setMaterialCode(boxType.getMaterialCode()); + box.setMaterialName(boxType.getMaterialName()); + box.setNum("1"); + box.setBoxLength(boxType.getBoxLength()); + box.setBoxWidth(boxType.getBoxWidth()); + box.setBoxHigh(boxType.getBoxHeight()); + box.setInsertTime(LocalDateTime.now().format(TIME_FORMATTER)); + box.setLashNum(boxType.getLashNum()); + box.setIsPacking("0"); + box.setStatus(EmptyBoxStatusEnum.PENDING_INBOUND.getCode()); + if (boxInfoMapper.insertPendingInbound(box) != 1) { + throw new ServiceException(500, "待入库木箱创建失败"); + } + } + + /** + * 按先进先出顺序抢占可出库木箱。 + */ + private BoxInfoDO reserveOutboundBox(String materialCode, String reservationCode) { + for (BoxInfoDO box : boxInfoMapper.selectOutboundCandidates(materialCode)) { + if (strucAttrMapper.lockEmptyBoxOutbound( + box.getStructId(), reservationCode, box.getBoxNo()) == 1) { + return box; + } + } + return null; + } + + /** + * 按任务和点位库存综合负载选择出库点。 + */ + private String selectOutboundPoint() { + // TODO 后续改为调用 LMS 接口,统一获取候选出库点和点位库存占用情况。 + long point1Load = transportTaskApi.countUnfinishedByDestination( + OUTBOUND_POINT_1, OWNER_SERVICE).getCheckedData() + + strucAttrMapper.countEmptyBoxAtPoint(OUTBOUND_POINT_1); + long point2Load = transportTaskApi.countUnfinishedByDestination( + OUTBOUND_POINT_2, OWNER_SERVICE).getCheckedData() + + strucAttrMapper.countEmptyBoxAtPoint(OUTBOUND_POINT_2); + return point1Load <= point2Load ? OUTBOUND_POINT_1 : OUTBOUND_POINT_2; + } + + /** + * 创建搬运任务。 + */ + private Long createTransportTask(String taskName, String bizType, StrucAttrDO struct, + String startPoint, String endPoint, String boxNo, String materialCode) { + TransportTaskCreateReqDTO task = new TransportTaskCreateReqDTO(); + task.setTaskName(taskName); + task.setOwnerService(OWNER_SERVICE); + task.setBizType(bizType); + task.setBizId(struct.getStructId()); + task.setHandleCode(HANDLE_CODE); + task.setTaskType("EMPTY_BOX_IN".equals(bizType) ? "010501" : "010503"); + task.setAcsTaskType("7"); + task.setAgvSystemType("1"); + task.setPointCode1(startPoint); + task.setPointCode2(endPoint); + task.setVehicleCode(boxNo); + task.setMaterialCode(materialCode); + task.setIsAutoIssue("0"); + task.setProductArea("LK"); + task.setIsCreateFinish(true); + return transportTaskApi.createTransportTask(task).getCheckedData(); + } + + /** + * 将仓位预占码替换为任务 ID。 + */ + private void bindTask(StrucAttrDO struct, String reservationCode, Long taskId, String lockType) { + if (strucAttrMapper.bindEmptyBoxTask(struct.getStructId(), reservationCode, + String.valueOf(taskId), lockType) != 1) { + throw new ServiceException(500, "空木箱仓位预占状态异常,请重试"); + } + } +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvServiceImpl.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvServiceImpl.java index 42ae65b9..b00b2eba 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvServiceImpl.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/moveinv/MoveInvServiceImpl.java @@ -259,9 +259,13 @@ public class MoveInvServiceImpl implements MoveInvService { Set moveInvIds = new HashSet<>(); for (MoveInvDtlDO detail : details) { String moveInvId = String.valueOf(detail.getMoveinvId()); + StrucAttrDO sourceStruct = strucAttrMapper.selectByStructCode(detail.getTurnoutStructCode()); + if (sourceStruct == null || "0".equals(sourceStruct.getInventoryType())) { + throw exception(MOVE_INV_SOURCE_INVALID); + } // 先占用目标仓位,再清理来源仓位;任一步失败都会回滚整个事务。 if (strucAttrMapper.completeMoveIn(detail.getTurninStructCode(), moveInvId, - detail.getStoragevehicleCode()) != 1) { + detail.getStoragevehicleCode(), sourceStruct.getInventoryType()) != 1) { throw exception(MOVE_INV_TARGET_INVALID); } if (strucAttrMapper.completeMoveOut(detail.getTurnoutStructCode(), moveInvId, diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/task/EmptyBoxTask.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/task/EmptyBoxTask.java new file mode 100644 index 00000000..b2175ad5 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/task/EmptyBoxTask.java @@ -0,0 +1,29 @@ +package cn.code.nl.module.wms.task; + +import cn.code.nl.framework.execute.core.AbstractTask; +import cn.code.nl.framework.execute.core.dto.TaskExecuteDTO; +import cn.code.nl.module.wms.service.emptybox.EmptyBoxService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +/** + * 空木箱任务状态处理器。 + */ +@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()); + } +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxinfo/BoxInfoMapper.xml b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxinfo/BoxInfoMapper.xml new file mode 100644 index 00000000..401e2c2f --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxinfo/BoxInfoMapper.xml @@ -0,0 +1,83 @@ + + + + + + + + INSERT INTO wmw_boxinfo + (box_id, box_no, material_code, material_name, num, box_length, box_width, box_high, + insert_time, lash_num, vehicle_type, is_packing, status) + VALUES + (#{boxId}, #{boxNo}, #{materialCode}, #{materialName}, #{num}, #{boxLength}, #{boxWidth}, #{boxHigh}, + #{insertTime}, #{lashNum}, #{vehicleType}, #{isPacking}, #{status}) + + + + + + UPDATE wmw_boxinfo + SET status = '1', insert_time = #{insertTime} + WHERE box_no = #{boxNo} AND status = '0' + + + + DELETE FROM wmw_boxinfo WHERE box_no = #{boxNo} AND status = '0' + + + + UPDATE wmw_boxinfo + SET status = '2', out_time = #{outTime} + WHERE box_no = #{boxNo} AND status = '1' + + + diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxtype/BoxTypeMapper.xml b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxtype/BoxTypeMapper.xml new file mode 100644 index 00000000..b9e6571d --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/boxtype/BoxTypeMapper.xml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/structAttr/StrucAttrMapper.xml b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/structAttr/StrucAttrMapper.xml index 4cea95d4..2c0566f8 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/structAttr/StrucAttrMapper.xml +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/structAttr/StrucAttrMapper.xml @@ -3,6 +3,116 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + + + + + + + UPDATE wms_structattr + SET lock_type = '8', task_code = #{reservationCode}, + inv_type = 'EMPTY_BOX_IN', inv_code = #{boxNo} + WHERE struct_id = #{structId} AND deleted = '0' AND is_used = 1 + AND lock_type = '1' + AND (storagevehicle_code IS NULL OR storagevehicle_code = '') + + + + UPDATE wms_structattr + SET lock_type = '9', task_code = #{reservationCode}, + inv_type = 'EMPTY_BOX_OUT', inv_code = #{boxNo} + WHERE struct_id = #{structId} AND deleted = '0' AND is_used = 1 + AND lock_type = '1' AND storagevehicle_code = #{boxNo} + AND EXISTS (SELECT 1 FROM wmw_boxinfo b + WHERE b.box_no = #{boxNo} AND b.status = '1') + + + + UPDATE wms_structattr SET task_code = #{taskId} + WHERE struct_id = #{structId} AND deleted = '0' + AND lock_type = #{lockType} AND task_code = #{reservationCode} + + + + + + UPDATE wms_structattr + SET storagevehicle_code = #{boxNo}, storagevehicle_qty = 1, + inventory_type = '3', + lock_type = '1', task_code = NULL, inv_type = NULL, inv_code = NULL + WHERE deleted = '0' AND task_code = #{taskId} AND lock_type = '8' + AND inv_code = #{boxNo} + AND (storagevehicle_code IS NULL OR storagevehicle_code = '') + + + + UPDATE wms_structattr + SET storagevehicle_code = NULL, storagevehicle_qty = 0, + inventory_type = '0', + lock_type = '1', task_code = NULL, inv_type = NULL, inv_code = NULL + WHERE deleted = '0' AND task_code = #{taskId} AND lock_type = '9' + AND inv_code = #{boxNo} AND storagevehicle_code = #{boxNo} + + + + UPDATE wms_structattr + SET lock_type = '1', task_code = NULL, inv_type = NULL, inv_code = NULL + WHERE deleted = '0' AND task_code = #{taskId} AND lock_type IN ('8', '9') + + + +