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 | 1 | package com.xly.erp.modules.usr.service; |
| 2 | 2 | |
| 3 | 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 | 9 | public interface UsrUserService { |
| 9 | 10 | |
| ... | ... | @@ -15,4 +16,16 @@ public interface UsrUserService { |
| 15 | 16 | * @return 新建用户主键 iIncrement |
| 16 | 17 | */ |
| 17 | 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 | 5 | import com.xly.erp.common.response.ResultCode; |
| 6 | 6 | import com.xly.erp.common.security.SecurityUtil; |
| 7 | 7 | import com.xly.erp.modules.usr.dto.CreateUserDTO; |
| 8 | +import com.xly.erp.modules.usr.dto.UpdateUserDTO; | |
| 8 | 9 | import com.xly.erp.modules.usr.entity.UsrUser; |
| 9 | 10 | import com.xly.erp.modules.usr.entity.UsrUserPermission; |
| 10 | 11 | import com.xly.erp.modules.usr.mapper.UsrEmployeeMapper; |
| ... | ... | @@ -129,4 +130,37 @@ public class UsrUserServiceImpl implements UsrUserService { |
| 129 | 130 | |
| 130 | 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 | 14 | import com.xly.erp.common.response.ResultCode; |
| 15 | 15 | import com.xly.erp.common.security.SecurityUtil; |
| 16 | 16 | import com.xly.erp.modules.usr.dto.CreateUserDTO; |
| 17 | +import com.xly.erp.modules.usr.dto.UpdateUserDTO; | |
| 17 | 18 | import com.xly.erp.modules.usr.entity.UsrEmployee; |
| 18 | 19 | import com.xly.erp.modules.usr.entity.UsrPermission; |
| 19 | 20 | import com.xly.erp.modules.usr.entity.UsrUser; |
| ... | ... | @@ -201,4 +202,76 @@ class UsrUserServiceImplTest { |
| 201 | 202 | assertThat(service.createUser(dto)).isEqualTo(303); |
| 202 | 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 | } | ... | ... |