# 2026-05-26 会话记忆 ## 本周聚焦 - 只关注 **tiny-erp**,不处理其他项目 - 只关注 **md(主数据)** 和 **im(库存)** 两个模块 - **不再关注** om(销售)、mc(采购)、fin(财务)、home、system 等其他模块 ## 模块路径确认 包路径: `com.witsoft.erp` 项目路径: `/home/yangxuan/Projects/IdeaProjects/tiny-erp` ### md 模块(主数据) `module/md/` — 已完整实现 - `category` — 物料分类 - `customer` — 客户 - `factory` — 工厂 - `material` — 物料 - `supplier` — 供应商 - `warehouse` — 仓库 ### im 模块(库存) `module/im/` — 已完整实现 - `inbound` — 入库单(含采购入库、生产入库) - `outbound` — 出库单 - `stock` — 库存查询、库存流水、呆滞分析 - `stocktake` — 库存盘点(含快速盘点) ## 原记忆清理 旧记忆(memory/2026-05-23.md)中关于 `bd` 模块的命名实际上是 `md`(代码实际使用的是 `md`,不是 `bd`)。 ome/mc/fin/home/system 模块依然存在但不处理。 ## 注释规范(已写入 MEMORY.md) 1. 所有公共接口和类必须包含 Javadoc 2. 注释语言:中文描述业务背景,技术术语保留英文(如 NPE) 3. 禁止生成 "Gets the value of X" 这类无意义的 getter/setter 注释 4. 复杂算法必须在代码块上方解释核心逻辑 5. 禁止使用 HTML 标签 ## 开发注意事项(已写入 MEMORY.md) 1. **复用优先**:逻辑雷同时提取公共方法复用 2. **业务表仅存物料 id**:物料相关字段仅存 `material_id`,其他数据通过关联查询填充 3. **填充在 Service 层**:关联数据填充逻辑统一在 Service 层处理 4. **首个版本直接改 DDL**:脚本直接修改建表语句,不需要写 ALTER TABLE 5. **字典优先用 DictService**:字典取值优先使用 DictService 公共方法 ## 库存业务表字段清理 **背景:** 业务表只存物料 ID,物料编码/名称/规格/单位通过关联查询填充。 **DDL 改动(50-inventory.sql):** - `im_stock` — 移除 material_code, material_name, spec, unit - `im_stock_transaction` — 移除 material_code, material_name - `im_inbound_item` — 移除 material_code, material_name, spec, unit - `im_outbound_item` — 移除 material_code, material_name, spec, unit **实体类:** Stock, StockTransaction, InboundItem, OutboundItem — 移除对应字段 **请求 DTO:** InboundItemSaveReqDTO, OutboundItemSaveReqDTO, StocktakeItemSaveReqDTO, QuickStocktakeItemDTO — 只保留 materialId **接口改动:** - StockTransactionService.recordTransaction() — 移除 materialCode/materialName 参数 - MaterialService — 新增 searchMaterialIdsByKeyword() 方法 **Service 改动:** - StockTransactionServiceImpl — keyword 搜索改为两步(先查物料表匹配 ID);DTO 填充从 materialInfoMap 读取 - StockServiceImpl — 同上 - InboundServiceImpl — saveItems 不 set 冗余字段;recordTransaction 去参;history 从 material 填充 - OutboundServiceImpl — 同上 - MaterialServiceImpl — 实现 searchMaterialIdsByKeyword() **编译验证:** `mvn compile -q` ✅ ## Git 提交约定 - **commit 跳过 application.yml** — `git add` 时用 `git add . -- ':!application.yml'` 或手动排除 - **commit 后不要 push** — 只做本地提交,不推送远程 # 2026-05-26 会话记忆 ## 物料信息填充公共服务 (MaterialFillService) ### 核心设计决策 1. **实体设计原则** - `InboundItem` 和 `OutboundItem` 只存储 `materialId` - 不存储物料编码、名称、规格、单位等字段 - 防止物料信息变更后历史数据不一致 2. **批量查询模式** ```java List materialIds = items.stream().map(Item::getMaterialId).collect(Collectors.toList()); Map materialInfoMap = materialFillService.getMaterialInfoMap(materialIds); ``` - 避免 N+1 查询问题 - 一次查询所有物料信息,性能更优 3. **Service 层填充** - 所有物料信息填充逻辑在 Service 层处理 - Controller 只负责返回 DTO - 不放在 Mapper 或 Controller 层 ### 修改的 Service 类(共 6 个) | Service | 方法 | 用途 | |---------|------|------| | InboundServiceImpl | executeInbound(), cancel() | 入库/冲销时记录流水 | | OutboundServiceImpl | executeOutbound(), cancel() | 出库/冲销时记录流水 | | FinAccountsAutoServiceImpl | createApFromInbound(), cancelApFromInbound() | 采购应付联动 | | FinAccountsAutoServiceImpl | createArFromOutbound(), cancelArFromOutbound() | 销售应收联动 | | HomeServiceImpl | getLatestNews() | 首页动态显示 | | DeliveryServiceImpl | confirmOutbound() | 发货确认 | ### 物料类型字典 - Dict Type: `material_type` - 值:`P`-原材料,`SA`-半成品,`FG`-成品 - SQL 脚本:`scripts/postgresql/22-material-type-dict.sql` ### Git 状态 - 所有修改已 commit(13+ commits) - **禁止 push** - 用户明确要求 - 分支:master ### 经验教训 1. 修改实体类后,所有引用该实类的地方都要检查 2. 编译错误可能分散在多个模块(im, fin, home, om) 3. 使用 `mvn clean compile` 确保完整编译,避免 IDE 缓存误导 # 2026-05-26 会话记忆 ## 本周聚焦 - 只关注 **tiny-erp**,不处理其他项目 - 只关注 **md(主数据)** 和 **im(库存)** 两个模块 - **不再关注** om(销售)、mc(采购)、fin(财务)、home、system 等其他模块 ## 模块路径确认 包路径: `com.witsoft.erp` 项目路径: `/home/yangxuan/Projects/IdeaProjects/tiny-erp` ### md 模块(主数据) `module/md/` — 已完整实现 - `category` — 物料分类 - `customer` — 客户 - `factory` — 工厂 - `material` — 物料 - `supplier` — 供应商 - `warehouse` — 仓库 ### im 模块(库存) `module/im/` — 已完整实现 - `inbound` — 入库单(含采购入库、生产入库) - `outbound` — 出库单 - `stock` — 库存查询、库存流水、呆滞分析 - `stocktake` — 库存盘点(含快速盘点) ## 原记忆清理 旧记忆(memory/2026-05-23.md)中关于 `bd` 模块的命名实际上是 `md`(代码实际使用的是 `md`,不是 `bd`)。 ome/mc/fin/home/system 模块依然存在但不处理。 ## 注释规范(已写入 MEMORY.md) 1. 所有公共接口和类必须包含 Javadoc 2. 注释语言:中文描述业务背景,技术术语保留英文(如 NPE) 3. 禁止生成 "Gets the value of X" 这类无意义的 getter/setter 注释 4. 复杂算法必须在代码块上方解释核心逻辑 5. 禁止使用 HTML 标签 ## 开发注意事项(已写入 MEMORY.md) 1. **复用优先**:逻辑雷同时提取公共方法复用 2. **业务表仅存物料 id**:物料相关字段仅存 `material_id`,其他数据通过关联查询填充 3. **填充在 Service 层**:关联数据填充逻辑统一在 Service 层处理 4. **首个版本直接改 DDL**:脚本直接修改建表语句,不需要写 ALTER TABLE 5. **字典优先用 DictService**:字典取值优先使用 DictService 公共方法 ## Git 提交约定 - **commit 跳过 application.yml** — `git add . -- ':!application.yml'` 排除,防止泄露配置 - **commit 后不要 push** — 只做本地 commit,不推送远程仓库 ## 库存业务表字段清理 **背景:** 业务表只存物料 ID,物料编码/名称/规格/单位通过关联查询填充。 **DDL 改动(50-inventory.sql):** - `im_stock` — 移除 material_code, material_name, spec, unit - `im_stock_transaction` — 移除 material_code, material_name - `im_inbound_item` — 移除 material_code, material_name, spec, unit - `im_outbound_item` — 移除 material_code, material_name, spec, unit **实体类:** Stock, StockTransaction, InboundItem, OutboundItem — 移除对应字段 **请求 DTO:** InboundItemSaveReqDTO, OutboundItemSaveReqDTO, StocktakeItemSaveReqDTO, QuickStocktakeItemDTO — 只保留 materialId **接口改动:** - StockTransactionService.recordTransaction() — 移除 materialCode/materialName 参数 - MaterialService — 新增 searchMaterialIdsByKeyword() 方法 **Service 改动:** - StockTransactionServiceImpl — keyword 搜索改为两步(先查物料表匹配 ID);DTO 填充从 materialInfoMap 读取 - StockServiceImpl — 同上 - InboundServiceImpl — saveItems 不 set 冗余字段;recordTransaction 去参;history 从 material 填充 - OutboundServiceImpl — 同上 - MaterialServiceImpl — 实现 searchMaterialIdsByKeyword() **编译验证:** `mvn compile -q` ✅ ## 物料信息填充公共服务 (MaterialFillService) ### 核心设计决策 1. **实体设计原则** - `InboundItem` 和 `OutboundItem` 只存储 `materialId` - 不存储物料编码、名称、规格、单位等字段 - 防止物料信息变更后历史数据不一致 2. **批量查询模式** ```java List materialIds = items.stream().map(Item::getMaterialId).collect(Collectors.toList()); Map materialInfoMap = materialFillService.getMaterialInfoMap(materialIds); ``` - 避免 N+1 查询问题 - 一次查询所有物料信息,性能更优 3. **Service 层填充** - 所有物料信息填充逻辑在 Service 层处理 - Controller 只负责返回 DTO - 不放在 Mapper 或 Controller 层 ### 修改的 Service 类(共 6 个) | Service | 方法 | 用途 | |---------|------|------| | InboundServiceImpl | executeInbound(), cancel() | 入库/冲销时记录流水 | | OutboundServiceImpl | executeOutbound(), cancel() | 出库/冲销时记录流水 | | FinAccountsAutoServiceImpl | createApFromInbound(), cancelApFromInbound() | 采购应付联动 | | FinAccountsAutoServiceImpl | createArFromOutbound(), cancelArFromOutbound() | 销售应收联动 | | HomeServiceImpl | getLatestNews() | 首页动态显示 | | DeliveryServiceImpl | confirmOutbound() | 发货确认 | ### 物料类型字典 - Dict Type: `material_type` - 值:`P`-原材料,`SA`-半成品,`FG`-成品 - SQL 脚本:`scripts/postgresql/22-material-type-dict.sql` ### Git 状态 - 所有修改已 commit(13+ commits) - **禁止 push** - 用户明确要求 - 分支:master ## 物料选择器(Material Selector) 新增物料选择器功能,用于入库/出库操作时弹窗选物料。 ### 接口 | 接口 | 位置 | 数据源 | 仓库参数 | |------|------|--------|---------| | `POST /im/inbound/material-selector` | InboundController | md_material | 不需要 | | `POST /im/outbound/material-selector` | OutboundController | im_stock JOIN md_material | 需要 warehouseId | ### 请求参数(MaterialSelectorReqDTO → md.material.dto) - `categoryId` — 0=常用物料,>0=按分类筛选 - `keyword` — 物料编码/名称/规格关键字 - `warehouseId` — 出库场景需要 - 继承 `PageQuery`(pageNum, pageSize) ### 常用物料 TOP10 逻辑 - **口径:** 近 30 天出入库明细条目数最多的 10 个物料 - **入库:** `im_inbound_item` GROUP BY material_id ORDER BY COUNT(*) DESC LIMIT 10 - **出库:** `im_outbound_item` 同上 - **SQL 放 Mapper XML**(方便后续扩展) ### 分类筛选逻辑 - **入库:** MyBatis Plus LambdaQueryWrapper 查 `md_material` 表,按 `category_id` + `keyword` 分页 - **出库:** XML 联表 `im_stock JOIN md_material`,按 `category_id` + `keyword` + `warehouseId` 分页,只查库存 > 0 的数据 ### 响应 - 复用 `StockPageDTO`(im.stock.dto) - 物料编码/名称/规格/单位/分类等从 `MaterialFillService` 填充 ### 新增文件(7个) | 文件 | 路径 | |------|------| | MaterialSelectorReqDTO | md.material.dto | | MaterialSelectorService | md.material.service | | MaterialSelectorServiceImpl | md.material.service.impl | | InboundItemMapper.xml | resources/mapper/im/ | | OutboundItemMapper.xml | resources/mapper/im/ | | StockMapper.xml | resources/mapper/im/ | | | | ### 修改文件(5个) | 文件 | 改动 | |------|------| | InboundItemMapper.java | + selectRecentMaterialIds() | | OutboundItemMapper.java | + selectRecentMaterialIds() | | StockMapper.java | + selectStockPage() / countStockPage() | | InboundController.java | + /material-selector 端点 | | OutboundController.java | + /material-selector 端点 | ## 历史单据创建人名称 ### 改动 - **InboundHistoryItemDTO** — 新增 `creatorName` 字段 - **OutboundHistoryItemDTO** — 新增 `creatorName` 字段 - **InboundServiceImpl.listHistory()** — 收集 creators → 批量查 sys_user(username→nickname) → 填充 - **OutboundServiceImpl.listHistory()** — 同上 ### 逻辑 1. 从单据列表收集所有 `creator`(用户名) 2. `userService.lambdaQuery().in(User::getUsername, creators).list()` 3. 转 `Map`(username → nickname) 4. 逐行 `dto.setCreatorName()` 5. 一次 SQL,不 N+1 # 2026-05-26 会话记忆 ## 本周聚焦 - 只关注 **tiny-erp**,不处理其他项目 - 只关注 **md(主数据)** 和 **im(库存)** 两个模块 - **不再关注** om(销售)、mc(采购)、fin(财务)、home、system 等其他模块 ## 模块路径确认 包路径: `com.witsoft.erp` 项目路径: `/home/yangxuan/Projects/IdeaProjects/tiny-erp` ### md 模块(主数据) `module/md/` — 已完整实现 - `category` — 物料分类 - `customer` — 客户 - `factory` — 工厂 - `material` — 物料 - `supplier` — 供应商 - `warehouse` — 仓库 ### im 模块(库存) `module/im/` — 已完整实现 - `inbound` — 入库单(含采购入库、生产入库) - `outbound` — 出库单 - `stock` — 库存查询、库存流水、呆滞分析 - `stocktake` — 库存盘点(含快速盘点) ## 原记忆清理 旧记忆(memory/2026-05-23.md)中关于 `bd` 模块的命名实际上是 `md`(代码实际使用的是 `md`,不是 `bd`)。 ome/mc/fin/home/system 模块依然存在但不处理。 ## 注释规范(已写入 MEMORY.md) 1. 所有公共接口和类必须包含 Javadoc 2. 注释语言:中文描述业务背景,技术术语保留英文(如 NPE) 3. 禁止生成 "Gets the value of X" 这类无意义的 getter/setter 注释 4. 复杂算法必须在代码块上方解释核心逻辑 5. 禁止使用 HTML 标签 ## 开发注意事项(已写入 MEMORY.md) 1. **复用优先**:逻辑雷同时提取公共方法复用 2. **业务表仅存物料 id**:物料相关字段仅存 `material_id`,其他数据通过关联查询填充 3. **填充在 Service 层**:关联数据填充逻辑统一在 Service 层处理 4. **首个版本直接改 DDL**:脚本直接修改建表语句,不需要写 ALTER TABLE 5. **字典优先用 DictService**:字典取值优先使用 DictService 公共方法 ## Git 提交约定 - **commit 跳过 application.yml** — `git add . -- ':!application.yml'` 排除,防止泄露配置 - **commit 后不要 push** — 只做本地 commit,不推送远程仓库 ## 库存业务表字段清理 **背景:** 业务表只存物料 ID,物料编码/名称/规格/单位通过关联查询填充。 **DDL 改动(50-inventory.sql):** - `im_stock` — 移除 material_code, material_name, spec, unit - `im_stock_transaction` — 移除 material_code, material_name - `im_inbound_item` — 移除 material_code, material_name, spec, unit - `im_outbound_item` — 移除 material_code, material_name, spec, unit **实体类:** Stock, StockTransaction, InboundItem, OutboundItem — 移除对应字段 **请求 DTO:** InboundItemSaveReqDTO, OutboundItemSaveReqDTO, StocktakeItemSaveReqDTO, QuickStocktakeItemDTO — 只保留 materialId **接口改动:** - StockTransactionService.recordTransaction() — 移除 materialCode/materialName 参数 - MaterialService — 新增 searchMaterialIdsByKeyword() 方法 **Service 改动:** - StockTransactionServiceImpl — keyword 搜索改为两步(先查物料表匹配 ID);DTO 填充从 materialInfoMap 读取 - StockServiceImpl — 同上 - InboundServiceImpl — saveItems 不 set 冗余字段;recordTransaction 去参;history 从 material 填充 - OutboundServiceImpl — 同上 - MaterialServiceImpl — 实现 searchMaterialIdsByKeyword() **编译验证:** `mvn compile -q` ✅ ## 物料信息填充公共服务 (MaterialFillService) ### 核心设计决策 1. **实体设计原则** - `InboundItem` 和 `OutboundItem` 只存储 `materialId` - 不存储物料编码、名称、规格、单位等字段 - 防止物料信息变更后历史数据不一致 2. **批量查询模式** ```java List materialIds = items.stream().map(Item::getMaterialId).collect(Collectors.toList()); Map materialInfoMap = materialFillService.getMaterialInfoMap(materialIds); ``` - 避免 N+1 查询问题 - 一次查询所有物料信息,性能更优 3. **Service 层填充** - 所有物料信息填充逻辑在 Service 层处理 - Controller 只负责返回 DTO - 不放在 Mapper 或 Controller 层 ### 修改的 Service 类(共 6 个) | Service | 方法 | 用途 | |---------|------|------| | InboundServiceImpl | executeInbound(), cancel() | 入库/冲销时记录流水 | | OutboundServiceImpl | executeOutbound(), cancel() | 出库/冲销时记录流水 | | FinAccountsAutoServiceImpl | createApFromInbound(), cancelApFromInbound() | 采购应付联动 | | FinAccountsAutoServiceImpl | createArFromOutbound(), cancelArFromOutbound() | 销售应收联动 | | HomeServiceImpl | getLatestNews() | 首页动态显示 | | DeliveryServiceImpl | confirmOutbound() | 发货确认 | ### 物料类型字典 - Dict Type: `material_type` - 值:`P`-原材料,`SA`-半成品,`FG`-成品 - SQL 脚本:`scripts/postgresql/22-material-type-dict.sql` ### Git 状态 - 所有修改已 commit(13+ commits) - **禁止 push** - 用户明确要求 - 分支:master ## 物料选择器(Material Selector) 新增物料选择器功能,用于入库/出库操作时弹窗选物料。 ### 接口 | 接口 | 位置 | 数据源 | 仓库参数 | |------|------|--------|---------| | `POST /im/inbound/material-selector` | InboundController | md_material | 不需要 | | `POST /im/outbound/material-selector` | OutboundController | im_stock JOIN md_material | 需要 warehouseId | ### 请求参数(MaterialSelectorReqDTO → md.material.dto) - `categoryId` — 0=常用物料,>0=按分类筛选 - `keyword` — 物料编码/名称/规格关键字 - `warehouseId` — 出库场景需要 - 继承 `PageQuery`(pageNum, pageSize) ### 常用物料 TOP10 逻辑 - **口径:** 近 30 天出入库明细条目数最多的 10 个物料 - **入库:** `im_inbound_item` GROUP BY material_id ORDER BY COUNT(*) DESC LIMIT 10 - **出库:** `im_outbound_item` 同上 - **SQL 放 Mapper XML**(方便后续扩展) ### 分类筛选逻辑 - **入库:** MyBatis Plus LambdaQueryWrapper 查 `md_material` 表,按 `category_id` + `keyword` 分页 - **出库:** XML 联表 `im_stock JOIN md_material`,按 `category_id` + `keyword` + `warehouseId` 分页,只查库存 > 0 的数据 ### 响应 - 复用 `StockPageDTO`(im.stock.dto) - 物料编码/名称/规格/单位/分类等从 `MaterialFillService` 填充 ### 新增文件(7个) | 文件 | 路径 | |------|------| | MaterialSelectorReqDTO | md.material.dto | | MaterialSelectorService | md.material.service | | MaterialSelectorServiceImpl | md.material.service.impl | | InboundItemMapper.xml | resources/mapper/im/ | | OutboundItemMapper.xml | resources/mapper/im/ | | StockMapper.xml | resources/mapper/im/ | | | | ### 修改文件(5个) | 文件 | 改动 | |------|------| | InboundItemMapper.java | + selectRecentMaterialIds() | | OutboundItemMapper.java | + selectRecentMaterialIds() | | StockMapper.java | + selectStockPage() / countStockPage() | | InboundController.java | + /material-selector 端点 | | OutboundController.java | + /material-selector 端点 | ## 历史单据创建人名称 ### 改动 - **InboundHistoryItemDTO** — 新增 `creatorName` 字段 - **OutboundHistoryItemDTO** — 新增 `creatorName` 字段 - **InboundServiceImpl.listHistory()** — 收集 creators → 批量查 sys_user(username→nickname) → 填充 - **OutboundServiceImpl.listHistory()** — 同上 ### 逻辑 1. 从单据列表收集所有 `creator`(用户名) 2. `userService.lambdaQuery().in(User::getUsername, creators).list()` 3. 转 `Map`(username → nickname) 4. 逐行 `dto.setCreatorName()` 5. 一次 SQL,不 N+1 ## 实体名称字段清理(SupplierName/CustomerName/WarehouseName) ### 背景 单据实体(Inbound、Outbound、PurchaseOrder、SalesOrder、Delivery)及其 Item 实体中,之前还存储了供应商名称、客户名称、仓库名称等冗余字段。本次清理全部移除,只存 ID,显示名称通过 master-data 查询填充。 ### 涉及实体 - **Inbound** — 移除 `supplierName`, `warehouseName` - **Outbound** — 移除 `customerName`, `warehouseName` - **PurchaseOrder** — 移除 `supplierName` - **PurchaseOrderItem** — 移除了 `materialCode`, `materialName`, `spec`, `unit`(之前已完成) - **SalesOrder** — 移除 `customerName` - **SalesOrderItem** — 移除了物料冗余字段(之前已完成) - **Delivery** — 移除 `customerName` - **DeliveryItem** — 移除了物料冗余字段(之前已完成) ### DTO 改动 - **InboundSaveReqDTO / PurchaseInboundSaveReqDTO / ProductionInboundSaveReqDTO** — 只保留 supplierId/warehouseId,移除 supplierName/warehouseName - **OutboundSaveReqDTO / SalesOutboundSaveReqDTO / MaterialOutboundSaveReqDTO** — 只保留 customerId/warehouseId,移除 customerName/warehouseName - **InboundHistoryItemDTO / OutboundHistoryItemDTO** — **保留** display name 字段(响应 DTO 需要显示),通过 master-data lookup 填充 ### 不做改动 - **Ar.java / Ap.java** — 保留 customerName / supplierName 作为财务历史快照 - **InboundHistoryItemDTO / OutboundHistoryItemDTO** — 保留 name 字段供前端展示 ### 主要 Service 改动 | 文件 | 改动内容 | |------|---------| | **InboundServiceImpl.java** | 注入 WarehouseMapper;移除 entity setSupplierName/setWarehouseName;history DTO 名通过 supplierMapper + warehouseMapper 批量 lookup;autoInboundFromOrder 用 supplierId 取代 supplierName;updatePurchaseOrderAfterInbound 用 supplierId 查询 | | **OutboundServiceImpl.java** | 注入 WarehouseMapper;移除 entity setCustomerName;history DTO 名通过 customerMapper + warehouseMapper 批量 lookup | | **FinAccountsAutoServiceImpl.java** | `createApFromInbound`/`cancelApFromInbound` 使用 supplierMapper 查询 supplierName 填充 Ap;`createArFromOutbound`/`cancelArFromOutbound` 使用 customerMapper 查询 customerName | | **PurchaseOrderServiceImpl.java** | `setSupplierName(reqDTO.getSupplierName())` → `setSupplierId(reqDTO.getSupplierId())` | | **SalesOrderServiceImpl.java** | `setCustomerName(reqDTO.getCustomerName())` → `setCustomerId(reqDTO.getCustomerId())` | | **DeliveryServiceImpl.java** | `setCustomerName(order.getCustomerName())` → `setCustomerId(order.getCustomerId())`;item 用 `materialFillService` 填充物料信息 | ### 控制器查询条件 - PurchaseOrderController / SalesOrderController / DeliveryController — 查询条件从名称改为 ID ### 编译验证 - `mvn clean compile` ✅ — 零错误 ## 集成测试(InboundOutboundIntegrationTest) ### 文件路径 `src/test/java/com/witsoft/erp/im/InboundOutboundIntegrationTest.java` ### 设计 - 使用 `TestRestTemplate`,`webEnvironment = RANDOM_PORT` - `@TestMethodOrder(MethodOrderer.OrderAnnotation.class)` 按顺序执行 - 复用已有的 `Result` 泛型返回包装类 ### 测试覆盖(11 个用例) | 序号 | 端点 | 说明 | |------|------|------| | 1 | POST /erp/system/user/login | 登录获取 Bearer token | | 2 | POST /erp/im/inbound/production | 生产入库(2 行物料) | | 3 | POST /erp/im/inbound/purchase | 采购入库(含单价) | | 4 | POST /erp/im/inbound/history-list | 查询入库历史 | | 5 | POST /erp/im/outbound/material | 领料出库 | | 6 | POST /erp/im/outbound/sales | 销售出库(含单价) | | 7 | POST /erp/im/outbound/history-list | 查询出库历史 | | 8 | POST /erp/im/inbound/auto-from-order | 自动入库(基于采购订单) | | 9 | POST /erp/im/stock/transaction/page | 查询库存流水 | | 10 | DELETE /erp/im/inbound/{id} | 撤销入库单 | | 11 | DELETE /erp/im/outbound/{id} | 撤销出库单 | ### 注意事项 - `application.yml` 中 `server.servlet.context-path: /erp`,所有路径须带 `/erp` 前缀 - 测试需要准备基础数据:物料 ID=3/4、供应商 ID=1、客户 ID=1、仓库 ID=1、采购订单 ID=1 - 当前 `authToken` 未通过 TestRestTemplate 的 basic auth,走的是 token-auth 模式 - 登录密码写的是 `***`,需要按实际数据库密码修改 `TEST_PASSWORD` 常量 ## Controller 集成测试(TestRestTemplate) ### 现有测试文件 | 测试类 | 路径 | 用例数 | 覆盖 Controller | |--------|------|--------|----------------| | InboundOutboundIntegrationTest | `im/InboundOutboundIntegrationTest.java` | 11 | Inbound + Outbound(合并) | | StockControllerTest | `im/stock/StockControllerTest.java` | 17 | StockController | | StocktakeControllerTest | `im/stocktake/StocktakeControllerTest.java` | 17 | StocktakeController | | InboundControllerTest | `im/inbound/InboundControllerTest.java` | 21 | InboundController | | OutboundControllerTest | `im/outbound/OutboundControllerTest.java` | 21 | OutboundController | ### 测试设计模式(所有测试统一) 1. **TestRestTemplate + RANDOM_PORT** — 自动分配端口,避免冲突 2. **@TestMethodOrder(OrderAnnotation.class)** — 按 Order 顺序执行 3. **CONTEXT_PATH = "/erp"** — 手动拼接,因为自动 context-path 处理不可靠 4. **assumeToken() 守卫** — 登录失败后通过 `Assumptions.assumeTrue(authToken != null)` 跳过后续测试,避免 403 5. **密码可配置** — `System.getProperty("test.password", "admin123")`,通过 `-Dtest.password=xxx` 覆盖 6. **url() 辅助方法** — `"http://localhost:" + port + CONTEXT_PATH + path` ### InboundControllerTest(21 用例) | 序号 | 端点 | 验证 | |------|------|------| | 1 | POST /system/user/login | 登录获取 Bearer token | | 2-3 | POST /im/inbound/material-selector | 物料选择器(分类筛选) | | 4-6 | POST /im/inbound | 新增 + 明细为空/类型为空校验边界 | | 7-8 | GET /im/inbound/{id} | 详情 / 不存在返回 null | | 9 | GET /im/inbound/{id}/items | 明细列表 | | 10 | PUT /im/inbound | 修改 | | 11-12 | PUT /im/inbound/{id}/cancel | 撤销 / 重复撤销 400 | | 13-14 | DELETE /im/inbound/{id} | 删除 | | 15-16 | POST /im/inbound/page | 分页(全查/仓库筛选) | | 17 | POST /im/inbound/history-list | 历史明细列表 | | 18 | POST /im/inbound/production | 生产入库 | | 19-20 | POST /im/inbound/purchase | 采购入库 / 供应商为空 400 | | 21 | POST /im/inbound/auto-from-order | 自动入库-订单不存在 400 | ### OutboundControllerTest(21 用例) | 序号 | 端点 | 验证 | |------|------|------| | 1 | POST /system/user/login | 登录 | | 2-3 | POST /im/outbound/material-selector | 物料选择器 | | 4-6 | POST /im/outbound | 新增 + 明细为空/客户为空校验 | | 7-8 | GET /im/outbound/{id} | 详情 / 不存在 | | 9 | GET /im/outbound/{id}/items | 明细 | | 10 | PUT /im/outbound | 修改 | | 11-12 | PUT /im/outbound/{id}/cancel | 撤销 / 重复撤销 400 | | 13-14 | DELETE /im/outbound/{id} | 删除 | | 15-16 | POST /im/outbound/page | 分页(全查/客户筛选) | | 17 | POST /im/outbound/history-list | 历史明细 | | 18-19 | POST /im/outbound/material | 领料出库 / 明细为空 400 | | 20-21 | POST /im/outbound/sales | 销售出库 / 客户为空 400 | ### StockControllerTest(17 用例) 覆盖 StockController 全部 4 个端点: | 序号 | 端点 | 验证 | |------|------|------| | 1 | POST /system/user/login | 登录 | | 2-6 | POST /im/stock/page | 空参数、仓库筛选、关键字、无匹配关键字、自定义分页 | | 7-11 | POST /im/stock/list | 空、仓库、关键字、预警筛选、无匹配关键字 | | 12-13 | GET /im/stock/{materialId} | 存在/不存在 | | 14-16 | PUT /im/stock/safe-qty | 正常设置、物料不存在 400、参数校验 | | 17 | POST /im/stock/page | DTO 字段完整性验证 | ### StocktakeControllerTest(17 用例) 覆盖 StocktakeController 全部 10 个端点: | 序号 | 端点 | 验证 | |------|------|------| | 1 | POST /system/user/login | 登录 | | 2-3 | POST /im/stocktake | 新增(草稿)/ 明细为空 400 | | 4-5 | GET /im/stocktake/{id} | 详情 / 不存在 | | 6 | GET /im/stocktake/{id}/items | 明细(含物料信息) | | 7 | PUT /im/stocktake | 修改 | | 8-9 | PUT /im/stocktake/{id}/audit | 审核 / 重复审核 400 | | 10-11 | PUT /im/stocktake/{id}/cancel | 作废 | | 12 | DELETE /im/stocktake/{id} | 删除 | | 13 | POST /im/stocktake/quick-save | 快速盘点(创建即审核) | | 14-15 | POST /im/stocktake/page | 分页(筛选/空 body) | | 16-17 | POST /im/stocktake/history-list | 历史明细(空条件) | ### 已知问题 - **InboundController.getPage() 有两个 @RequestBody** — 测试用 Map 代理 - **OutboundController.getPage() 同理** - **StocktakeController.getPage() 同理** - 密码默认 `admin123`,通过 `-Dtest.password=xxx` 配置 - IntelliJ 解析 `junit-platform-launcher:1.8.2` 失败,已在 pom.xml 中添加依赖