Commit e27b394bfff1a96a1ef4f2cf5183c5de7bbd13ae

Authored by zichun
1 parent 4cf3a3bb

feat(usr): user create dto + service happy path REQ-USR-001

backend/src/main/java/com/xly/erp/module/usr/dto/CreateUserDTO.java 0 → 100644
  1 +package com.xly.erp.module.usr.dto;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonProperty;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.Size;
  6 +
  7 +import java.util.List;
  8 +
  9 +public class CreateUserDTO {
  10 +
  11 + @JsonProperty("sUserNo")
  12 + @NotBlank
  13 + @Size(max = 50)
  14 + private String sUserNo;
  15 +
  16 + @JsonProperty("sUserName")
  17 + @NotBlank
  18 + @Size(max = 50)
  19 + private String sUserName;
  20 +
  21 + @JsonProperty("iStaffId")
  22 + private Integer iStaffId;
  23 +
  24 + @JsonProperty("sUserType")
  25 + @NotBlank
  26 + private String sUserType;
  27 +
  28 + @JsonProperty("sLanguage")
  29 + @NotBlank
  30 + private String sLanguage;
  31 +
  32 + @JsonProperty("bCanModifyDocs")
  33 + private Boolean bCanModifyDocs;
  34 +
  35 + @JsonProperty("permissionCategoryIds")
  36 + private List<Integer> permissionCategoryIds;
  37 +
  38 + public String getSUserNo() { return sUserNo; }
  39 + public void setSUserNo(String sUserNo) { this.sUserNo = sUserNo; }
  40 + public String getSUserName() { return sUserName; }
  41 + public void setSUserName(String sUserName) { this.sUserName = sUserName; }
  42 + public Integer getIStaffId() { return iStaffId; }
  43 + public void setIStaffId(Integer iStaffId) { this.iStaffId = iStaffId; }
  44 + public String getSUserType() { return sUserType; }
  45 + public void setSUserType(String sUserType) { this.sUserType = sUserType; }
  46 + public String getSLanguage() { return sLanguage; }
  47 + public void setSLanguage(String sLanguage) { this.sLanguage = sLanguage; }
  48 + public Boolean getBCanModifyDocs() { return bCanModifyDocs; }
  49 + public void setBCanModifyDocs(Boolean bCanModifyDocs) { this.bCanModifyDocs = bCanModifyDocs; }
  50 + public List<Integer> getPermissionCategoryIds() { return permissionCategoryIds; }
  51 + public void setPermissionCategoryIds(List<Integer> permissionCategoryIds) { this.permissionCategoryIds = permissionCategoryIds; }
  52 +}
... ...
backend/src/main/java/com/xly/erp/module/usr/service/UserService.java 0 → 100644
  1 +package com.xly.erp.module.usr.service;
  2 +
  3 +import com.xly.erp.module.usr.dto.CreateUserDTO;
  4 +
  5 +import java.util.Map;
  6 +
  7 +public interface UserService {
  8 + Map<String, Object> create(CreateUserDTO dto);
  9 +}
... ...
backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java 0 → 100644
  1 +package com.xly.erp.module.usr.service.impl;
  2 +
  3 +import com.xly.erp.common.config.StubSecurityProperties;
  4 +import com.xly.erp.common.config.TenantProperties;
  5 +import com.xly.erp.common.exception.BizException;
  6 +import com.xly.erp.common.security.SecurityContextHelper;
  7 +import com.xly.erp.module.usr.dto.CreateUserDTO;
  8 +import com.xly.erp.module.usr.entity.User;
  9 +import com.xly.erp.module.usr.entity.UserPermission;
  10 +import com.xly.erp.module.usr.mapper.PermissionCategoryMapper;
  11 +import com.xly.erp.module.usr.mapper.StaffMapper;
  12 +import com.xly.erp.module.usr.mapper.UserMapper;
  13 +import com.xly.erp.module.usr.mapper.UserPermissionMapper;
  14 +import com.xly.erp.module.usr.service.UserService;
  15 +import org.springframework.dao.DuplicateKeyException;
  16 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  17 +import org.springframework.stereotype.Service;
  18 +import org.springframework.transaction.annotation.Transactional;
  19 +
  20 +import java.time.LocalDateTime;
  21 +import java.util.LinkedHashMap;
  22 +import java.util.List;
  23 +import java.util.Map;
  24 +import java.util.Set;
  25 +
  26 +@Service
  27 +@Transactional(rollbackFor = Exception.class)
  28 +public class UserServiceImpl implements UserService {
  29 +
  30 + static final Set<String> USER_TYPES = Set.of("普通用户", "超级管理员");
  31 + static final Set<String> LANGUAGES = Set.of("zh", "en", "zh-TW");
  32 + static final String DEFAULT_PASSWORD = "666666";
  33 +
  34 + private final UserMapper userMapper;
  35 + private final UserPermissionMapper userPermissionMapper;
  36 + private final StaffMapper staffMapper;
  37 + private final PermissionCategoryMapper permissionCategoryMapper;
  38 + private final TenantProperties tenant;
  39 + private final StubSecurityProperties stub;
  40 + private final BCryptPasswordEncoder passwordEncoder;
  41 +
  42 + public UserServiceImpl(UserMapper userMapper,
  43 + UserPermissionMapper userPermissionMapper,
  44 + StaffMapper staffMapper,
  45 + PermissionCategoryMapper permissionCategoryMapper,
  46 + TenantProperties tenant,
  47 + StubSecurityProperties stub,
  48 + BCryptPasswordEncoder passwordEncoder) {
  49 + this.userMapper = userMapper;
  50 + this.userPermissionMapper = userPermissionMapper;
  51 + this.staffMapper = staffMapper;
  52 + this.permissionCategoryMapper = permissionCategoryMapper;
  53 + this.tenant = tenant;
  54 + this.stub = stub;
  55 + this.passwordEncoder = passwordEncoder;
  56 + }
  57 +
  58 + @Override
  59 + public Map<String, Object> create(CreateUserDTO dto) {
  60 + User entity = new User();
  61 + entity.setSBrandsId(tenant.getBrandsId());
  62 + entity.setSSubsidiaryId(tenant.getSubsidiaryId());
  63 + entity.setTCreateDate(LocalDateTime.now());
  64 + entity.setSUserNo(dto.getSUserNo());
  65 + entity.setSUserName(dto.getSUserName());
  66 + entity.setIStaffId(dto.getIStaffId());
  67 + entity.setSUserType(dto.getSUserType());
  68 + entity.setSLanguage(dto.getSLanguage());
  69 + entity.setBCanModifyDocs(dto.getBCanModifyDocs() != null ? dto.getBCanModifyDocs() : false);
  70 + entity.setSPasswordHash(passwordEncoder.encode(DEFAULT_PASSWORD));
  71 + String authedUserNo = SecurityContextHelper.currentUserNo();
  72 + String createdBy = authedUserNo != null ? authedUserNo : stub.getStubUserNo();
  73 + entity.setSCreatedBy(createdBy);
  74 + entity.setBDeleted(false);
  75 +
  76 + userMapper.insert(entity);
  77 +
  78 + List<Integer> ids = dto.getPermissionCategoryIds();
  79 + if (ids != null && !ids.isEmpty()) {
  80 + for (Integer cid : ids) {
  81 + UserPermission rel = new UserPermission();
  82 + rel.setSBrandsId(tenant.getBrandsId());
  83 + rel.setSSubsidiaryId(tenant.getSubsidiaryId());
  84 + rel.setTCreateDate(LocalDateTime.now());
  85 + rel.setIUserId(entity.getIIncrement());
  86 + rel.setICategoryId(cid);
  87 + rel.setSCreatedBy(createdBy);
  88 + userPermissionMapper.insert(rel);
  89 + }
  90 + }
  91 +
  92 + Map<String, Object> result = new LinkedHashMap<>();
  93 + result.put("iIncrement", entity.getIIncrement());
  94 + result.put("sUserNo", entity.getSUserNo());
  95 + return result;
  96 + }
  97 +}
... ...
backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java 0 → 100644
  1 +package com.xly.erp.module.usr.service;
  2 +
  3 +import com.xly.erp.common.config.StubSecurityProperties;
  4 +import com.xly.erp.common.config.TenantProperties;
  5 +import com.xly.erp.common.exception.BizException;
  6 +import com.xly.erp.module.usr.dto.CreateUserDTO;
  7 +import com.xly.erp.module.usr.entity.User;
  8 +import com.xly.erp.module.usr.entity.UserPermission;
  9 +import com.xly.erp.module.usr.mapper.PermissionCategoryMapper;
  10 +import com.xly.erp.module.usr.mapper.StaffMapper;
  11 +import com.xly.erp.module.usr.mapper.UserMapper;
  12 +import com.xly.erp.module.usr.mapper.UserPermissionMapper;
  13 +import com.xly.erp.module.usr.service.impl.UserServiceImpl;
  14 +import org.junit.jupiter.api.AfterEach;
  15 +import org.junit.jupiter.api.BeforeEach;
  16 +import org.junit.jupiter.api.Test;
  17 +import org.mockito.ArgumentCaptor;
  18 +import org.springframework.dao.DuplicateKeyException;
  19 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  20 +import org.springframework.security.core.context.SecurityContextHolder;
  21 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  22 +
  23 +import java.util.Collections;
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +
  27 +import static org.assertj.core.api.Assertions.assertThat;
  28 +import static org.assertj.core.api.Assertions.assertThatThrownBy;
  29 +import static org.mockito.ArgumentMatchers.any;
  30 +import static org.mockito.ArgumentMatchers.anyList;
  31 +import static org.mockito.Mockito.lenient;
  32 +import static org.mockito.Mockito.mock;
  33 +import static org.mockito.Mockito.never;
  34 +import static org.mockito.Mockito.times;
  35 +import static org.mockito.Mockito.verify;
  36 +import static org.mockito.Mockito.when;
  37 +
  38 +class UserServiceImplTest {
  39 +
  40 + private UserMapper userMapper;
  41 + private UserPermissionMapper userPermissionMapper;
  42 + private StaffMapper staffMapper;
  43 + private PermissionCategoryMapper permissionCategoryMapper;
  44 + private BCryptPasswordEncoder encoder;
  45 + private UserServiceImpl service;
  46 +
  47 + @BeforeEach
  48 + void setUp() {
  49 + userMapper = mock(UserMapper.class);
  50 + userPermissionMapper = mock(UserPermissionMapper.class);
  51 + staffMapper = mock(StaffMapper.class);
  52 + permissionCategoryMapper = mock(PermissionCategoryMapper.class);
  53 + encoder = new BCryptPasswordEncoder();
  54 + TenantProperties tenant = new TenantProperties();
  55 + tenant.setBrandsId("XLY");
  56 + tenant.setSubsidiaryId("XLY");
  57 + StubSecurityProperties stub = new StubSecurityProperties();
  58 + stub.setStubUserNo("STUB_ADMIN");
  59 +
  60 + service = new UserServiceImpl(userMapper, userPermissionMapper, staffMapper,
  61 + permissionCategoryMapper, tenant, stub, encoder);
  62 +
  63 + lenient().when(userMapper.insert(any(User.class))).thenAnswer(inv -> {
  64 + User u = inv.getArgument(0);
  65 + u.setIIncrement(456);
  66 + return 1;
  67 + });
  68 + lenient().when(userPermissionMapper.insert(any(UserPermission.class))).thenReturn(1);
  69 + }
  70 +
  71 + @AfterEach
  72 + void clearContext() {
  73 + SecurityContextHolder.clearContext();
  74 + }
  75 +
  76 + @Test
  77 + void createWithValidDto_persistsUser_andUserPermissions() {
  78 + when(staffMapper.existsActiveById(7)).thenReturn(true);
  79 + when(permissionCategoryMapper.countActiveByIds(List.of(11, 22))).thenReturn(2);
  80 +
  81 + CreateUserDTO dto = baseDto();
  82 + dto.setIStaffId(7);
  83 + dto.setPermissionCategoryIds(List.of(11, 22));
  84 +
  85 + Map<String, Object> result = service.create(dto);
  86 +
  87 + assertThat(result).containsEntry("iIncrement", 456).containsEntry("sUserNo", "u001");
  88 + ArgumentCaptor<User> userCap = ArgumentCaptor.forClass(User.class);
  89 + verify(userMapper).insert(userCap.capture());
  90 + User saved = userCap.getValue();
  91 + assertThat(saved.getSBrandsId()).isEqualTo("XLY");
  92 + assertThat(saved.getSCreatedBy()).isEqualTo("STUB_ADMIN");
  93 + assertThat(saved.getTCreateDate()).isNotNull();
  94 + assertThat(saved.getSPasswordHash()).startsWith("$2a$");
  95 + assertThat(saved.getBDeleted()).isFalse();
  96 + assertThat(saved.getIStaffId()).isEqualTo(7);
  97 + assertThat(saved.getBCanModifyDocs()).isFalse();
  98 +
  99 + ArgumentCaptor<UserPermission> permCap = ArgumentCaptor.forClass(UserPermission.class);
  100 + verify(userPermissionMapper, times(2)).insert(permCap.capture());
  101 + List<UserPermission> rels = permCap.getAllValues();
  102 + assertThat(rels).extracting(UserPermission::getICategoryId).containsExactly(11, 22);
  103 + assertThat(rels).allMatch(r -> r.getIUserId().equals(456));
  104 + assertThat(rels).allMatch(r -> r.getSBrandsId().equals("XLY"));
  105 + }
  106 +
  107 + @Test
  108 + void createWithoutPermissionCategoryIds_skipsUserPermissionInserts() {
  109 + CreateUserDTO dto = baseDto();
  110 + dto.setPermissionCategoryIds(null);
  111 +
  112 + service.create(dto);
  113 +
  114 + verify(userMapper, times(1)).insert(any(User.class));
  115 + verify(userPermissionMapper, never()).insert(any(UserPermission.class));
  116 + }
  117 +
  118 + private CreateUserDTO baseDto() {
  119 + CreateUserDTO dto = new CreateUserDTO();
  120 + dto.setSUserNo("u001");
  121 + dto.setSUserName("用户1");
  122 + dto.setSUserType("普通用户");
  123 + dto.setSLanguage("zh");
  124 + dto.setBCanModifyDocs(false);
  125 + return dto;
  126 + }
  127 +}
... ...