[CLAUDE] Domain+Infra: Migration 18 PE workflow inner steps + User.PositionLevel (Chunk A)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled

N-stage workflow approval — mỗi WorkflowStep cha (= 1 phase) cấu hình
được chuỗi InnerSteps con theo Department × PositionLevel với Order
sequential. Phase 9+ feature, mở rộng từ 2-stage Mig 16.

Schema:
- enum PositionLevel { NhanVien=1, PhoPhong=2, TruongPhong=3 } (Domain/Identity)
- ALTER Users + PositionLevel int? NULL (admin/system user vẫn null)
- CREATE TABLE PurchaseEvaluationWorkflowStepInnerSteps:
  Id PK, PurchaseEvaluationWorkflowStepId FK Cascade,
  Order int, DepartmentId FK Restrict, PositionLevel int,
  Name nvarchar(200), SlaDays int?, IsRequired bit
- ALTER PurchaseEvaluationDepartmentApprovals + InnerStepId Guid? FK Restrict
  (null cho data legacy 2-stage Review/Confirm Mig 16)

Backward compat: step KHÔNG có InnerSteps → service fallback logic
2-stage Stage=Review|Confirm cũ (Chunk C). Data Mig 16 hiện có giữ
nguyên, InnerStepId=null.

Verify:
- dotnet build SolutionErp.slnx pass (0 error, 2 pre-existing warning DocxRenderer)
- dotnet ef database update LocalDB applied OK
- dotnet test SolutionErp.slnx 83 pass (54 Domain + 29 Infra) — no regression
- 3-file rule: Migration.cs + Designer.cs + Snapshot updated

Pending Chunk B: Application CQRS — extend CreatePeWorkflowDefinitionCommand
với InnerSteps DTO + UpdateUserPositionLevelCommand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-07 18:11:42 +07:00
parent 130903fe1b
commit 13ab533fe7
10 changed files with 3944 additions and 1 deletions

View File

@ -0,0 +1,16 @@
namespace SolutionErp.Domain.Identity;
// Cấp chức danh trong phòng ban — phục vụ N-stage workflow inner step (Mig 18).
// Mỗi WorkflowStep cha (= 1 phase) có thể cấu hình 1 chuỗi inner step
// con (Order asc) khớp Department × PositionLevel. Ai đúng phòng + đúng cấp
// (+ Order tiếp theo chưa duyệt) thì duyệt được.
//
// Default null cho admin/system/external user không thuộc cấp cụ thể.
// CanBypassReview=true ở User cho phép cấp cao hơn (TP) skip cấp dưới
// (NV+PP) cùng Department khi duyệt 1 inner step block.
public enum PositionLevel
{
NhanVien = 1, // NV
PhoPhong = 2, // PP
TruongPhong = 3, // TP
}

View File

@ -18,6 +18,13 @@ public class User : IdentityUser<Guid>
// 2-stage department approval (Phase 9 — Migration 16): khi true, NV
// được quyền duyệt thay TPB (skip Stage Review, đẩy thẳng Stage Confirm).
// Cũng dùng cho N-stage inner step (Mig 18) — TP có CanBypassReview
// skip NV+PP cùng Department.
// Mặc định false (an toàn). Admin set ở UserManager UI.
public bool CanBypassReview { get; set; }
// Cấp chức danh trong phòng — phục vụ N-stage workflow inner step (Mig 18).
// Mỗi inner step yêu cầu user khớp DepartmentId + PositionLevel mới duyệt
// được sub-step đó. Null cho admin/system/external user.
public PositionLevel? PositionLevel { get; set; }
}

View File

@ -15,12 +15,18 @@ public class PurchaseEvaluationDepartmentApproval : AuditableEntity
public Guid PurchaseEvaluationId { get; set; }
public int PhaseAtApproval { get; set; } // snapshot PurchaseEvaluationPhase int
public Guid DepartmentId { get; set; }
public ApprovalStage Stage { get; set; } // 1=Review (NV), 2=Confirm (TPB)
public ApprovalStage Stage { get; set; } // 1=Review (NV), 2=Confirm (TPB) — legacy 2-stage Mig 16
public Guid ApproverUserId { get; set; }
public string? ApproverRoleSnapshot { get; set; }
public string? Comment { get; set; }
public DateTime ApprovedAt { get; set; }
public bool IsBypassed { get; set; }
// N-stage inner step link (Mig 18) — null cho data legacy 2-stage Review/Confirm.
// Có giá trị khi step cha có InnerSteps configured → mỗi sub-step approve =
// 1 row riêng với InnerStepId set. Cùng Stage=Confirm (legacy field giữ nguyên
// cho backward compat — N-stage không dùng Review/Confirm semantics).
public Guid? InnerStepId { get; set; }
public PurchaseEvaluation? PurchaseEvaluation { get; set; }
}

View File

@ -1,5 +1,6 @@
using SolutionErp.Domain.Common;
using SolutionErp.Domain.Contracts; // reuse WorkflowApproverKind
using SolutionErp.Domain.Identity; // reuse PositionLevel
namespace SolutionErp.Domain.PurchaseEvaluations;
@ -34,6 +35,12 @@ public class PurchaseEvaluationWorkflowStep : BaseEntity
public PurchaseEvaluationWorkflowDefinition? Definition { get; set; }
public List<PurchaseEvaluationWorkflowStepApprover> Approvers { get; set; } = new();
// Inner steps (Mig 18) — N-stage approval cấu hình động trong cùng 1 phase.
// Empty list → fallback logic 2-stage Review/Confirm legacy (Mig 16) per dept.
// Có item → service loop theo Order: user khớp Department × PositionLevel
// duyệt sub-step. Tất cả required InnerSteps Done → cho phase transition.
public List<PurchaseEvaluationWorkflowStepInnerStep> InnerSteps { get; set; } = new();
}
public class PurchaseEvaluationWorkflowStepApprover : BaseEntity
@ -44,3 +51,24 @@ public class PurchaseEvaluationWorkflowStepApprover : BaseEntity
public PurchaseEvaluationWorkflowStep? Step { get; set; }
}
// Inner step (Mig 18 — Phase 9+) — sub-step level con cấu hình bên trong 1
// WorkflowStep cha (= 1 phase). Cho phép admin định nghĩa thứ tự duyệt N-stage
// theo Department × PositionLevel: NV.A → PP.A → TP.A → NV.B → PP.B → TP.B → ...
//
// User khớp DepartmentId + PositionLevel + Order tiếp theo chưa duyệt = approver
// hợp lệ. CanBypassReview ở User cho TP skip NV+PP cùng dept (audit IsBypassed).
//
// IsRequired=false → cho phép skip không cần row approval (vd "PP optional").
public class PurchaseEvaluationWorkflowStepInnerStep : BaseEntity
{
public Guid PurchaseEvaluationWorkflowStepId { get; set; }
public int Order { get; set; } // thứ tự sequential trong cùng step cha
public Guid DepartmentId { get; set; }
public PositionLevel PositionLevel { get; set; } // NV / PP / TP
public string? Name { get; set; } // hiển thị FE — vd "NV.PRO duyệt"
public int? SlaDays { get; set; } // override step.SlaDays nếu set
public bool IsRequired { get; set; } = true; // false → optional skip không cần row
public PurchaseEvaluationWorkflowStep? Step { get; set; }
}