[CLAUDE] App: PE workflow inner steps DTO + UpdateUserPositionLevel CQRS (Chunk B)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m42s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m42s
DTO + Validator + Handler mở rộng cho N-stage admin designer (Mig 18): PeWorkflowAdminFeatures: - PeWorkflowStepInnerStepDto record (Order/Dept/PositionLevel/Name/SlaDays/IsRequired) - PeWorkflowStepDto extend +InnerSteps List - GetPeWorkflowAdminOverviewQuery: Include InnerSteps OrderBy Order + resolve DeptNames cho display - CreatePeWorkflowStepInnerStepInput record - CreatePeWorkflowStepInput extend +InnerSteps (nullable, default null — backward compat existing test PeWorkflowAdminTests positional new()) - Validator child rules cho InnerSteps (Order >=1, DeptId not empty, PositionLevel 1-3, SlaDays >=0) - Handler convert InnerSteps khi build entity (atomic batch insert) UserFeatures: - UserDto +PositionLevel int? field - ListUsers + GetUser handlers map (int?)u.PositionLevel - SetUserPositionLevelCommand + Validator + Handler mirror SetUserBypassReviewCommand pattern (admin set qua UserManager UI) Verify: - dotnet build SolutionErp.slnx 0 error - dotnet test 83 pass (54+29) — no regression - Backward compat: PeWorkflowAdminTests existing 6 test pass (named-arg positional record vẫn work với InnerSteps default null) Pending Chunk C: PurchaseEvaluationWorkflowService.TransitionAsync N-stage logic + legacy 2-stage fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -21,7 +21,8 @@ public record UserDto(
|
||||
Guid? DepartmentId,
|
||||
string? DepartmentName,
|
||||
string? Position,
|
||||
bool CanBypassReview);
|
||||
bool CanBypassReview,
|
||||
int? PositionLevel); // Mig 18 — 1=NV, 2=PP, 3=TP. Null cho admin/system/external user.
|
||||
|
||||
// ========== LIST ==========
|
||||
public record ListUsersQuery : PagedRequest, IRequest<PagedResult<UserDto>>;
|
||||
@ -60,7 +61,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, u.CanBypassReview));
|
||||
items.Add(new UserDto(u.Id, u.Email!, u.FullName, u.IsActive, isLocked, u.CreatedAt, roles.ToList(), u.DepartmentId, deptName, u.Position, u.CanBypassReview, (int?)u.PositionLevel));
|
||||
}
|
||||
|
||||
return new PagedResult<UserDto>(items, total, request.Page, request.PageSize);
|
||||
@ -82,7 +83,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, u.CanBypassReview);
|
||||
return new UserDto(u.Id, u.Email!, u.FullName, u.IsActive, isLocked, u.CreatedAt, roles.ToList(), u.DepartmentId, deptName, u.Position, u.CanBypassReview, (int?)u.PositionLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,3 +289,36 @@ public class SetUserBypassReviewCommandHandler(UserManager<User> userManager)
|
||||
throw new ConflictException(string.Join("; ", result.Errors.Select(e => e.Description)));
|
||||
}
|
||||
}
|
||||
|
||||
// ========== SET POSITION LEVEL (Mig 18) ==========
|
||||
// Admin set cấp chức danh trong phòng cho user phục vụ N-stage workflow inner
|
||||
// step matching. Nullable — null cho admin/system/external user. Chỉ accept
|
||||
// 1 (NV) / 2 (PP) / 3 (TP) hoặc null. Không kiểm role tương ứng — admin
|
||||
// tự chịu trách nhiệm map đúng.
|
||||
public record SetUserPositionLevelCommand(Guid Id, int? PositionLevel) : IRequest;
|
||||
|
||||
public class SetUserPositionLevelCommandValidator : AbstractValidator<SetUserPositionLevelCommand>
|
||||
{
|
||||
public SetUserPositionLevelCommandValidator()
|
||||
{
|
||||
RuleFor(x => x.PositionLevel)
|
||||
.Must(v => v == null || (v >= 1 && v <= 3))
|
||||
.WithMessage("PositionLevel chỉ chấp nhận null hoặc 1=NV, 2=PP, 3=TP.");
|
||||
}
|
||||
}
|
||||
|
||||
public class SetUserPositionLevelCommandHandler(UserManager<User> userManager)
|
||||
: IRequestHandler<SetUserPositionLevelCommand>
|
||||
{
|
||||
public async Task Handle(SetUserPositionLevelCommand request, CancellationToken ct)
|
||||
{
|
||||
var user = await userManager.FindByIdAsync(request.Id.ToString())
|
||||
?? throw new NotFoundException("User", request.Id);
|
||||
user.PositionLevel = request.PositionLevel == null
|
||||
? null
|
||||
: (PositionLevel?)request.PositionLevel.Value;
|
||||
var result = await userManager.UpdateAsync(user);
|
||||
if (!result.Succeeded)
|
||||
throw new ConflictException(string.Join("; ", result.Errors.Select(e => e.Description)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user