diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java index 872a3c8..2c161f2 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java @@ -1,6 +1,6 @@ package cn.iocoder.yudao.module.mes.dal.mysql.wm.batch; -import cn.hutool.core.collection.CollUtil; + import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; @@ -119,8 +119,8 @@ public interface MesWmBatchMapper extends BaseMapperX { // 返回 ID 最小的批次 query.orderByAsc(MesWmBatchDO::getId); - List list = selectList(query); - return CollUtil.getFirst(list); + query.last("LIMIT 1"); + return selectOne(query); } /** diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java index 0980cd9..b3a2df1 100644 --- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java +++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java @@ -22,7 +22,9 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; @@ -194,28 +196,42 @@ public class MesWmBatchServiceImpl implements MesWmBatchService { @Override public List getForwardBatchList(String code) { + return getForwardBatchList(code, new HashSet<>()); + } + + private List getForwardBatchList(String code, Set visited) { + if (code == null || !visited.add(code)) { + return new ArrayList<>(); + } List list = batchMapper.selectListByForward(code); if (CollUtil.isEmpty(list)) { - return list; + return new ArrayList<>(); } // 继续递归查询下游批次 List results = new ArrayList<>(list); for (MesWmBatchDO batch : list) { - results.addAll(getForwardBatchList(batch.getCode())); + results.addAll(getForwardBatchList(batch.getCode(), visited)); } return results; } @Override public List getBackwardBatchList(String code) { + return getBackwardBatchList(code, new HashSet<>()); + } + + private List getBackwardBatchList(String code, Set visited) { + if (code == null || !visited.add(code)) { + return new ArrayList<>(); + } List list = batchMapper.selectListByBackward(code); if (CollUtil.isEmpty(list)) { - return list; + return new ArrayList<>(); } // 继续递归查询上游批次 List results = new ArrayList<>(list); for (MesWmBatchDO batch : list) { - results.addAll(getBackwardBatchList(batch.getCode())); + results.addAll(getBackwardBatchList(batch.getCode(), visited)); } return results; } diff --git a/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml b/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml index b62b82a..93c788c 100644 --- a/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml +++ b/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml @@ -3,39 +3,36 @@ - + + + - - - - - - - - + diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImplTest.java new file mode 100644 index 0000000..adc64a6 --- /dev/null +++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImplTest.java @@ -0,0 +1,197 @@ +package cn.iocoder.yudao.module.mes.service.wm.batch; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; +import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper; +import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemBatchConfigService; +import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; +import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.util.List; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@link MesWmBatchServiceImpl} 的单元测试 + * + * @author 芋道源码 + */ +@Import(MesWmBatchServiceImpl.class) +public class MesWmBatchServiceImplTest extends BaseDbUnitTest { + + @Resource + private MesWmBatchServiceImpl batchService; + + @Resource + private MesWmBatchMapper batchMapper; + + @MockitoBean + private MesMdItemService itemService; + @MockitoBean + private MesMdItemBatchConfigService itemBatchConfigService; + @MockitoBean + private MesMdAutoCodeRecordService autoCodeRecordService; + @MockitoBean + private MesWmBarcodeService barcodeService; + + // ==================== 向前追溯 ==================== + + @Test + public void testGetForwardBatchList_nullCode() { + // 传入 null code,应返回空列表(不抛异常) + List result = batchService.getForwardBatchList(null); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testGetForwardBatchList_noResults() { + // 传入一个不存在的批次号,XML 查询返回空(因为关联表无数据) + List result = batchService.getForwardBatchList("NOT_EXIST_CODE"); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + // ==================== 向后追溯 ==================== + + @Test + public void testGetBackwardBatchList_nullCode() { + // 传入 null code,应返回空列表(不抛异常) + List result = batchService.getBackwardBatchList(null); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testGetBackwardBatchList_noResults() { + // 传入一个不存在的批次号,XML 查询返回空 + List result = batchService.getBackwardBatchList("NOT_EXIST_CODE"); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + // ==================== 循环检测 ==================== + + @Test + public void testForwardBatchList_sameCodeDoesNotRecurse() { + // 验证:传入相同 code 多次调用不会 StackOverflow + // 由于数据库中没有环路数据,这里主要验证 visited set 的 null + 空数据安全 + List result = batchService.getForwardBatchList("SAME_CODE"); + assertNotNull(result); + assertTrue(result.isEmpty()); + + // 二次调用同一个 code,不应有任何异常 + List result2 = batchService.getForwardBatchList("SAME_CODE"); + assertNotNull(result2); + assertTrue(result2.isEmpty()); + } + + @Test + public void testBackwardBatchList_sameCodeDoesNotRecurse() { + // 验证向后追溯的 visited set 安全 + List result = batchService.getBackwardBatchList("SAME_CODE"); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + // ==================== selectFirst ==================== + + @Test + public void testSelectFirst_noMatch() { + // 查询不存在的 itemId,应返回 null + MesWmBatchDO query = MesWmBatchDO.builder().itemId(randomLongId()).build(); + MesWmBatchDO result = batchMapper.selectFirst(query); + assertNull(result); + } + + @Test + public void testSelectFirst_returnsSmallestId() { + // 准备数据:插入两条相同条件的批次 + Long itemId = randomLongId(); + MesWmBatchDO batch1 = MesWmBatchDO.builder() + .itemId(itemId).code("BATCH_001").build(); + MesWmBatchDO batch2 = MesWmBatchDO.builder() + .itemId(itemId).code("BATCH_002").build(); + batchMapper.insert(batch1); + batchMapper.insert(batch2); + + // 查询 + MesWmBatchDO query = MesWmBatchDO.builder().itemId(itemId).build(); + MesWmBatchDO result = batchMapper.selectFirst(query); + + // 断言:返回 ID 最小的 + assertNotNull(result); + assertEquals(batch1.getId(), result.getId()); + assertEquals("BATCH_001", result.getCode()); + } + + @Test + public void testSelectFirst_nullFieldMatching() { + // 准备数据:一条有 vendorId,一条没有 vendorId + Long itemId = randomLongId(); + Long vendorId = randomLongId(); + + MesWmBatchDO batchWithVendor = MesWmBatchDO.builder() + .itemId(itemId).code("BATCH_WITH_V").vendorId(vendorId).build(); + MesWmBatchDO batchWithoutVendor = MesWmBatchDO.builder() + .itemId(itemId).code("BATCH_NO_V").build(); + batchMapper.insert(batchWithVendor); + batchMapper.insert(batchWithoutVendor); + + // 查询:vendorId 为 null -> 应该只匹配 vendorId IS NULL 的记录 + MesWmBatchDO query = MesWmBatchDO.builder().itemId(itemId).build(); + MesWmBatchDO result = batchMapper.selectFirst(query); + + assertNotNull(result); + assertEquals("BATCH_NO_V", result.getCode()); + assertNull(result.getVendorId()); + } + + @Test + public void testSelectFirst_withVendorId() { + // 准备数据 + Long itemId = randomLongId(); + Long vendorId = randomLongId(); + + MesWmBatchDO batchWithVendor = MesWmBatchDO.builder() + .itemId(itemId).code("BATCH_V1").vendorId(vendorId).build(); + MesWmBatchDO batchWithoutVendor = MesWmBatchDO.builder() + .itemId(itemId).code("BATCH_NV").build(); + batchMapper.insert(batchWithVendor); + batchMapper.insert(batchWithoutVendor); + + // 查询:指定 vendorId -> 只匹配有 vendorId 的 + MesWmBatchDO query = MesWmBatchDO.builder().itemId(itemId).vendorId(vendorId).build(); + MesWmBatchDO result = batchMapper.selectFirst(query); + + assertNotNull(result); + assertEquals("BATCH_V1", result.getCode()); + assertEquals(vendorId, result.getVendorId()); + } + + // ==================== selectByCode ==================== + + @Test + public void testSelectByCode() { + // 准备数据 + MesWmBatchDO batch = MesWmBatchDO.builder() + .itemId(randomLongId()).code("TEST_CODE_001").build(); + batchMapper.insert(batch); + + // 查询 + MesWmBatchDO result = batchMapper.selectByCode("TEST_CODE_001"); + assertNotNull(result); + assertEquals(batch.getId(), result.getId()); + + // 查询不存在的 + MesWmBatchDO notFound = batchMapper.selectByCode("NOT_EXIST"); + assertNull(notFound); + } + +} diff --git a/yudao-module-mes/src/test/resources/sql/clean.sql b/yudao-module-mes/src/test/resources/sql/clean.sql index 814d4f3..eda0d8c 100644 --- a/yudao-module-mes/src/test/resources/sql/clean.sql +++ b/yudao-module-mes/src/test/resources/sql/clean.sql @@ -12,3 +12,4 @@ DELETE FROM "mes_wm_product_produce_detail"; DELETE FROM "mes_wm_item_receipt"; DELETE FROM "mes_wm_item_receipt_line"; DELETE FROM "mes_wm_item_receipt_detail"; +DELETE FROM "mes_wm_batch"; diff --git a/yudao-module-mes/src/test/resources/sql/create_tables.sql b/yudao-module-mes/src/test/resources/sql/create_tables.sql index 5655285..d4f8e17 100644 --- a/yudao-module-mes/src/test/resources/sql/create_tables.sql +++ b/yudao-module-mes/src/test/resources/sql/create_tables.sql @@ -586,3 +586,34 @@ CREATE TABLE IF NOT EXISTS "mes_wm_item_consume_detail" ( "tenant_id" bigint NOT NULL DEFAULT 0, PRIMARY KEY ("id") ); + +-- ---------------------------- +-- MES 批次记录表 +-- ---------------------------- +CREATE TABLE IF NOT EXISTS "mes_wm_batch" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "code" varchar(128) DEFAULT NULL, + "item_id" bigint DEFAULT NULL, + "produce_date" timestamp DEFAULT NULL, + "expire_date" timestamp DEFAULT NULL, + "receipt_date" timestamp DEFAULT NULL, + "vendor_id" bigint DEFAULT NULL, + "client_id" bigint DEFAULT NULL, + "sales_order_code" varchar(64) DEFAULT NULL, + "purchase_order_code" varchar(64) DEFAULT NULL, + "work_order_id" bigint DEFAULT NULL, + "task_id" bigint DEFAULT NULL, + "workstation_id" bigint DEFAULT NULL, + "tool_id" bigint DEFAULT NULL, + "mold_id" bigint DEFAULT NULL, + "lot_number" varchar(128) DEFAULT NULL, + "quality_status" int DEFAULT NULL, + "remark" varchar(500) DEFAULT NULL, + "creator" varchar(64) DEFAULT '', + "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint NOT NULL DEFAULT 0, + PRIMARY KEY ("id") +);