package cn.iocoder.yudao.module.mes.service.md.item; import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.bom.MesMdProductBomSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdProductBomDO; import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdProductBomMapper; import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportCommitReqVO; import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportPreflightReqVO; import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportPreflightRespVO; import cn.iocoder.yudao.module.system.api.agent.imports.AgentImportRowReceipt; import cn.iocoder.yudao.module.system.framework.agent.AgentPrincipal; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; import java.math.BigDecimal; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; class MesBomImportAdapterTest { private static final AgentPrincipal PRINCIPAL = new AgentPrincipal(1L, "live"); private MesBomImportAdapter adapter; private MesMdItemService itemService; private MesMdProductBomService productBomService; private MesMdProductBomMapper productBomMapper; @BeforeEach void setUp() { adapter = new MesBomImportAdapter(); itemService = mock(MesMdItemService.class); productBomService = mock(MesMdProductBomService.class); productBomMapper = mock(MesMdProductBomMapper.class); ReflectionTestUtils.setField(adapter, "itemService", itemService); ReflectionTestUtils.setField(adapter, "productBomService", productBomService); ReflectionTestUtils.setField(adapter, "productBomMapper", productBomMapper); when(itemService.getItemByCode("P")).thenReturn(item(1L, "P")); when(itemService.getItemByCode("C")).thenReturn(item(2L, "C")); when(itemService.getItemByCode("A")).thenReturn(item(3L, "A")); when(itemService.getItemByCode("X")).thenReturn(null); when(productBomMapper.selectAll()).thenReturn(List.of()); } @Test void preflightCreatesWhenItemsAreSynced() { AgentImportPreflightRespVO plan = adapter.preflight(PRINCIPAL, request(row("r1", "P", "C", "2"))); assertEquals("CREATE", plan.getRows().get(0).getAction()); assertEquals(1, plan.getSummary().getCreate()); assertTrue(plan.getCanCommit()); assertNotNull(plan.getPreflightHash()); } @Test void existingCombinationIsSkippedWithWarning() { when(productBomMapper.selectAll()).thenReturn(List.of(bom(50L, 1L, 2L))); AgentImportPreflightRespVO plan = adapter.preflight(PRINCIPAL, request(row("r1", "P", "C", "2"))); assertEquals("SKIP", plan.getRows().get(0).getAction()); assertEquals(50L, plan.getRows().get(0).getTargetId()); assertEquals(1, plan.getSummary().getSkip()); assertTrue(plan.getRows().get(0).getWarnings().stream() .anyMatch(item -> "BOM_EXISTS".equals(item.getCode()))); } @Test void unsyncedComponentBlocksRow() { AgentImportPreflightRespVO plan = adapter.preflight(PRINCIPAL, request(row("r1", "P", "X", "1"))); assertEquals("BLOCKED", plan.getRows().get(0).getAction()); assertTrue(plan.getRows().get(0).getBlockers().stream() .anyMatch(item -> "MES_ITEM_NOT_SYNCED".equals(item.getCode()))); assertFalse(plan.getCanCommit()); } @Test void selfReferenceBlocksRow() { AgentImportPreflightRespVO plan = adapter.preflight(PRINCIPAL, request(row("r1", "P", "P", "1"))); assertEquals("BLOCKED", plan.getRows().get(0).getAction()); assertTrue(plan.getRows().get(0).getBlockers().stream() .anyMatch(item -> "BOM_SELF_REFERENCE".equals(item.getCode()))); } @Test void duplicateCombinationInFileBlocksSecondRow() { AgentImportPreflightRespVO plan = adapter.preflight(PRINCIPAL, request( row("r1", "P", "C", "1"), row("r2", "P", "C", "2"))); assertEquals("CREATE", plan.getRows().get(0).getAction()); assertEquals("BLOCKED", plan.getRows().get(1).getAction()); assertTrue(plan.getRows().get(1).getBlockers().stream() .anyMatch(item -> "DUPLICATE_IN_FILE".equals(item.getCode()))); } @Test void cycleAgainstExistingGraphBlocksRow() { when(productBomMapper.selectAll()).thenReturn(List.of(bom(60L, 3L, 1L))); AgentImportPreflightRespVO plan = adapter.preflight(PRINCIPAL, request(row("r1", "P", "A", "1"))); assertEquals("BLOCKED", plan.getRows().get(0).getAction()); assertTrue(plan.getRows().get(0).getBlockers().stream() .anyMatch(item -> "BOM_CYCLE".equals(item.getCode()))); } @Test void invalidQuantityBlocksRow() { AgentImportPreflightRespVO plan = adapter.preflight(PRINCIPAL, request(row("r1", "P", "C", "0"))); assertEquals("BLOCKED", plan.getRows().get(0).getAction()); assertTrue(plan.getRows().get(0).getBlockers().stream() .anyMatch(item -> "INVALID_QUANTITY".equals(item.getCode()))); } @Test void commitCreatesBomWithResolvedItemIds() { when(productBomService.createProductBom(any())).thenReturn(88L); AgentImportRowReceipt receipt = adapter.commitRow(PRINCIPAL, new AgentImportCommitReqVO(), AgentImportPreflightRespVO.Row.builder().candidateId("r1").action("CREATE").build(), row("r1", "P", "C", "2")); assertEquals(88L, receipt.targetId()); assertEquals("P", receipt.code()); verify(productBomService).createProductBom(any(MesMdProductBomSaveReqVO.class)); } private static MesMdItemDO item(Long id, String code) { MesMdItemDO item = new MesMdItemDO(); item.setId(id); item.setCode(code); return item; } private static MesMdProductBomDO bom(Long id, Long itemId, Long bomItemId) { MesMdProductBomDO bom = new MesMdProductBomDO(); bom.setId(id); bom.setItemId(itemId); bom.setBomItemId(bomItemId); bom.setQuantity(BigDecimal.ONE); return bom; } private static AgentImportPreflightReqVO request(AgentImportPreflightReqVO.Row... rows) { AgentImportPreflightReqVO request = new AgentImportPreflightReqVO(); request.setAttachmentId(1L); request.setSourceSha256("a".repeat(64)); request.setProposalRevision("0"); request.setRows(List.of(rows)); return request; } private static AgentImportPreflightReqVO.Row row(String candidateId, String parent, String component, String quantity) { Map values = new LinkedHashMap<>(); values.put("parentCode", parent); values.put("componentCode", component); values.put("quantity", quantity); AgentImportPreflightReqVO.Row row = new AgentImportPreflightReqVO.Row(); row.setCandidateId(candidateId); row.setRowNo(1); row.setValues(values); return row; } }