Commit 3f1b9e89007a308b451ea4c6d30db4e9f8a78dda

Authored by zichun
1 parent b4a152cc

feat(usr): update user service REQ-USR-002

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.UserCreateDTO; 3 import com.xly.erp.module.usr.dto.UserCreateDTO;
  4 +import com.xly.erp.module.usr.dto.UserUpdateDTO;
4 import com.xly.erp.module.usr.vo.UserVO; 5 import com.xly.erp.module.usr.vo.UserVO;
5 6
6 public interface UserService { 7 public interface UserService {
7 /** REQ-USR-001 用户新增 */ 8 /** REQ-USR-001 用户新增 */
8 UserVO create(UserCreateDTO dto); 9 UserVO create(UserCreateDTO dto);
  10 +
  11 + /** REQ-USR-002 用户修改 */
  12 + UserVO update(Integer id, UserUpdateDTO dto);
9 } 13 }
backend/src/main/java/com/xly/erp/module/usr/service/impl/UserServiceImpl.java
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4 import com.xly.erp.common.exception.BizException; 4 import com.xly.erp.common.exception.BizException;
5 import com.xly.erp.common.response.ErrorCode; 5 import com.xly.erp.common.response.ErrorCode;
6 import com.xly.erp.module.usr.dto.UserCreateDTO; 6 import com.xly.erp.module.usr.dto.UserCreateDTO;
  7 +import com.xly.erp.module.usr.dto.UserUpdateDTO;
7 import com.xly.erp.module.usr.entity.PermissionCategoryEntity; 8 import com.xly.erp.module.usr.entity.PermissionCategoryEntity;
8 import com.xly.erp.module.usr.entity.StaffEntity; 9 import com.xly.erp.module.usr.entity.StaffEntity;
9 import com.xly.erp.module.usr.entity.UserEntity; 10 import com.xly.erp.module.usr.entity.UserEntity;
@@ -106,4 +107,59 @@ public class UserServiceImpl implements UserService { @@ -106,4 +107,59 @@ public class UserServiceImpl implements UserService {
106 107
107 return UserVO.from(user, categoryIds); 108 return UserVO.from(user, categoryIds);
108 } 109 }
  110 +
  111 + /** REQ-USR-002 用户修改 */
  112 + @Override
  113 + @Transactional(rollbackFor = Exception.class)
  114 + public UserVO update(Integer id, UserUpdateDTO dto) {
  115 + // 1. 目标用户存在 + 未软删除
  116 + UserEntity target = userMapper.selectById(id);
  117 + if (target == null || Boolean.TRUE.equals(target.getBDeleted())) {
  118 + throw new BizException(ErrorCode.USR_NOT_FOUND);
  119 + }
  120 +
  121 + // 2. iStaffId 校验(仅当非空)
  122 + if (dto.getIStaffId() != null) {
  123 + StaffEntity staff = staffMapper.selectById(dto.getIStaffId());
  124 + if (staff == null || Boolean.TRUE.equals(staff.getBDeleted())) {
  125 + throw new BizException(ErrorCode.STAFF_NOT_FOUND);
  126 + }
  127 + }
  128 +
  129 + // 3. 权限分类校验(仅当非空)
  130 + List<Integer> categoryIds = dto.getPermissionCategoryIds() == null
  131 + ? new ArrayList<>() : dto.getPermissionCategoryIds();
  132 + if (!categoryIds.isEmpty()) {
  133 + List<PermissionCategoryEntity> found = permissionCategoryMapper.selectBatchIds(categoryIds);
  134 + if (found.size() != categoryIds.size()
  135 + || found.stream().anyMatch(p -> Boolean.TRUE.equals(p.getBDeleted()))) {
  136 + throw new BizException(ErrorCode.PERM_CATEGORY_NOT_FOUND);
  137 + }
  138 + }
  139 +
  140 + // 4. 字段合并到 target(保留 sUserNo / sUserName / sPasswordHash 等保护字段)
  141 + target.setIStaffId(dto.getIStaffId()); // 含 null 清空(依赖 iStaffId.IGNORED 策略)
  142 + target.setSUserType(dto.getSUserType());
  143 + target.setSLanguage(dto.getSLanguage());
  144 + if (dto.getBCanModifyDocs() != null) {
  145 + target.setBCanModifyDocs(dto.getBCanModifyDocs());
  146 + }
  147 +
  148 + // 5. 落库 user
  149 + userMapper.updateById(target);
  150 +
  151 + // 6. 重建权限关联:先删后插(清空原有,再按 dto 插入)
  152 + userPermissionMapper.delete(
  153 + new LambdaQueryWrapper<UserPermissionEntity>()
  154 + .eq(UserPermissionEntity::getIUserId, id));
  155 + for (Integer categoryId : categoryIds) {
  156 + UserPermissionEntity up = new UserPermissionEntity();
  157 + up.setIUserId(id);
  158 + up.setICategoryId(categoryId);
  159 + up.setTCreateDate(LocalDateTime.now());
  160 + userPermissionMapper.insert(up);
  161 + }
  162 +
  163 + return UserVO.from(target, categoryIds);
  164 + }
109 } 165 }
backend/src/test/java/com/xly/erp/module/usr/service/UserServiceImplTest.java
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper; @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.Wrapper;
4 import com.xly.erp.common.exception.BizException; 4 import com.xly.erp.common.exception.BizException;
5 import com.xly.erp.common.response.ErrorCode; 5 import com.xly.erp.common.response.ErrorCode;
6 import com.xly.erp.module.usr.dto.UserCreateDTO; 6 import com.xly.erp.module.usr.dto.UserCreateDTO;
  7 +import com.xly.erp.module.usr.dto.UserUpdateDTO;
7 import com.xly.erp.module.usr.entity.PermissionCategoryEntity; 8 import com.xly.erp.module.usr.entity.PermissionCategoryEntity;
8 import com.xly.erp.module.usr.entity.StaffEntity; 9 import com.xly.erp.module.usr.entity.StaffEntity;
9 import com.xly.erp.module.usr.entity.UserEntity; 10 import com.xly.erp.module.usr.entity.UserEntity;
@@ -218,4 +219,195 @@ class UserServiceImplTest { @@ -218,4 +219,195 @@ class UserServiceImplTest {
218 p.setBDeleted(false); 219 p.setBDeleted(false);
219 return p; 220 return p;
220 } 221 }
  222 +
  223 + // ============================================================
  224 + // REQ-USR-002 update 系列
  225 + // ============================================================
  226 +
  227 + private UserUpdateDTO updateDto() {
  228 + UserUpdateDTO d = new UserUpdateDTO();
  229 + d.setIStaffId(7);
  230 + d.setSUserType("超级管理员");
  231 + d.setSLanguage("en");
  232 + d.setBCanModifyDocs(true);
  233 + d.setPermissionCategoryIds(List.of(10, 20));
  234 + return d;
  235 + }
  236 +
  237 + private UserEntity existingTargetUser(int id) {
  238 + UserEntity u = new UserEntity();
  239 + u.setIIncrement(id);
  240 + u.setSUserNo("u_orig");
  241 + u.setSUserName("alice_orig");
  242 + u.setIStaffId(3);
  243 + u.setSUserType("普通用户");
  244 + u.setSLanguage("zh");
  245 + u.setBCanModifyDocs(false);
  246 + u.setSPasswordHash("$2a$10$origHash");
  247 + u.setBDeleted(false);
  248 + u.setSCreatedBy("system");
  249 + return u;
  250 + }
  251 +
  252 + @Test
  253 + void update_targetNotFound_throws40431() {
  254 + when(userMapper.selectById(99)).thenReturn(null);
  255 +
  256 + assertThatThrownBy(() -> service.update(99, updateDto()))
  257 + .isInstanceOf(BizException.class)
  258 + .extracting(e -> ((BizException) e).getCode())
  259 + .isEqualTo(ErrorCode.USR_NOT_FOUND.getCode());
  260 + }
  261 +
  262 + @Test
  263 + void update_targetSoftDeleted_throws40431() {
  264 + UserEntity target = existingTargetUser(100);
  265 + target.setBDeleted(true);
  266 + when(userMapper.selectById(100)).thenReturn(target);
  267 +
  268 + assertThatThrownBy(() -> service.update(100, updateDto()))
  269 + .isInstanceOf(BizException.class)
  270 + .extracting(e -> ((BizException) e).getCode())
  271 + .isEqualTo(ErrorCode.USR_NOT_FOUND.getCode());
  272 + }
  273 +
  274 + @Test
  275 + void update_staffNotFound_throws40421() {
  276 + UserEntity target = existingTargetUser(101);
  277 + when(userMapper.selectById(101)).thenReturn(target);
  278 + UserUpdateDTO d = updateDto();
  279 + d.setIStaffId(999999);
  280 + when(staffMapper.selectById(999999)).thenReturn(null);
  281 +
  282 + assertThatThrownBy(() -> service.update(101, d))
  283 + .isInstanceOf(BizException.class)
  284 + .extracting(e -> ((BizException) e).getCode())
  285 + .isEqualTo(ErrorCode.STAFF_NOT_FOUND.getCode());
  286 + }
  287 +
  288 + @Test
  289 + void update_staffSoftDeleted_throws40421() {
  290 + UserEntity target = existingTargetUser(102);
  291 + when(userMapper.selectById(102)).thenReturn(target);
  292 + UserUpdateDTO d = updateDto();
  293 + d.setIStaffId(8);
  294 + StaffEntity staff = new StaffEntity();
  295 + staff.setIIncrement(8);
  296 + staff.setBDeleted(true);
  297 + when(staffMapper.selectById(8)).thenReturn(staff);
  298 +
  299 + assertThatThrownBy(() -> service.update(102, d))
  300 + .isInstanceOf(BizException.class)
  301 + .extracting(e -> ((BizException) e).getCode())
  302 + .isEqualTo(ErrorCode.STAFF_NOT_FOUND.getCode());
  303 + }
  304 +
  305 + @Test
  306 + void update_permissionCategoryNotFound_throws40422() {
  307 + UserEntity target = existingTargetUser(103);
  308 + when(userMapper.selectById(103)).thenReturn(target);
  309 + UserUpdateDTO d = updateDto();
  310 + d.setIStaffId(null); // 跳过 staff 校验
  311 + d.setPermissionCategoryIds(List.of(10, 999999));
  312 + when(permissionCategoryMapper.selectBatchIds(anyList())).thenReturn(List.of(cat(10)));
  313 +
  314 + assertThatThrownBy(() -> service.update(103, d))
  315 + .isInstanceOf(BizException.class)
  316 + .extracting(e -> ((BizException) e).getCode())
  317 + .isEqualTo(ErrorCode.PERM_CATEGORY_NOT_FOUND.getCode());
  318 + }
  319 +
  320 + @Test
  321 + void update_full_returnsVOWithUpdatedFields_andRebuildsPermissions() {
  322 + UserEntity target = existingTargetUser(104);
  323 + when(userMapper.selectById(104)).thenReturn(target);
  324 + StaffEntity staff = new StaffEntity();
  325 + staff.setIIncrement(7);
  326 + staff.setBDeleted(false);
  327 + when(staffMapper.selectById(7)).thenReturn(staff);
  328 + when(permissionCategoryMapper.selectBatchIds(anyList())).thenReturn(List.of(cat(10), cat(20)));
  329 +
  330 + UserVO vo = service.update(104, updateDto());
  331 +
  332 + ArgumentCaptor<UserEntity> userCap = ArgumentCaptor.forClass(UserEntity.class);
  333 + verify(userMapper).updateById(userCap.capture());
  334 + UserEntity saved = userCap.getValue();
  335 + // 已修改字段
  336 + assertThat(saved.getIStaffId()).isEqualTo(7);
  337 + assertThat(saved.getSUserType()).isEqualTo("超级管理员");
  338 + assertThat(saved.getSLanguage()).isEqualTo("en");
  339 + assertThat(saved.getBCanModifyDocs()).isTrue();
  340 + // 保留字段
  341 + assertThat(saved.getSUserNo()).isEqualTo("u_orig");
  342 + assertThat(saved.getSUserName()).isEqualTo("alice_orig");
  343 + assertThat(saved.getSPasswordHash()).isEqualTo("$2a$10$origHash");
  344 + assertThat(saved.getSCreatedBy()).isEqualTo("system");
  345 +
  346 + // 关联表先删后插
  347 + verify(userPermissionMapper).delete(any(Wrapper.class));
  348 + ArgumentCaptor<UserPermissionEntity> upCap = ArgumentCaptor.forClass(UserPermissionEntity.class);
  349 + verify(userPermissionMapper, times(2)).insert(upCap.capture());
  350 + assertThat(upCap.getAllValues()).extracting(UserPermissionEntity::getICategoryId)
  351 + .containsExactly(10, 20);
  352 + assertThat(upCap.getAllValues()).extracting(UserPermissionEntity::getIUserId)
  353 + .containsOnly(104);
  354 +
  355 + assertThat(vo.getPermissionCategoryIds()).containsExactly(10, 20);
  356 + }
  357 +
  358 + @Test
  359 + void update_partialNullBCanModifyDocs_keepsOriginal() {
  360 + UserEntity target = existingTargetUser(105);
  361 + target.setBCanModifyDocs(true); // 原值 true
  362 + when(userMapper.selectById(105)).thenReturn(target);
  363 + StaffEntity staff = new StaffEntity();
  364 + staff.setIIncrement(7);
  365 + staff.setBDeleted(false);
  366 + when(staffMapper.selectById(7)).thenReturn(staff);
  367 + when(permissionCategoryMapper.selectBatchIds(anyList())).thenReturn(List.of(cat(10), cat(20)));
  368 +
  369 + UserUpdateDTO d = updateDto();
  370 + d.setBCanModifyDocs(null); // 期望保留原值
  371 +
  372 + service.update(105, d);
  373 +
  374 + ArgumentCaptor<UserEntity> userCap = ArgumentCaptor.forClass(UserEntity.class);
  375 + verify(userMapper).updateById(userCap.capture());
  376 + assertThat(userCap.getValue().getBCanModifyDocs()).isTrue();
  377 + }
  378 +
  379 + @Test
  380 + void update_clearStaffId_setsToNull() {
  381 + UserEntity target = existingTargetUser(106);
  382 + target.setIStaffId(3); // 原本有
  383 + when(userMapper.selectById(106)).thenReturn(target);
  384 + when(permissionCategoryMapper.selectBatchIds(anyList())).thenReturn(List.of(cat(10), cat(20)));
  385 +
  386 + UserUpdateDTO d = updateDto();
  387 + d.setIStaffId(null); // 显式清空
  388 +
  389 + service.update(106, d);
  390 +
  391 + ArgumentCaptor<UserEntity> userCap = ArgumentCaptor.forClass(UserEntity.class);
  392 + verify(userMapper).updateById(userCap.capture());
  393 + assertThat(userCap.getValue().getIStaffId()).isNull();
  394 + }
  395 +
  396 + @Test
  397 + void update_emptyPermissionCategoryIds_clearsAllAssociations() {
  398 + UserEntity target = existingTargetUser(107);
  399 + when(userMapper.selectById(107)).thenReturn(target);
  400 + StaffEntity staff = new StaffEntity();
  401 + staff.setIIncrement(7);
  402 + staff.setBDeleted(false);
  403 + when(staffMapper.selectById(7)).thenReturn(staff);
  404 +
  405 + UserUpdateDTO d = updateDto();
  406 + d.setPermissionCategoryIds(List.of()); // 清空全部
  407 +
  408 + service.update(107, d);
  409 +
  410 + verify(userPermissionMapper).delete(any(Wrapper.class));
  411 + verify(userPermissionMapper, never()).insert((UserPermissionEntity) any());
  412 + }
221 } 413 }