[CLAUDE] PE Workflow V2: disable nút Duyệt nếu actor không trong cấp hiện tại
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m14s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m14s
User feedback: "Nếu không đúng bước duyệt thì nút duyệt cho Disable luôn cũng đc."
BE — DTO + Handler populate "Bước/Cấp đang chờ duyệt":
- Application/PurchaseEvaluations/Dtos/PurchaseEvaluationDtos.cs:
+PurchaseEvaluationApprovalLevelApproverDto { UserId, FullName, Email }
+PurchaseEvaluationCurrentApprovalDto { StepIndex, StepName,
StepDepartmentId/Name, LevelOrder, LevelName, Approvers[] }
PurchaseEvaluationDetailBundleDto +CurrentApproval? optional field
- Application/PurchaseEvaluations/PurchaseEvaluationFeatures.cs handler
GetById: khi pin V2 + Phase=ChoDuyet → load AW.Steps.Levels Include
3-level + group by Order = Cấp + resolve user names → populate
CurrentApproval. Null khi V1 legacy hoặc không phải ChoDuyet.
FE — types + PeWorkflowPanel (cả 2 app mirror):
- types/purchaseEvaluation.ts: +PeCurrentApproval + PeCurrentApprovalLevelApprover
+ PeDetail.currentApproval optional
- PeWorkflowPanel:
* Banner V2 hiển thị "Đang chờ Bước N (TênBước · Phòng X) — Cấp K"
+ list NV được duyệt + status emerald (đến lượt) / amber (không phải lượt)
* useAuth() để check currentUser.id ∈ approvers + Admin bypass
* Button "Duyệt forward" disabled khi V2 pin + actor không khớp.
Title tooltip "Cấp K chỉ {NV X / NV Y} mới duyệt được."
* Button "Trả lại" + "Từ chối" vẫn enabled (BE không gating 2 hành
động này theo Cấp — Approver có thể reject bất cứ lúc nào).
* Send-back logic update: target = DangSoanThao OR TraLai (V2 dùng TraLai)
- Admin role bypass mọi check.
Verify: 81 test pass · npm build × 2 OK · BE 0 error.
Test thử:
1. NV X (approver Cấp 1 V2) login → banner emerald "Đến lượt bạn duyệt"
+ nút "✓ Duyệt → ChoDuyet" enabled
2. NV Y (không phải approver) login → banner amber "Không phải lượt
bạn — chỉ NV X mới duyệt được" + nút Duyệt grey disabled, hover tooltip
3. Admin login → bypass, button enabled
This commit is contained in:
@ -84,6 +84,23 @@ public record PurchaseEvaluationWorkflowSummaryDto(
|
||||
List<PurchaseEvaluationPhase> ActivePhases,
|
||||
List<PurchaseEvaluationPhase> NextPhases);
|
||||
|
||||
// Mig 22-24 V2 schema — info "Bước/Cấp đang chờ duyệt" để FE disable nút
|
||||
// Duyệt nếu user không phải approver. Null khi pin V1 legacy hoặc Phase
|
||||
// không phải ChoDuyet.
|
||||
public record PurchaseEvaluationApprovalLevelApproverDto(
|
||||
Guid UserId,
|
||||
string FullName,
|
||||
string? Email);
|
||||
|
||||
public record PurchaseEvaluationCurrentApprovalDto(
|
||||
int StepIndex, // 0-based
|
||||
string StepName,
|
||||
Guid? StepDepartmentId,
|
||||
string? StepDepartmentName,
|
||||
int LevelOrder, // 1/2/3
|
||||
string? LevelName,
|
||||
List<PurchaseEvaluationApprovalLevelApproverDto> Approvers);
|
||||
|
||||
public record PurchaseEvaluationAttachmentDto(
|
||||
Guid Id,
|
||||
Guid? PurchaseEvaluationSupplierId,
|
||||
@ -135,6 +152,7 @@ public record PurchaseEvaluationDetailBundleDto(
|
||||
string? ApprovalWorkflowCode,
|
||||
string? ApprovalWorkflowName,
|
||||
int? ApprovalWorkflowVersion,
|
||||
PurchaseEvaluationCurrentApprovalDto? CurrentApproval,
|
||||
List<PurchaseEvaluationSupplierDto> Suppliers,
|
||||
List<PurchaseEvaluationDetailDto> Details,
|
||||
List<PurchaseEvaluationApprovalDto> Approvals,
|
||||
|
||||
@ -453,19 +453,66 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
|
||||
// Mig 23 — load ApprovalWorkflow V2 info nếu pin (Code/Name/Version
|
||||
// hiển thị FE detail card "QT-DN-V2-001 - Tên (v01)").
|
||||
// Mig 24 — populate CurrentApproval { Bước/Cấp + N approvers } để
|
||||
// FE biết user nào được duyệt cấp hiện tại → disable button đúng.
|
||||
string? awCode = null, awName = null;
|
||||
int? awVersion = null;
|
||||
PurchaseEvaluationCurrentApprovalDto? currentApproval = null;
|
||||
if (e.ApprovalWorkflowId is Guid awId)
|
||||
{
|
||||
var aw = await db.ApprovalWorkflows.AsNoTracking()
|
||||
.Where(w => w.Id == awId)
|
||||
.Select(w => new { w.Code, w.Name, w.Version })
|
||||
.FirstOrDefaultAsync(ct);
|
||||
.Include(w => w.Steps.OrderBy(s => s.Order))
|
||||
.ThenInclude(s => s.Levels.OrderBy(l => l.Order))
|
||||
.FirstOrDefaultAsync(w => w.Id == awId, ct);
|
||||
if (aw is not null)
|
||||
{
|
||||
awCode = aw.Code;
|
||||
awName = aw.Name;
|
||||
awVersion = aw.Version;
|
||||
|
||||
// Compute current approval level info nếu Phase=ChoDuyet
|
||||
if (e.Phase == PurchaseEvaluationPhase.ChoDuyet
|
||||
&& e.CurrentWorkflowStepIndex is int idx
|
||||
&& e.CurrentApprovalLevelOrder is int levelOrder)
|
||||
{
|
||||
var steps = aw.Steps.OrderBy(s => s.Order).ToList();
|
||||
if (idx >= 0 && idx < steps.Count)
|
||||
{
|
||||
var step = steps[idx];
|
||||
string? stepDeptName = null;
|
||||
if (step.DepartmentId is Guid stepDeptId)
|
||||
{
|
||||
stepDeptName = await db.Departments.AsNoTracking()
|
||||
.Where(d => d.Id == stepDeptId)
|
||||
.Select(d => d.Name)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
}
|
||||
var levelGroup = step.Levels.Where(l => l.Order == levelOrder).ToList();
|
||||
var approverIds = levelGroup.Select(l => l.ApproverUserId).Distinct().ToList();
|
||||
var approverInfos = approverIds.Count == 0
|
||||
? new Dictionary<Guid, (string FullName, string? Email)>()
|
||||
: await userManager.Users.AsNoTracking()
|
||||
.Where(u => approverIds.Contains(u.Id))
|
||||
.Select(u => new { u.Id, u.FullName, u.Email })
|
||||
.ToDictionaryAsync(u => u.Id, u => (u.FullName, u.Email), ct);
|
||||
|
||||
var approvers = levelGroup
|
||||
.Select(l =>
|
||||
{
|
||||
approverInfos.TryGetValue(l.ApproverUserId, out var info);
|
||||
return new PurchaseEvaluationApprovalLevelApproverDto(
|
||||
l.ApproverUserId,
|
||||
info.FullName ?? l.ApproverUserId.ToString(),
|
||||
info.Email);
|
||||
})
|
||||
.ToList();
|
||||
var levelName = levelGroup.FirstOrDefault()?.Name;
|
||||
|
||||
currentApproval = new PurchaseEvaluationCurrentApprovalDto(
|
||||
idx, step.Name, step.DepartmentId, stepDeptName,
|
||||
levelOrder, levelName, approvers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,6 +527,7 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
e.BudgetId, budgetSummary,
|
||||
e.BudgetManualName, e.BudgetManualAmount,
|
||||
e.ApprovalWorkflowId, awCode, awName, awVersion,
|
||||
currentApproval,
|
||||
e.Suppliers
|
||||
.OrderBy(s => s.Order)
|
||||
.Select(s => new PurchaseEvaluationSupplierDto(
|
||||
|
||||
Reference in New Issue
Block a user