Commit a75b8d36742b9d287da3694f542e937beb2c1089
1 parent
36c5912f
feat(usr): user update dto + service happy path REQ-USR-002
Showing
4 changed files
with
199 additions
and
0 deletions
backend/src/main/java/com/xly/erp/module/usr/dto/UpdateUserDTO.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 UpdateUserDTO { | ||
| 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
| 1 | package com.xly.erp.module.usr.service; | 1 | package com.xly.erp.module.usr.service; |
| 2 | 2 | ||
| 3 | import com.xly.erp.module.usr.dto.CreateUserDTO; | 3 | import com.xly.erp.module.usr.dto.CreateUserDTO; |
| 4 | +import com.xly.erp.module.usr.dto.UpdateUserDTO; | ||
| 4 | 5 | ||
| 5 | import java.util.Map; | 6 | import java.util.Map; |
| 6 | 7 | ||
| 7 | public interface UserService { | 8 | public interface UserService { |
| 8 | Map<String, Object> create(CreateUserDTO dto); | 9 | Map<String, Object> create(CreateUserDTO dto); |
| 10 | + | ||
| 11 | + Integer update(Integer id, UpdateUserDTO dto); | ||
| 9 | } | 12 | } |
backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java
| @@ -5,6 +5,7 @@ import com.xly.erp.common.config.TenantProperties; | @@ -5,6 +5,7 @@ import com.xly.erp.common.config.TenantProperties; | ||
| 5 | import com.xly.erp.common.exception.BizException; | 5 | import com.xly.erp.common.exception.BizException; |
| 6 | import com.xly.erp.common.security.SecurityContextHelper; | 6 | import com.xly.erp.common.security.SecurityContextHelper; |
| 7 | import com.xly.erp.module.usr.dto.CreateUserDTO; | 7 | import com.xly.erp.module.usr.dto.CreateUserDTO; |
| 8 | +import com.xly.erp.module.usr.dto.UpdateUserDTO; | ||
| 8 | import com.xly.erp.module.usr.entity.User; | 9 | import com.xly.erp.module.usr.entity.User; |
| 9 | import com.xly.erp.module.usr.entity.UserPermission; | 10 | 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.PermissionCategoryMapper; |
| @@ -114,4 +115,60 @@ public class UserServiceImpl implements UserService { | @@ -114,4 +115,60 @@ public class UserServiceImpl implements UserService { | ||
| 114 | result.put("sUserNo", entity.getSUserNo()); | 115 | result.put("sUserNo", entity.getSUserNo()); |
| 115 | return result; | 116 | return result; |
| 116 | } | 117 | } |
| 118 | + | ||
| 119 | + @Override | ||
| 120 | + public Integer update(Integer id, UpdateUserDTO dto) { | ||
| 121 | + User original = userMapper.selectById(id); | ||
| 122 | + if (original == null || Boolean.TRUE.equals(original.getBDeleted())) { | ||
| 123 | + throw new BizException(40400, "用户不存在或已删除"); | ||
| 124 | + } | ||
| 125 | + if (!USER_TYPES.contains(dto.getSUserType())) { | ||
| 126 | + throw new BizException(40001, "sUserType: 取值非法"); | ||
| 127 | + } | ||
| 128 | + if (!LANGUAGES.contains(dto.getSLanguage())) { | ||
| 129 | + throw new BizException(40001, "sLanguage: 取值非法"); | ||
| 130 | + } | ||
| 131 | + if (dto.getIStaffId() != null && !staffMapper.existsActiveById(dto.getIStaffId())) { | ||
| 132 | + throw new BizException(40022, "职员不存在或已删除"); | ||
| 133 | + } | ||
| 134 | + List<Integer> ids = dto.getPermissionCategoryIds(); | ||
| 135 | + if (ids != null && !ids.isEmpty()) { | ||
| 136 | + int found = permissionCategoryMapper.countActiveByIds(ids); | ||
| 137 | + if (found != ids.size()) { | ||
| 138 | + throw new BizException(40023, "权限分类含无效 id"); | ||
| 139 | + } | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + User entity = new User(); | ||
| 143 | + entity.setIIncrement(id); | ||
| 144 | + entity.setSUserNo(dto.getSUserNo()); | ||
| 145 | + entity.setSUserName(dto.getSUserName()); | ||
| 146 | + entity.setIStaffId(dto.getIStaffId()); | ||
| 147 | + entity.setSUserType(dto.getSUserType()); | ||
| 148 | + entity.setSLanguage(dto.getSLanguage()); | ||
| 149 | + entity.setBCanModifyDocs(dto.getBCanModifyDocs() != null ? dto.getBCanModifyDocs() : false); | ||
| 150 | + try { | ||
| 151 | + userMapper.updateById(entity); | ||
| 152 | + } catch (DuplicateKeyException e) { | ||
| 153 | + throw new BizException(40020, "用户号或用户名已存在"); | ||
| 154 | + } | ||
| 155 | + | ||
| 156 | + userPermissionMapper.deleteByUserId(id); | ||
| 157 | + if (ids != null && !ids.isEmpty()) { | ||
| 158 | + String authedUserNo = SecurityContextHelper.currentUserNo(); | ||
| 159 | + String createdBy = authedUserNo != null ? authedUserNo : stub.getStubUserNo(); | ||
| 160 | + LocalDateTime now = LocalDateTime.now(); | ||
| 161 | + for (Integer cid : ids) { | ||
| 162 | + UserPermission rel = new UserPermission(); | ||
| 163 | + rel.setSBrandsId(tenant.getBrandsId()); | ||
| 164 | + rel.setSSubsidiaryId(tenant.getSubsidiaryId()); | ||
| 165 | + rel.setTCreateDate(now); | ||
| 166 | + rel.setIUserId(id); | ||
| 167 | + rel.setICategoryId(cid); | ||
| 168 | + rel.setSCreatedBy(createdBy); | ||
| 169 | + userPermissionMapper.insert(rel); | ||
| 170 | + } | ||
| 171 | + } | ||
| 172 | + return id; | ||
| 173 | + } | ||
| 117 | } | 174 | } |
backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java
| @@ -4,6 +4,7 @@ import com.xly.erp.common.config.StubSecurityProperties; | @@ -4,6 +4,7 @@ import com.xly.erp.common.config.StubSecurityProperties; | ||
| 4 | import com.xly.erp.common.config.TenantProperties; | 4 | import com.xly.erp.common.config.TenantProperties; |
| 5 | import com.xly.erp.common.exception.BizException; | 5 | import com.xly.erp.common.exception.BizException; |
| 6 | import com.xly.erp.module.usr.dto.CreateUserDTO; | 6 | import com.xly.erp.module.usr.dto.CreateUserDTO; |
| 7 | +import com.xly.erp.module.usr.dto.UpdateUserDTO; | ||
| 7 | import com.xly.erp.module.usr.entity.User; | 8 | import com.xly.erp.module.usr.entity.User; |
| 8 | import com.xly.erp.module.usr.entity.UserPermission; | 9 | 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.PermissionCategoryMapper; |
| @@ -180,6 +181,92 @@ class UserServiceImplTest { | @@ -180,6 +181,92 @@ class UserServiceImplTest { | ||
| 180 | assertThat(cap.getValue().getSCreatedBy()).isEqualTo("ALICE"); | 181 | assertThat(cap.getValue().getSCreatedBy()).isEqualTo("ALICE"); |
| 181 | } | 182 | } |
| 182 | 183 | ||
| 184 | + @Test | ||
| 185 | + void updateWithValidDto_invokesUpdateById_andRebuildsPermissions() { | ||
| 186 | + when(userMapper.selectById(10)).thenReturn(stubExistingUser(10)); | ||
| 187 | + when(staffMapper.existsActiveById(7)).thenReturn(true); | ||
| 188 | + when(permissionCategoryMapper.countActiveByIds(List.of(11, 22))).thenReturn(2); | ||
| 189 | + when(userMapper.updateById(any(User.class))).thenReturn(1); | ||
| 190 | + when(userPermissionMapper.deleteByUserId(10)).thenReturn(3); | ||
| 191 | + | ||
| 192 | + UpdateUserDTO dto = baseUpdateDto(); | ||
| 193 | + dto.setIStaffId(7); | ||
| 194 | + dto.setPermissionCategoryIds(List.of(11, 22)); | ||
| 195 | + | ||
| 196 | + Integer result = service.update(10, dto); | ||
| 197 | + | ||
| 198 | + assertThat(result).isEqualTo(10); | ||
| 199 | + ArgumentCaptor<User> uc = ArgumentCaptor.forClass(User.class); | ||
| 200 | + verify(userMapper).updateById(uc.capture()); | ||
| 201 | + User passed = uc.getValue(); | ||
| 202 | + assertThat(passed.getIIncrement()).isEqualTo(10); | ||
| 203 | + assertThat(passed.getSUserNo()).isEqualTo("u_new"); | ||
| 204 | + assertThat(passed.getSPasswordHash()).isNull(); | ||
| 205 | + assertThat(passed.getSCreatedBy()).isNull(); | ||
| 206 | + assertThat(passed.getTCreateDate()).isNull(); | ||
| 207 | + assertThat(passed.getSBrandsId()).isNull(); | ||
| 208 | + | ||
| 209 | + verify(userPermissionMapper, times(1)).deleteByUserId(10); | ||
| 210 | + verify(userPermissionMapper, times(2)).insert(any(UserPermission.class)); | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + @Test | ||
| 214 | + void updateWithTargetNotFound_throws40400() { | ||
| 215 | + when(userMapper.selectById(99)).thenReturn(null); | ||
| 216 | + assertThatThrownBy(() -> service.update(99, baseUpdateDto())) | ||
| 217 | + .isInstanceOf(BizException.class) | ||
| 218 | + .hasFieldOrPropertyWithValue("code", 40400); | ||
| 219 | + verify(userMapper, never()).updateById(any(User.class)); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + @Test | ||
| 223 | + void updateWithTargetAlreadyDeleted_throws40400() { | ||
| 224 | + User deleted = stubExistingUser(10); | ||
| 225 | + deleted.setBDeleted(true); | ||
| 226 | + when(userMapper.selectById(10)).thenReturn(deleted); | ||
| 227 | + assertThatThrownBy(() -> service.update(10, baseUpdateDto())) | ||
| 228 | + .isInstanceOf(BizException.class) | ||
| 229 | + .hasFieldOrPropertyWithValue("code", 40400); | ||
| 230 | + verify(userMapper, never()).updateById(any(User.class)); | ||
| 231 | + } | ||
| 232 | + | ||
| 233 | + @Test | ||
| 234 | + void updateWithBCanModifyDocsNull_setsFalseInEntity() { | ||
| 235 | + when(userMapper.selectById(10)).thenReturn(stubExistingUser(10)); | ||
| 236 | + when(userMapper.updateById(any(User.class))).thenReturn(1); | ||
| 237 | + UpdateUserDTO dto = baseUpdateDto(); | ||
| 238 | + dto.setBCanModifyDocs(null); | ||
| 239 | + | ||
| 240 | + service.update(10, dto); | ||
| 241 | + | ||
| 242 | + ArgumentCaptor<User> uc = ArgumentCaptor.forClass(User.class); | ||
| 243 | + verify(userMapper).updateById(uc.capture()); | ||
| 244 | + assertThat(uc.getValue().getBCanModifyDocs()).isFalse(); | ||
| 245 | + } | ||
| 246 | + | ||
| 247 | + private UpdateUserDTO baseUpdateDto() { | ||
| 248 | + UpdateUserDTO dto = new UpdateUserDTO(); | ||
| 249 | + dto.setSUserNo("u_new"); | ||
| 250 | + dto.setSUserName("用户新"); | ||
| 251 | + dto.setSUserType("普通用户"); | ||
| 252 | + dto.setSLanguage("zh"); | ||
| 253 | + dto.setBCanModifyDocs(false); | ||
| 254 | + return dto; | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + private User stubExistingUser(Integer id) { | ||
| 258 | + User u = new User(); | ||
| 259 | + u.setIIncrement(id); | ||
| 260 | + u.setSUserNo("u_orig"); | ||
| 261 | + u.setSUserName("原用户"); | ||
| 262 | + u.setSUserType("普通用户"); | ||
| 263 | + u.setSLanguage("zh"); | ||
| 264 | + u.setSPasswordHash("$2a$10$origHash"); | ||
| 265 | + u.setSCreatedBy("ORIG_USER"); | ||
| 266 | + u.setBDeleted(false); | ||
| 267 | + return u; | ||
| 268 | + } | ||
| 269 | + | ||
| 183 | private CreateUserDTO baseDto() { | 270 | private CreateUserDTO baseDto() { |
| 184 | CreateUserDTO dto = new CreateUserDTO(); | 271 | CreateUserDTO dto = new CreateUserDTO(); |
| 185 | dto.setSUserNo("u001"); | 272 | dto.setSUserNo("u001"); |