Commit 81a263b909a3f3c174838b87208cc6bba01b70be

Authored by zichun
1 parent 704d75a4

fix(usr): 修复 review round 1 must-fix REQ-USR-002

M2: UserUpdateReqDTO 添加 @NotBlank/@Pattern/@NotNull 字段校验
M3: UserFormDrawer InitialData 补 permGroupIds 字段并初始化
M4: 列表查询加 bCanEditDoc 返回字段,编辑抽屉传真实值而非 false
M1: 补充 updateUser_selfAdminKeepType_doesNotThrow 测试
backend/src/main/java/com/example/erp/module/usr/dto/UserUpdateReqDTO.java
1 1 package com.example.erp.module.usr.dto;
2 2  
  3 +import jakarta.validation.constraints.NotBlank;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import jakarta.validation.constraints.Pattern;
3 6 import lombok.Getter;
4 7 import lombok.Setter;
5 8  
... ... @@ -9,10 +12,16 @@ import java.util.List;
9 12 @Setter
10 13 public class UserUpdateReqDTO {
11 14  
  15 + @NotBlank(message = "用户类型不能为空")
  16 + @Pattern(regexp = "普通用户|超级管理员", message = "用户类型无效")
12 17 private String userType;
  18 +
  19 + @NotBlank(message = "语言不能为空")
  20 + @Pattern(regexp = "中文|英文|繁体", message = "语言无效")
13 21 private String language;
14 22 private boolean canEditDoc;
15 23 private boolean isDisabled;
16 24 private String employeeId;
  25 + @NotNull(message = "权限组列表不能为空")
17 26 private List<String> permGroupIds;
18 27 }
... ...
backend/src/main/java/com/example/erp/module/usr/vo/UserListItemVO.java
... ... @@ -25,6 +25,9 @@ public class UserListItemVO {
25 25 @JsonProperty("sLanguage")
26 26 private String sLanguage;
27 27  
  28 + @JsonProperty("bCanEditDoc")
  29 + private Integer bCanEditDoc;
  30 +
28 31 @JsonProperty("bIsDisabled")
29 32 private Integer bIsDisabled;
30 33  
... ...
backend/src/main/resources/mapper/UsrUserMapper.xml
... ... @@ -6,7 +6,7 @@
6 6 <!-- REQ-USR-003: 查询用户 -->
7 7 <select id="selectUserList" resultType="com.example.erp.module.usr.vo.UserListItemVO">
8 8 SELECT u.sId, u.sUsername, u.sUserCode, u.sUserType, u.sLanguage,
9   - u.bIsDisabled, u.tLastLoginDate, u.sCreatorUsername, u.tCreateDate,
  9 + u.bCanEditDoc, u.bIsDisabled, u.tLastLoginDate, u.sCreatorUsername, u.tCreateDate,
10 10 s.sStaffName, s.sDepartment
11 11 FROM usr_user u
12 12 LEFT JOIN tStaff s ON u.sEmployeeId = s.sId
... ...
backend/src/test/java/com/example/erp/module/usr/UserServiceTest.java
... ... @@ -233,6 +233,22 @@ class UserServiceTest {
233 233 }
234 234  
235 235 @Test
  236 + void updateUser_selfAdminKeepType_doesNotThrow() {
  237 + UserUpdateReqDTO dto = new UserUpdateReqDTO();
  238 + dto.setUserType("超级管理员");
  239 + dto.setLanguage("中文");
  240 + dto.setPermGroupIds(List.of());
  241 + UsrUserEntity existing = new UsrUserEntity();
  242 + existing.setSId("u1");
  243 + existing.setSUsername("admin");
  244 + when(userMapper.selectOne(any())).thenReturn(existing);
  245 + when(userMapper.update(any(), any())).thenReturn(1);
  246 +
  247 + // 自己保持 超级管理员 角色 → 不应抛 40301
  248 + assertDoesNotThrow(() -> userService.updateUser("u1", dto, superAdmin));
  249 + }
  250 +
  251 + @Test
236 252 void updateUser_invalidEmployee_throws40001() {
237 253 UserUpdateReqDTO dto = new UserUpdateReqDTO();
238 254 dto.setUserType("普通用户");
... ...
docs/superpowers/reviews/2026-05-08-REQ-USR-002.md 0 → 100644
  1 +---
  2 +req_id: REQ-USR-002
  3 +date: 2026-05-08
  4 +round: 1
  5 +reviewer: superpower-code-reviewer
  6 +---
  7 +
  8 +# Review: REQ-USR-002 — round 1
  9 +
  10 +## 结论
  11 +request-changes
  12 +
  13 +## Must-fix
  14 +- [HIGH] backend/src/main/java/com/example/erp/module/usr/dto/UserUpdateReqDTO.java:12-13 — userType / language 缺少 @NotBlank + @Pattern 校验,非法值直接写库(建议:参照 UserCreateReqDTO 添加 @NotBlank + @Pattern 注解)
  15 +- [HIGH] frontend/src/pages/usr/UserFormDrawer.tsx:8-14 — InitialData 接口缺少 permGroupIds 字段,导致有权限组的用户打开编辑抽屉时权限组无法被预填(建议:添加 permGroupIds?: string[],useEffect 改为 setSelectedPermIds(initialData.permGroupIds ?? []))
  16 +- [HIGH] frontend/src/pages/usr/UserListPage.tsx:124 — canEditDoc 写死为 false,每次保存会将已有用户的 canEditDoc 覆盖为 false(建议:在列表查询中加入 bCanEditDoc,传入 initialData 时使用真实值)
  17 +- [MEDIUM] backend/src/test/java/com/example/erp/module/usr/UserServiceTest.java — 缺少测试 userId==自己 但 userType 仍为 "超级管理员" 时不抛出 40301(建议:补充 updateUser_selfAdminKeepType_doesNotThrow 测试,确认允许路径)
  18 +
  19 +## Nice-to-have
  20 +- backend/.../vo/UserUpdateRespVO.java — updatedAt 建议加 @JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss") 确保序列化格式显式
  21 +- backend/.../dto/UserUpdateReqDTO.java — permGroupIds 建议加 @NotNull,防止调用方传 null 时静默跳过权限组删除
  22 +- frontend/src/test/UserListPage.test.tsx:106 — editMode_submit_callsUpdateUser 只断言调用次数,建议补充 toHaveBeenCalledWith('u1', ...) 验证 userId 传值正确
  23 +
  24 +## 反例 / 测试覆盖缺口
  25 +- 缺 "自己修改自己 userType 但保持 超级管理员" 的无异常路径测试(G1)
  26 +- controller 层无 BizException → 错误码映射测试(G4,40300/40400/40301 未被 controller 测试覆盖)
  27 +- canEditDoc/isDisabled 写入 DB 的 int 映射值(0/1)未在 service happy-path 测试中验证
... ...
frontend/src/api/usr.ts
... ... @@ -54,6 +54,7 @@ export interface UserListItemVO {
54 54 sUserCode: string
55 55 sUserType: string
56 56 sLanguage: string
  57 + bCanEditDoc: number
57 58 bIsDisabled: number
58 59 tLastLoginDate: string | null
59 60 sCreatorUsername: string | null
... ...
frontend/src/pages/usr/UserFormDrawer.tsx
... ... @@ -11,6 +11,7 @@ interface InitialData {
11 11 canEditDoc: boolean
12 12 isDisabled: boolean
13 13 employeeId?: string | null
  14 + permGroupIds?: string[]
14 15 }
15 16  
16 17 interface Props {
... ... @@ -42,7 +43,7 @@ export default function UserFormDrawer({ open, onClose, onSuccess, userId, initi
42 43 isDisabled: initialData.isDisabled,
43 44 employeeId: initialData.employeeId ?? null,
44 45 })
45   - setSelectedPermIds([])
  46 + setSelectedPermIds(initialData.permGroupIds ?? [])
46 47 } else {
47 48 form.resetFields()
48 49 setSelectedPermIds([])
... ...
frontend/src/pages/usr/UserListPage.tsx
... ... @@ -121,7 +121,7 @@ export default function UserListPage() {
121 121 initialData={editingUser ? {
122 122 userType: editingUser.sUserType,
123 123 language: editingUser.sLanguage,
124   - canEditDoc: false,
  124 + canEditDoc: editingUser.bCanEditDoc === 1,
125 125 isDisabled: editingUser.bIsDisabled === 1,
126 126 employeeId: null,
127 127 } : undefined}
... ...
frontend/src/test/UserListPage.test.tsx
... ... @@ -70,7 +70,7 @@ describe(&#39;UserListPage&#39;, () =&gt; {
70 70 total: 1, page: 1, pageSize: 20,
71 71 list: [{
72 72 sId: 'u1', sUsername: 'alice', sUserCode: 'UC001', sUserType: '普通用户',
73   - sLanguage: '中文', bIsDisabled: 0, tLastLoginDate: null,
  73 + sLanguage: '中文', bCanEditDoc: 0, bIsDisabled: 0, tLastLoginDate: null,
74 74 sCreatorUsername: 'admin', tCreateDate: '2026-01-01T00:00:00',
75 75 sStaffName: '张三', sDepartment: '研发部'
76 76 }]
... ... @@ -93,7 +93,7 @@ describe(&#39;UserListPage&#39;, () =&gt; {
93 93 total: 1, page: 1, pageSize: 20,
94 94 list: [{
95 95 sId: 'u1', sUsername: 'alice', sUserCode: 'UC001', sUserType: '普通用户',
96   - sLanguage: '中文', bIsDisabled: 0, tLastLoginDate: null,
  96 + sLanguage: '中文', bCanEditDoc: 0, bIsDisabled: 0, tLastLoginDate: null,
97 97 sCreatorUsername: 'admin', tCreateDate: '2026-01-01T00:00:00',
98 98 sStaffName: null, sDepartment: null
99 99 }]
... ...