Commit 74a4d485b7c13a35ed8427c6ceb037b5b386e032

Authored by zichun
1 parent 4a1e616f

feat(mod): create module service REQ-MOD-001

backend/src/main/java/com/xly/erp/module/mod/service/ModuleService.java 0 → 100644
  1 +package com.xly.erp.module.mod.service;
  2 +
  3 +import com.xly.erp.module.mod.dto.ModuleCreateDTO;
  4 +import com.xly.erp.module.mod.vo.ModuleVO;
  5 +
  6 +public interface ModuleService {
  7 + ModuleVO create(ModuleCreateDTO dto);
  8 +}
backend/src/main/java/com/xly/erp/module/mod/service/impl/ModuleServiceImpl.java 0 → 100644
  1 +package com.xly.erp.module.mod.service.impl;
  2 +
  3 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4 +import com.xly.erp.common.exception.BizException;
  5 +import com.xly.erp.common.response.ErrorCode;
  6 +import com.xly.erp.module.mod.dto.ModuleCreateDTO;
  7 +import com.xly.erp.module.mod.entity.ModuleEntity;
  8 +import com.xly.erp.module.mod.mapper.ModuleMapper;
  9 +import com.xly.erp.module.mod.service.ModuleService;
  10 +import com.xly.erp.module.mod.vo.ModuleVO;
  11 +import lombok.RequiredArgsConstructor;
  12 +import org.springframework.dao.DuplicateKeyException;
  13 +import org.springframework.stereotype.Service;
  14 +import org.springframework.transaction.annotation.Transactional;
  15 +
  16 +import java.time.LocalDateTime;
  17 +
  18 +@Service
  19 +@RequiredArgsConstructor
  20 +public class ModuleServiceImpl implements ModuleService {
  21 +
  22 + private final ModuleMapper moduleMapper;
  23 +
  24 + @Override
  25 + @Transactional(rollbackFor = Exception.class)
  26 + public ModuleVO create(ModuleCreateDTO dto) {
  27 + // 1. 父模块校验(仅当 iParentId 非空)
  28 + if (dto.getIParentId() != null) {
  29 + ModuleEntity parent = moduleMapper.selectById(dto.getIParentId());
  30 + if (parent == null || Boolean.TRUE.equals(parent.getBDeleted())) {
  31 + throw new BizException(ErrorCode.MOD_PARENT_NOT_FOUND);
  32 + }
  33 + }
  34 +
  35 + // 2. sProcedureName 唯一性预检(未软删除范围)
  36 + Long exist = moduleMapper.selectCount(
  37 + new LambdaQueryWrapper<ModuleEntity>()
  38 + .eq(ModuleEntity::getSProcedureName, dto.getSProcedureName())
  39 + .eq(ModuleEntity::getBDeleted, false));
  40 + if (exist != null && exist > 0L) {
  41 + throw new BizException(ErrorCode.MOD_PROC_NAME_DUP);
  42 + }
  43 +
  44 + // 3. 构造 entity
  45 + ModuleEntity e = new ModuleEntity();
  46 + e.setSDisplayType(dto.getSDisplayType());
  47 + e.setSProcedureName(dto.getSProcedureName());
  48 + e.setSModuleType(dto.getSModuleType());
  49 + e.setSManageDeptEn(dto.getSManageDeptEn());
  50 + e.setBShowPermission(dto.getBShowPermission() != null ? dto.getBShowPermission() : Boolean.FALSE);
  51 + e.setSModuleNameZh(dto.getSModuleNameZh());
  52 + e.setIParentId(dto.getIParentId());
  53 + e.setISortOrder(dto.getISortOrder() != null ? dto.getISortOrder() : 0);
  54 + e.setTCreateDate(LocalDateTime.now());
  55 + e.setBDeleted(Boolean.FALSE);
  56 + // sId / sBrandsId / sSubsidiaryId / sCreatedBy / tDeletedDate / sDeletedBy 留 null(REQ-USR-004 后由登录上下文 / 多租户上下文回填)
  57 +
  58 + // 4. 插入;并发下唯一约束兜底
  59 + try {
  60 + moduleMapper.insert(e);
  61 + } catch (DuplicateKeyException dup) {
  62 + throw new BizException(ErrorCode.MOD_PROC_NAME_DUP);
  63 + }
  64 +
  65 + // 5. 返回 VO
  66 + return ModuleVO.from(e);
  67 + }
  68 +}
backend/src/test/java/com/xly/erp/module/mod/service/ModuleServiceImplTest.java 0 → 100644
  1 +package com.xly.erp.module.mod.service;
  2 +
  3 +import com.baomidou.mybatisplus.core.conditions.Wrapper;
  4 +import com.xly.erp.common.exception.BizException;
  5 +import com.xly.erp.common.response.ErrorCode;
  6 +import com.xly.erp.module.mod.dto.ModuleCreateDTO;
  7 +import com.xly.erp.module.mod.entity.ModuleEntity;
  8 +import com.xly.erp.module.mod.mapper.ModuleMapper;
  9 +import com.xly.erp.module.mod.service.impl.ModuleServiceImpl;
  10 +import com.xly.erp.module.mod.vo.ModuleVO;
  11 +import org.junit.jupiter.api.Test;
  12 +import org.junit.jupiter.api.extension.ExtendWith;
  13 +import org.mockito.ArgumentMatchers;
  14 +import org.mockito.InjectMocks;
  15 +import org.mockito.Mock;
  16 +import org.mockito.junit.jupiter.MockitoExtension;
  17 +import org.springframework.dao.DuplicateKeyException;
  18 +
  19 +import static org.assertj.core.api.Assertions.assertThat;
  20 +import static org.assertj.core.api.Assertions.assertThatThrownBy;
  21 +import static org.mockito.ArgumentMatchers.any;
  22 +import static org.mockito.Mockito.never;
  23 +import static org.mockito.Mockito.verify;
  24 +import static org.mockito.Mockito.when;
  25 +
  26 +@ExtendWith(MockitoExtension.class)
  27 +class ModuleServiceImplTest {
  28 +
  29 + @Mock ModuleMapper moduleMapper;
  30 +
  31 + @InjectMocks ModuleServiceImpl service;
  32 +
  33 + private ModuleCreateDTO baseDto() {
  34 + ModuleCreateDTO d = new ModuleCreateDTO();
  35 + d.setSDisplayType("前端业务");
  36 + d.setSProcedureName("sp_audit_user");
  37 + d.setSModuleType("USR");
  38 + d.setSManageDeptEn("IT");
  39 + d.setBShowPermission(false);
  40 + d.setSModuleNameZh("用户管理");
  41 + return d;
  42 + }
  43 +
  44 + @Test
  45 + void create_rootModule_returnsVOWithGeneratedId() {
  46 + when(moduleMapper.selectCount(any(Wrapper.class))).thenReturn(0L);
  47 + when(moduleMapper.insert((ModuleEntity) any())).thenAnswer(inv -> {
  48 + ModuleEntity e = inv.getArgument(0);
  49 + e.setIIncrement(123);
  50 + return 1;
  51 + });
  52 +
  53 + ModuleVO vo = service.create(baseDto());
  54 +
  55 + assertThat(vo.getIIncrement()).isEqualTo(123);
  56 + assertThat(vo.getSProcedureName()).isEqualTo("sp_audit_user");
  57 + assertThat(vo.getBShowPermission()).isFalse();
  58 + assertThat(vo.getISortOrder()).isZero();
  59 + assertThat(vo.getBDeleted()).isFalse();
  60 + assertThat(vo.getTCreateDate()).isNotNull();
  61 + }
  62 +
  63 + @Test
  64 + void create_childModule_validatesParentExists() {
  65 + ModuleCreateDTO d = baseDto();
  66 + d.setIParentId(7);
  67 +
  68 + ModuleEntity parent = new ModuleEntity();
  69 + parent.setIIncrement(7);
  70 + parent.setBDeleted(false);
  71 + when(moduleMapper.selectById(7)).thenReturn(parent);
  72 + when(moduleMapper.selectCount(any(Wrapper.class))).thenReturn(0L);
  73 + when(moduleMapper.insert((ModuleEntity) any())).thenAnswer(inv -> {
  74 + ModuleEntity e = inv.getArgument(0);
  75 + e.setIIncrement(8);
  76 + return 1;
  77 + });
  78 +
  79 + ModuleVO vo = service.create(d);
  80 + assertThat(vo.getIIncrement()).isEqualTo(8);
  81 + assertThat(vo.getIParentId()).isEqualTo(7);
  82 + }
  83 +
  84 + @Test
  85 + void create_parentNotFound_throwsBizException40411() {
  86 + ModuleCreateDTO d = baseDto();
  87 + d.setIParentId(999999);
  88 + when(moduleMapper.selectById(999999)).thenReturn(null);
  89 +
  90 + assertThatThrownBy(() -> service.create(d))
  91 + .isInstanceOf(BizException.class)
  92 + .extracting(e -> ((BizException) e).getCode())
  93 + .isEqualTo(ErrorCode.MOD_PARENT_NOT_FOUND.getCode());
  94 + verify(moduleMapper, never()).insert((ModuleEntity) any());
  95 + }
  96 +
  97 + @Test
  98 + void create_parentSoftDeleted_throwsBizException40411() {
  99 + ModuleCreateDTO d = baseDto();
  100 + d.setIParentId(5);
  101 +
  102 + ModuleEntity deleted = new ModuleEntity();
  103 + deleted.setIIncrement(5);
  104 + deleted.setBDeleted(true);
  105 + when(moduleMapper.selectById(5)).thenReturn(deleted);
  106 +
  107 + assertThatThrownBy(() -> service.create(d))
  108 + .isInstanceOf(BizException.class)
  109 + .extracting(e -> ((BizException) e).getCode())
  110 + .isEqualTo(ErrorCode.MOD_PARENT_NOT_FOUND.getCode());
  111 + }
  112 +
  113 + @Test
  114 + void create_duplicateProcedureName_preCheck_throwsBizException40911() {
  115 + when(moduleMapper.selectCount(any(Wrapper.class))).thenReturn(1L);
  116 +
  117 + assertThatThrownBy(() -> service.create(baseDto()))
  118 + .isInstanceOf(BizException.class)
  119 + .extracting(e -> ((BizException) e).getCode())
  120 + .isEqualTo(ErrorCode.MOD_PROC_NAME_DUP.getCode());
  121 + verify(moduleMapper, never()).insert((ModuleEntity) any());
  122 + }
  123 +
  124 + @Test
  125 + void create_duplicateProcedureName_concurrentInsert_throwsBizException40911() {
  126 + when(moduleMapper.selectCount(any(Wrapper.class))).thenReturn(0L);
  127 + when(moduleMapper.insert((ModuleEntity) any()))
  128 + .thenThrow(new DuplicateKeyException("uk_procedure_name"));
  129 +
  130 + assertThatThrownBy(() -> service.create(baseDto()))
  131 + .isInstanceOf(BizException.class)
  132 + .extracting(e -> ((BizException) e).getCode())
  133 + .isEqualTo(ErrorCode.MOD_PROC_NAME_DUP.getCode());
  134 + }
  135 +}