[CLAUDE] App+FE-Admin: Chunk E3 — UserManager toggle CanBypassReview

Admin UI bật/tắt CanBypassReview per user (Migration 16):
- BE: UserDto thêm field CanBypassReview (List + Get queries)
- FE: User type thêm canBypassReview field
- UsersPage: column "Bypass" badge fuchsia khi true + button toggle ShieldCheck
  (icon highlight fuchsia khi enabled, slate khi disabled)
- bypassMut PATCH /users/{id}/bypass-review { canBypassReview: !current }

Use case: phòng ban không có TPB hoặc TPB ủy quyền cho 1 NV cụ thể —
NV được Stage=Confirm trực tiếp (skip Stage Review), IsBypassed=true ghi audit.

Endpoint backend đã có sẵn ở Chunk E1 (commit 3c49316). Chỉ wire FE.

fe-user KHÔNG có UsersPage (admin-only function) — chỉ update fe-admin.

Build: BE pass + FE-admin pass + 77 test pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-04 13:38:09 +07:00
parent f8eebd57d1
commit 4380bdc075
3 changed files with 42 additions and 4 deletions

View File

@ -20,7 +20,8 @@ public record UserDto(
List<string> Roles,
Guid? DepartmentId,
string? DepartmentName,
string? Position);
string? Position,
bool CanBypassReview);
// ========== LIST ==========
public record ListUsersQuery : PagedRequest, IRequest<PagedResult<UserDto>>;
@ -59,7 +60,7 @@ public class ListUsersQueryHandler(UserManager<User> userManager, IApplicationDb
var roles = await userManager.GetRolesAsync(u);
var isLocked = u.LockoutEnd.HasValue && u.LockoutEnd.Value.UtcDateTime > now;
string? deptName = u.DepartmentId is { } did && deptNames.TryGetValue(did, out var dn) ? dn : null;
items.Add(new UserDto(u.Id, u.Email!, u.FullName, u.IsActive, isLocked, u.CreatedAt, roles.ToList(), u.DepartmentId, deptName, u.Position));
items.Add(new UserDto(u.Id, u.Email!, u.FullName, u.IsActive, isLocked, u.CreatedAt, roles.ToList(), u.DepartmentId, deptName, u.Position, u.CanBypassReview));
}
return new PagedResult<UserDto>(items, total, request.Page, request.PageSize);
@ -81,7 +82,7 @@ public class GetUserQueryHandler(UserManager<User> userManager, IApplicationDbCo
string? deptName = null;
if (u.DepartmentId is { } did)
deptName = await db.Departments.AsNoTracking().Where(d => d.Id == did).Select(d => d.Name).FirstOrDefaultAsync(ct);
return new UserDto(u.Id, u.Email!, u.FullName, u.IsActive, isLocked, u.CreatedAt, roles.ToList(), u.DepartmentId, deptName, u.Position);
return new UserDto(u.Id, u.Email!, u.FullName, u.IsActive, isLocked, u.CreatedAt, roles.ToList(), u.DepartmentId, deptName, u.Position, u.CanBypassReview);
}
}