Commit 13500fafe38ebc601d2933104d0e244cb80b3bcb
1 parent
26bef0e5
feat(usr): 修改用户主记录部分更新与存在性校验 REQ-USR-002
Showing
3 changed files
with
121 additions
and
1 deletions
backend/src/main/java/com/xly/erp/modules/usr/service/UsrUserService.java
| 1 | package com.xly.erp.modules.usr.service; | 1 | package com.xly.erp.modules.usr.service; |
| 2 | 2 | ||
| 3 | import com.xly.erp.modules.usr.dto.CreateUserDTO; | 3 | import com.xly.erp.modules.usr.dto.CreateUserDTO; |
| 4 | +import com.xly.erp.modules.usr.dto.UpdateUserDTO; | ||
| 4 | 5 | ||
| 5 | /** | 6 | /** |
| 6 | - * 用户业务服务(docs/04 § 1.2)。REQ-USR-001。 | 7 | + * 用户业务服务(docs/04 § 1.2)。REQ-USR-001 / REQ-USR-002。 |
| 7 | */ | 8 | */ |
| 8 | public interface UsrUserService { | 9 | public interface UsrUserService { |
| 9 | 10 | ||
| @@ -15,4 +16,16 @@ public interface UsrUserService { | @@ -15,4 +16,16 @@ public interface UsrUserService { | ||
| 15 | * @return 新建用户主键 iIncrement | 16 | * @return 新建用户主键 iIncrement |
| 16 | */ | 17 | */ |
| 17 | Integer createUser(CreateUserDTO dto); | 18 | Integer createUser(CreateUserDTO dto); |
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 修改用户(REQ-USR-002):校验目标用户存在 → 校验关联职员 / 权限存在 → | ||
| 22 | + * 按"部分更新(null 不改)"语义更新 {@code usr_user} 主记录(不动 sUserName / | ||
| 23 | + * sPassword / 审计列 / 租户列)→ 按"全量覆盖"语义重写该用户在 {@code usr_user_permission} | ||
| 24 | + * 的授权。整体单事务。 | ||
| 25 | + * | ||
| 26 | + * @param id 目标用户主键 iIncrement | ||
| 27 | + * @param dto 修改用户入参 | ||
| 28 | + * @return 被修改用户主键 iIncrement(等于入参 id) | ||
| 29 | + */ | ||
| 30 | + Integer updateUser(Integer id, UpdateUserDTO dto); | ||
| 18 | } | 31 | } |
backend/src/main/java/com/xly/erp/modules/usr/service/impl/UsrUserServiceImpl.java
| @@ -5,6 +5,7 @@ import com.xly.erp.common.exception.BusinessException; | @@ -5,6 +5,7 @@ import com.xly.erp.common.exception.BusinessException; | ||
| 5 | import com.xly.erp.common.response.ResultCode; | 5 | import com.xly.erp.common.response.ResultCode; |
| 6 | import com.xly.erp.common.security.SecurityUtil; | 6 | import com.xly.erp.common.security.SecurityUtil; |
| 7 | import com.xly.erp.modules.usr.dto.CreateUserDTO; | 7 | import com.xly.erp.modules.usr.dto.CreateUserDTO; |
| 8 | +import com.xly.erp.modules.usr.dto.UpdateUserDTO; | ||
| 8 | import com.xly.erp.modules.usr.entity.UsrUser; | 9 | import com.xly.erp.modules.usr.entity.UsrUser; |
| 9 | import com.xly.erp.modules.usr.entity.UsrUserPermission; | 10 | import com.xly.erp.modules.usr.entity.UsrUserPermission; |
| 10 | import com.xly.erp.modules.usr.mapper.UsrEmployeeMapper; | 11 | import com.xly.erp.modules.usr.mapper.UsrEmployeeMapper; |
| @@ -129,4 +130,37 @@ public class UsrUserServiceImpl implements UsrUserService { | @@ -129,4 +130,37 @@ public class UsrUserServiceImpl implements UsrUserService { | ||
| 129 | 130 | ||
| 130 | return newUserId; | 131 | return newUserId; |
| 131 | } | 132 | } |
| 133 | + | ||
| 134 | + @Override | ||
| 135 | + @Transactional(rollbackFor = Exception.class) | ||
| 136 | + public Integer updateUser(Integer id, UpdateUserDTO dto) { | ||
| 137 | + // 1. 目标用户存在性校验(不存在 → 40401,不写入任何表)。 | ||
| 138 | + UsrUser existing = usrUserMapper.selectById(id); | ||
| 139 | + if (existing == null) { | ||
| 140 | + throw new BusinessException(ResultCode.NOT_FOUND); | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + // 2. 组装目标实体:仅 set 主键 + 非 null 可更新列。 | ||
| 144 | + // 不 set sUserName / sPassword / sCreator / tCreateDate / 租户列, | ||
| 145 | + // 依赖 MP updateById「null 字段不参与 SET」语义保持原值不被覆盖。 | ||
| 146 | + UsrUser target = new UsrUser(); | ||
| 147 | + target.setIIncrement(id); | ||
| 148 | + target.setSUserType(dto.getSUserType()); | ||
| 149 | + target.setSLanguage(dto.getSLanguage()); | ||
| 150 | + if (dto.getSUserNo() != null) { | ||
| 151 | + target.setSUserNo(dto.getSUserNo()); | ||
| 152 | + } | ||
| 153 | + if (dto.getIEmployeeId() != null) { | ||
| 154 | + target.setIEmployeeId(dto.getIEmployeeId()); | ||
| 155 | + } | ||
| 156 | + if (dto.getICanModifyBill() != null) { | ||
| 157 | + target.setICanModifyBill(dto.getICanModifyBill()); | ||
| 158 | + } | ||
| 159 | + if (dto.getIIsVoid() != null) { | ||
| 160 | + target.setIIsVoid(dto.getIIsVoid()); | ||
| 161 | + } | ||
| 162 | + usrUserMapper.updateById(target); | ||
| 163 | + | ||
| 164 | + return id; | ||
| 165 | + } | ||
| 132 | } | 166 | } |
backend/src/test/java/com/xly/erp/modules/usr/service/UsrUserServiceImplTest.java
| @@ -14,6 +14,7 @@ import com.xly.erp.common.exception.BusinessException; | @@ -14,6 +14,7 @@ import com.xly.erp.common.exception.BusinessException; | ||
| 14 | import com.xly.erp.common.response.ResultCode; | 14 | import com.xly.erp.common.response.ResultCode; |
| 15 | import com.xly.erp.common.security.SecurityUtil; | 15 | import com.xly.erp.common.security.SecurityUtil; |
| 16 | import com.xly.erp.modules.usr.dto.CreateUserDTO; | 16 | import com.xly.erp.modules.usr.dto.CreateUserDTO; |
| 17 | +import com.xly.erp.modules.usr.dto.UpdateUserDTO; | ||
| 17 | import com.xly.erp.modules.usr.entity.UsrEmployee; | 18 | import com.xly.erp.modules.usr.entity.UsrEmployee; |
| 18 | import com.xly.erp.modules.usr.entity.UsrPermission; | 19 | import com.xly.erp.modules.usr.entity.UsrPermission; |
| 19 | import com.xly.erp.modules.usr.entity.UsrUser; | 20 | import com.xly.erp.modules.usr.entity.UsrUser; |
| @@ -201,4 +202,76 @@ class UsrUserServiceImplTest { | @@ -201,4 +202,76 @@ class UsrUserServiceImplTest { | ||
| 201 | assertThat(service.createUser(dto)).isEqualTo(303); | 202 | assertThat(service.createUser(dto)).isEqualTo(303); |
| 202 | verify(usrEmployeeMapper).selectById(7); | 203 | verify(usrEmployeeMapper).selectById(7); |
| 203 | } | 204 | } |
| 205 | + | ||
| 206 | + // ---------------- REQ-USR-002 T2:目标用户存在性 + 主记录部分更新 ---------------- | ||
| 207 | + | ||
| 208 | + private UpdateUserDTO minimalUpdateDto() { | ||
| 209 | + UpdateUserDTO dto = new UpdateUserDTO(); | ||
| 210 | + dto.setSUserType("普通用户"); | ||
| 211 | + dto.setSLanguage("中文"); | ||
| 212 | + return dto; | ||
| 213 | + } | ||
| 214 | + | ||
| 215 | + @Test | ||
| 216 | + void updateNonExistentUserThrows40401() { | ||
| 217 | + when(usrUserMapper.selectById(404)).thenReturn(null); | ||
| 218 | + | ||
| 219 | + assertThatThrownBy(() -> service.updateUser(404, minimalUpdateDto())) | ||
| 220 | + .isInstanceOf(BusinessException.class) | ||
| 221 | + .extracting(e -> ((BusinessException) e).getResultCode()) | ||
| 222 | + .isEqualTo(ResultCode.NOT_FOUND); | ||
| 223 | + verify(usrUserMapper, never()).updateById(any(UsrUser.class)); | ||
| 224 | + verify(usrUserPermissionMapper, never()).delete(any(Wrapper.class)); | ||
| 225 | + verify(usrUserPermissionMapper, never()).insert(any(UsrUserPermission.class)); | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + @Test | ||
| 229 | + void updateAppliesNonNullColumnsAndKeepsIdentityImmutable() { | ||
| 230 | + when(usrUserMapper.selectById(55)).thenReturn(new UsrUser()); | ||
| 231 | + when(usrUserMapper.updateById(any(UsrUser.class))).thenReturn(1); | ||
| 232 | + UpdateUserDTO dto = new UpdateUserDTO(); | ||
| 233 | + dto.setSUserType("超级管理员"); | ||
| 234 | + dto.setSLanguage("英文"); | ||
| 235 | + dto.setICanModifyBill(1); | ||
| 236 | + dto.setIIsVoid(1); | ||
| 237 | + dto.setSUserNo("N9"); | ||
| 238 | + | ||
| 239 | + Integer returned = service.updateUser(55, dto); | ||
| 240 | + | ||
| 241 | + assertThat(returned).isEqualTo(55); | ||
| 242 | + ArgumentCaptor<UsrUser> captor = ArgumentCaptor.forClass(UsrUser.class); | ||
| 243 | + verify(usrUserMapper).updateById(captor.capture()); | ||
| 244 | + UsrUser saved = captor.getValue(); | ||
| 245 | + assertThat(saved.getIIncrement()).isEqualTo(55); | ||
| 246 | + assertThat(saved.getSUserType()).isEqualTo("超级管理员"); | ||
| 247 | + assertThat(saved.getSLanguage()).isEqualTo("英文"); | ||
| 248 | + assertThat(saved.getICanModifyBill()).isEqualTo(1); | ||
| 249 | + assertThat(saved.getIIsVoid()).isEqualTo(1); | ||
| 250 | + assertThat(saved.getSUserNo()).isEqualTo("N9"); | ||
| 251 | + // 身份 / 密码 / 审计列不参与 SET(保持 null,依赖 MP null 不更新语义)。 | ||
| 252 | + assertThat(saved.getSUserName()).isNull(); | ||
| 253 | + assertThat(saved.getSPassword()).isNull(); | ||
| 254 | + assertThat(saved.getSCreator()).isNull(); | ||
| 255 | + assertThat(saved.getTCreateDate()).isNull(); | ||
| 256 | + } | ||
| 257 | + | ||
| 258 | + @Test | ||
| 259 | + void nullOptionalColumnsAreNotOverwritten() { | ||
| 260 | + when(usrUserMapper.selectById(77)).thenReturn(new UsrUser()); | ||
| 261 | + when(usrUserMapper.updateById(any(UsrUser.class))).thenReturn(1); | ||
| 262 | + | ||
| 263 | + service.updateUser(77, minimalUpdateDto()); | ||
| 264 | + | ||
| 265 | + ArgumentCaptor<UsrUser> captor = ArgumentCaptor.forClass(UsrUser.class); | ||
| 266 | + verify(usrUserMapper).updateById(captor.capture()); | ||
| 267 | + UsrUser saved = captor.getValue(); | ||
| 268 | + assertThat(saved.getIIncrement()).isEqualTo(77); | ||
| 269 | + assertThat(saved.getSUserType()).isEqualTo("普通用户"); | ||
| 270 | + assertThat(saved.getSLanguage()).isEqualTo("中文"); | ||
| 271 | + // 可选列 DTO 为 null → 实体保持 null,MP 不 SET。 | ||
| 272 | + assertThat(saved.getSUserNo()).isNull(); | ||
| 273 | + assertThat(saved.getIEmployeeId()).isNull(); | ||
| 274 | + assertThat(saved.getICanModifyBill()).isNull(); | ||
| 275 | + assertThat(saved.getIIsVoid()).isNull(); | ||
| 276 | + } | ||
| 204 | } | 277 | } |