diff --git a/backend/src/main/java/com/xly/erp/modules/usr/service/UsrUserService.java b/backend/src/main/java/com/xly/erp/modules/usr/service/UsrUserService.java index 4235f84..3795390 100644 --- a/backend/src/main/java/com/xly/erp/modules/usr/service/UsrUserService.java +++ b/backend/src/main/java/com/xly/erp/modules/usr/service/UsrUserService.java @@ -1,9 +1,10 @@ package com.xly.erp.modules.usr.service; import com.xly.erp.modules.usr.dto.CreateUserDTO; +import com.xly.erp.modules.usr.dto.UpdateUserDTO; /** - * 用户业务服务(docs/04 § 1.2)。REQ-USR-001。 + * 用户业务服务(docs/04 § 1.2)。REQ-USR-001 / REQ-USR-002。 */ public interface UsrUserService { @@ -15,4 +16,16 @@ public interface UsrUserService { * @return 新建用户主键 iIncrement */ Integer createUser(CreateUserDTO dto); + + /** + * 修改用户(REQ-USR-002):校验目标用户存在 → 校验关联职员 / 权限存在 → + * 按"部分更新(null 不改)"语义更新 {@code usr_user} 主记录(不动 sUserName / + * sPassword / 审计列 / 租户列)→ 按"全量覆盖"语义重写该用户在 {@code usr_user_permission} + * 的授权。整体单事务。 + * + * @param id 目标用户主键 iIncrement + * @param dto 修改用户入参 + * @return 被修改用户主键 iIncrement(等于入参 id) + */ + Integer updateUser(Integer id, UpdateUserDTO dto); } diff --git a/backend/src/main/java/com/xly/erp/modules/usr/service/impl/UsrUserServiceImpl.java b/backend/src/main/java/com/xly/erp/modules/usr/service/impl/UsrUserServiceImpl.java index 33e9a3b..33842cf 100644 --- a/backend/src/main/java/com/xly/erp/modules/usr/service/impl/UsrUserServiceImpl.java +++ b/backend/src/main/java/com/xly/erp/modules/usr/service/impl/UsrUserServiceImpl.java @@ -5,6 +5,7 @@ import com.xly.erp.common.exception.BusinessException; import com.xly.erp.common.response.ResultCode; import com.xly.erp.common.security.SecurityUtil; import com.xly.erp.modules.usr.dto.CreateUserDTO; +import com.xly.erp.modules.usr.dto.UpdateUserDTO; import com.xly.erp.modules.usr.entity.UsrUser; import com.xly.erp.modules.usr.entity.UsrUserPermission; import com.xly.erp.modules.usr.mapper.UsrEmployeeMapper; @@ -129,4 +130,37 @@ public class UsrUserServiceImpl implements UsrUserService { return newUserId; } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateUser(Integer id, UpdateUserDTO dto) { + // 1. 目标用户存在性校验(不存在 → 40401,不写入任何表)。 + UsrUser existing = usrUserMapper.selectById(id); + if (existing == null) { + throw new BusinessException(ResultCode.NOT_FOUND); + } + + // 2. 组装目标实体:仅 set 主键 + 非 null 可更新列。 + // 不 set sUserName / sPassword / sCreator / tCreateDate / 租户列, + // 依赖 MP updateById「null 字段不参与 SET」语义保持原值不被覆盖。 + UsrUser target = new UsrUser(); + target.setIIncrement(id); + target.setSUserType(dto.getSUserType()); + target.setSLanguage(dto.getSLanguage()); + if (dto.getSUserNo() != null) { + target.setSUserNo(dto.getSUserNo()); + } + if (dto.getIEmployeeId() != null) { + target.setIEmployeeId(dto.getIEmployeeId()); + } + if (dto.getICanModifyBill() != null) { + target.setICanModifyBill(dto.getICanModifyBill()); + } + if (dto.getIIsVoid() != null) { + target.setIIsVoid(dto.getIIsVoid()); + } + usrUserMapper.updateById(target); + + return id; + } } diff --git a/backend/src/test/java/com/xly/erp/modules/usr/service/UsrUserServiceImplTest.java b/backend/src/test/java/com/xly/erp/modules/usr/service/UsrUserServiceImplTest.java index d9dc279..ddb5e02 100644 --- a/backend/src/test/java/com/xly/erp/modules/usr/service/UsrUserServiceImplTest.java +++ b/backend/src/test/java/com/xly/erp/modules/usr/service/UsrUserServiceImplTest.java @@ -14,6 +14,7 @@ import com.xly.erp.common.exception.BusinessException; import com.xly.erp.common.response.ResultCode; import com.xly.erp.common.security.SecurityUtil; import com.xly.erp.modules.usr.dto.CreateUserDTO; +import com.xly.erp.modules.usr.dto.UpdateUserDTO; import com.xly.erp.modules.usr.entity.UsrEmployee; import com.xly.erp.modules.usr.entity.UsrPermission; import com.xly.erp.modules.usr.entity.UsrUser; @@ -201,4 +202,76 @@ class UsrUserServiceImplTest { assertThat(service.createUser(dto)).isEqualTo(303); verify(usrEmployeeMapper).selectById(7); } + + // ---------------- REQ-USR-002 T2:目标用户存在性 + 主记录部分更新 ---------------- + + private UpdateUserDTO minimalUpdateDto() { + UpdateUserDTO dto = new UpdateUserDTO(); + dto.setSUserType("普通用户"); + dto.setSLanguage("中文"); + return dto; + } + + @Test + void updateNonExistentUserThrows40401() { + when(usrUserMapper.selectById(404)).thenReturn(null); + + assertThatThrownBy(() -> service.updateUser(404, minimalUpdateDto())) + .isInstanceOf(BusinessException.class) + .extracting(e -> ((BusinessException) e).getResultCode()) + .isEqualTo(ResultCode.NOT_FOUND); + verify(usrUserMapper, never()).updateById(any(UsrUser.class)); + verify(usrUserPermissionMapper, never()).delete(any(Wrapper.class)); + verify(usrUserPermissionMapper, never()).insert(any(UsrUserPermission.class)); + } + + @Test + void updateAppliesNonNullColumnsAndKeepsIdentityImmutable() { + when(usrUserMapper.selectById(55)).thenReturn(new UsrUser()); + when(usrUserMapper.updateById(any(UsrUser.class))).thenReturn(1); + UpdateUserDTO dto = new UpdateUserDTO(); + dto.setSUserType("超级管理员"); + dto.setSLanguage("英文"); + dto.setICanModifyBill(1); + dto.setIIsVoid(1); + dto.setSUserNo("N9"); + + Integer returned = service.updateUser(55, dto); + + assertThat(returned).isEqualTo(55); + ArgumentCaptor captor = ArgumentCaptor.forClass(UsrUser.class); + verify(usrUserMapper).updateById(captor.capture()); + UsrUser saved = captor.getValue(); + assertThat(saved.getIIncrement()).isEqualTo(55); + assertThat(saved.getSUserType()).isEqualTo("超级管理员"); + assertThat(saved.getSLanguage()).isEqualTo("英文"); + assertThat(saved.getICanModifyBill()).isEqualTo(1); + assertThat(saved.getIIsVoid()).isEqualTo(1); + assertThat(saved.getSUserNo()).isEqualTo("N9"); + // 身份 / 密码 / 审计列不参与 SET(保持 null,依赖 MP null 不更新语义)。 + assertThat(saved.getSUserName()).isNull(); + assertThat(saved.getSPassword()).isNull(); + assertThat(saved.getSCreator()).isNull(); + assertThat(saved.getTCreateDate()).isNull(); + } + + @Test + void nullOptionalColumnsAreNotOverwritten() { + when(usrUserMapper.selectById(77)).thenReturn(new UsrUser()); + when(usrUserMapper.updateById(any(UsrUser.class))).thenReturn(1); + + service.updateUser(77, minimalUpdateDto()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(UsrUser.class); + verify(usrUserMapper).updateById(captor.capture()); + UsrUser saved = captor.getValue(); + assertThat(saved.getIIncrement()).isEqualTo(77); + assertThat(saved.getSUserType()).isEqualTo("普通用户"); + assertThat(saved.getSLanguage()).isEqualTo("中文"); + // 可选列 DTO 为 null → 实体保持 null,MP 不 SET。 + assertThat(saved.getSUserNo()).isNull(); + assertThat(saved.getIEmployeeId()).isNull(); + assertThat(saved.getICanModifyBill()).isNull(); + assertThat(saved.getIIsVoid()).isNull(); + } }