[CLAUDE] Domain: Chunk A — Mig 31 swap F2 storage Users→ApprovalWorkflowLevels (Approver scope ChoDuyet)

Mig 31 RefactorSkipToFinalToApproverLevel — 2 stage manual reorder:
- ADD ApprovalWorkflowLevels.AllowApproverSkipToFinal bit NOT NULL DEFAULT 0
- DROP Users.AllowDrafterSkipToFinal (semantic mới khác hẳn — admin re-config qua Designer)
- NO BACKFILL (Option A — accept lose 4 prod user value per K0-bis audit)

Plan K refactor F2 semantic: Drafter from Nháp → Approver during ChoDuyet skip thẳng Cấp cuối.
Mirror F3+F4 admin opt-in per-Approver-slot pattern (Mig 29 + Mig 30) reinforced 3× cumulative.

Service line 121-157 F2 Drafter SUBMIT branch REMOVED stub (K2 sẽ add Approver F2 branch
trong APPROVE STEP line ~393-525). TransitionAsync skipToFinal param 8th KEPT cho K2 repurpose.

Application layer compile-break fix transient: UserDto field mapping + GET handler + LIST
handler + SetUserAllowDrafterSkipToFinalCommandHandler NoOp + PurchaseEvaluationFeatures
drafter flag → sentinel false. DTO + Command signature UNCHANGED (K2 chunk Chủ trì sẽ
refactor DTO/Command theo plan).

4 prod user (fin.pp + pm.nv + nv.test + truong.nguyen) lose AllowDrafterSkipToFinal=true
per bro Option A. Audit trail trong session log K8.

Verify:
- dotnet ef migrations add pass
- dotnet ef database update Dev + Design pass (Mig 31 applied both DB)
- dotnet build src/Backend/SolutionErp.Api production projects clean (0 err, 0 warn)
- dotnet test SKIPPED per UAT mode (memory feedback_uat_skip_verify) — K7 chunk fix
  remaining PurchaseEvaluationWorkflowServiceReturnModeTests.cs:253 reference

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-14 23:03:05 +07:00
parent 56868bfd7f
commit db6625304a
10 changed files with 4039 additions and 63 deletions

View File

@ -62,7 +62,9 @@ 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, (int?)u.PositionLevel, u.AllowDrafterSkipToFinal));
// Mig 31 (S23 t1 K1) — F2 storage moved to ApprovalWorkflowLevels.
// DTO field kept transiently (K2 sẽ refactor DTO + drop field). Sentinel false.
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, false));
}
return new PagedResult<UserDto>(items, total, request.Page, request.PageSize);
@ -84,7 +86,9 @@ 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, (int?)u.PositionLevel, u.AllowDrafterSkipToFinal);
// Mig 31 (S23 t1 K1) — F2 storage moved to ApprovalWorkflowLevels.
// DTO field kept transiently (K2 sẽ refactor DTO + drop field). Sentinel false.
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, false);
}
}
@ -336,11 +340,11 @@ public class SetUserAllowDrafterSkipToFinalCommandHandler(UserManager<User> user
{
public async Task Handle(SetUserAllowDrafterSkipToFinalCommand request, CancellationToken ct)
{
var user = await userManager.FindByIdAsync(request.Id.ToString())
// Mig 31 (S23 t1 K1) — F2 storage moved to ApprovalWorkflowLevels.AllowApproverSkipToFinal
// (per-Approver slot, set via Workflow Designer). Command kept transiently NoOp until K2
// sẽ replace với SetApprovalWorkflowLevelAllowApproverSkipToFinalCommand + FE remove toggle.
_ = await userManager.FindByIdAsync(request.Id.ToString())
?? throw new NotFoundException("User", request.Id);
user.AllowDrafterSkipToFinal = request.AllowDrafterSkipToFinal;
var result = await userManager.UpdateAsync(user);
if (!result.Succeeded)
throw new ConflictException(string.Join("; ", result.Errors.Select(e => e.Description)));
await Task.CompletedTask;
}
}