From 7eec9887448c484e3ba85407fc9af185ee1b696e Mon Sep 17 00:00:00 2001 From: zichun Date: Wed, 29 Apr 2026 17:07:43 +0800 Subject: [PATCH] feat(mod): module create dto + service happy path REQ-MOD-001 --- backend/src/main/java/com/xly/erp/module/mod/dto/CreateModuleDTO.java | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ backend/src/main/java/com/xly/erp/module/mod/service/ModuleService.java | 7 +++++++ backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 0 deletions(-) create mode 100644 backend/src/main/java/com/xly/erp/module/mod/dto/CreateModuleDTO.java create mode 100644 backend/src/main/java/com/xly/erp/module/mod/service/ModuleService.java create mode 100644 backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java create mode 100644 backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java diff --git a/backend/src/main/java/com/xly/erp/module/mod/dto/CreateModuleDTO.java b/backend/src/main/java/com/xly/erp/module/mod/dto/CreateModuleDTO.java new file mode 100644 index 0000000..2345a03 --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/mod/dto/CreateModuleDTO.java @@ -0,0 +1,49 @@ +package com.xly.erp.module.mod.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public class CreateModuleDTO { + + @NotBlank + private String sDisplayType; + + @NotBlank + @Size(max = 100) + private String sProcedureName; + + @NotBlank + @Size(max = 50) + private String sModuleType; + + @NotBlank + @Size(max = 50) + private String sManageDeptEn; + + private Boolean bShowPermission; + + @NotBlank + @Size(max = 100) + private String sModuleNameZh; + + private Integer iParentId; + + private Integer iSortOrder; + + public String getSDisplayType() { return sDisplayType; } + public void setSDisplayType(String sDisplayType) { this.sDisplayType = sDisplayType; } + public String getSProcedureName() { return sProcedureName; } + public void setSProcedureName(String sProcedureName) { this.sProcedureName = sProcedureName; } + public String getSModuleType() { return sModuleType; } + public void setSModuleType(String sModuleType) { this.sModuleType = sModuleType; } + public String getSManageDeptEn() { return sManageDeptEn; } + public void setSManageDeptEn(String sManageDeptEn) { this.sManageDeptEn = sManageDeptEn; } + public Boolean getBShowPermission() { return bShowPermission; } + public void setBShowPermission(Boolean bShowPermission) { this.bShowPermission = bShowPermission; } + public String getSModuleNameZh() { return sModuleNameZh; } + public void setSModuleNameZh(String sModuleNameZh) { this.sModuleNameZh = sModuleNameZh; } + public Integer getIParentId() { return iParentId; } + public void setIParentId(Integer iParentId) { this.iParentId = iParentId; } + public Integer getISortOrder() { return iSortOrder; } + public void setISortOrder(Integer iSortOrder) { this.iSortOrder = iSortOrder; } +} diff --git a/backend/src/main/java/com/xly/erp/module/mod/service/ModuleService.java b/backend/src/main/java/com/xly/erp/module/mod/service/ModuleService.java new file mode 100644 index 0000000..19913f3 --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/mod/service/ModuleService.java @@ -0,0 +1,7 @@ +package com.xly.erp.module.mod.service; + +import com.xly.erp.module.mod.dto.CreateModuleDTO; + +public interface ModuleService { + Integer create(CreateModuleDTO dto); +} diff --git a/backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java b/backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java new file mode 100644 index 0000000..e84a888 --- /dev/null +++ b/backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java @@ -0,0 +1,57 @@ +package com.xly.erp.module.mod.service.impl; + +import com.xly.erp.common.config.StubSecurityProperties; +import com.xly.erp.common.config.TenantProperties; +import com.xly.erp.common.exception.BizException; +import com.xly.erp.common.security.SecurityContextHelper; +import com.xly.erp.module.mod.dto.CreateModuleDTO; +import com.xly.erp.module.mod.entity.Module; +import com.xly.erp.module.mod.mapper.ModuleMapper; +import com.xly.erp.module.mod.service.ModuleService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +@Transactional(rollbackFor = Exception.class) +public class ModuleServiceImpl implements ModuleService { + + private final ModuleMapper moduleMapper; + private final TenantProperties tenant; + private final StubSecurityProperties stub; + + public ModuleServiceImpl(ModuleMapper moduleMapper, + TenantProperties tenant, + StubSecurityProperties stub) { + this.moduleMapper = moduleMapper; + this.tenant = tenant; + this.stub = stub; + } + + @Override + public Integer create(CreateModuleDTO dto) { + if (dto.getIParentId() != null && !moduleMapper.existsActiveById(dto.getIParentId())) { + throw new BizException(40021, "父模块不存在或已删除"); + } + + Module m = new Module(); + m.setSBrandsId(tenant.getBrandsId()); + m.setSSubsidiaryId(tenant.getSubsidiaryId()); + m.setTCreateDate(LocalDateTime.now()); + m.setSDisplayType(dto.getSDisplayType()); + m.setSProcedureName(dto.getSProcedureName()); + m.setSModuleType(dto.getSModuleType()); + m.setSManageDeptEn(dto.getSManageDeptEn()); + m.setBShowPermission(dto.getBShowPermission() != null ? dto.getBShowPermission() : false); + m.setSModuleNameZh(dto.getSModuleNameZh()); + m.setIParentId(dto.getIParentId()); + m.setISortOrder(dto.getISortOrder() != null ? dto.getISortOrder() : 0); + String authedUserNo = SecurityContextHelper.currentUserNo(); + m.setSCreatedBy(authedUserNo != null ? authedUserNo : stub.getStubUserNo()); + m.setBDeleted(false); + + moduleMapper.insert(m); + return m.getIIncrement(); + } +} diff --git a/backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java b/backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java new file mode 100644 index 0000000..58c9bb1 --- /dev/null +++ b/backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java @@ -0,0 +1,94 @@ +package com.xly.erp.module.mod.service; + +import com.xly.erp.common.config.StubSecurityProperties; +import com.xly.erp.common.config.TenantProperties; +import com.xly.erp.common.exception.BizException; +import com.xly.erp.module.mod.dto.CreateModuleDTO; +import com.xly.erp.module.mod.entity.Module; +import com.xly.erp.module.mod.mapper.ModuleMapper; +import com.xly.erp.module.mod.service.impl.ModuleServiceImpl; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ModuleServiceImplTest { + + private ModuleMapper moduleMapper; + private ModuleServiceImpl service; + + @BeforeEach + void setUp() { + moduleMapper = mock(ModuleMapper.class); + TenantProperties tenant = new TenantProperties(); + tenant.setBrandsId("XLY"); + tenant.setSubsidiaryId("XLY"); + StubSecurityProperties stub = new StubSecurityProperties(); + stub.setStubUserNo("STUB_ADMIN"); + service = new ModuleServiceImpl(moduleMapper, tenant, stub); + + lenient().when(moduleMapper.insert(any(Module.class))).thenAnswer(inv -> { + Module m = inv.getArgument(0); + m.setIIncrement(99); + return 1; + }); + } + + @AfterEach + void clearContext() { + SecurityContextHolder.clearContext(); + } + + @Test + void createWithValidDto_persistsWithStandardCols() { + CreateModuleDTO dto = baseDto(); + + Integer id = service.create(dto); + + assertThat(id).isEqualTo(99); + ArgumentCaptor captor = ArgumentCaptor.forClass(Module.class); + verify(moduleMapper, times(1)).insert(captor.capture()); + Module saved = captor.getValue(); + assertThat(saved.getSBrandsId()).isEqualTo("XLY"); + assertThat(saved.getSSubsidiaryId()).isEqualTo("XLY"); + assertThat(saved.getTCreateDate()).isNotNull(); + assertThat(saved.getSCreatedBy()).isEqualTo("STUB_ADMIN"); + assertThat(saved.getBDeleted()).isFalse(); + assertThat(saved.getBShowPermission()).isFalse(); + assertThat(saved.getISortOrder()).isZero(); + verify(moduleMapper, never()).findActiveFlagById(any()); + } + + @Test + void createWithParentNotFound_throws40021() { + CreateModuleDTO dto = baseDto(); + dto.setIParentId(42); + when(moduleMapper.findActiveFlagById(42)).thenReturn(null); + + assertThatThrownBy(() -> service.create(dto)) + .isInstanceOf(BizException.class) + .hasFieldOrPropertyWithValue("code", 40021); + verify(moduleMapper, never()).insert(any(Module.class)); + } + + private CreateModuleDTO baseDto() { + CreateModuleDTO dto = new CreateModuleDTO(); + dto.setSDisplayType("手机端"); + dto.setSProcedureName("sp_test_unit"); + dto.setSModuleType("业务模块"); + dto.setSManageDeptEn("IT"); + dto.setSModuleNameZh("单测模块"); + return dto; + } +} -- libgit2 0.22.2