[CLAUDE] Drastic refactor: flat workflow Phòng × Cấp + Migration 21 (Chunk A)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m18s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m18s
User chốt drastic refactor — bỏ phase enum hoàn toàn, dùng ChoDuyet=10 đơn nhất + currentStepIndex tracking. Workflow flat list (Phòng × Cấp × Approvers). Mỗi PE/HĐ pin WorkflowDefinitionId chạy hết quy trình đó. Schema (Migration 21 `RefactorWorkflowToFlatModel`): - Phase enum +ChoDuyet=10 (PE + Contract). Legacy 2-9 + 98 deprecated. - WorkflowStep + DepartmentId Guid? (FK Restrict) + PositionLevel int? (PE + Contract — mirror). - PE/Contract + CurrentWorkflowStepIndex int? + RejectedAtStepIndex int? - DROP table PurchaseEvaluationWorkflowStepInnerSteps (Mig 18) - DROP table WorkflowStepInnerSteps (Mig 20) - DROP column ContractDeptApproval.InnerStepId (Mig 20) - DROP column PEDeptApproval.InnerStepId (Mig 18) - DROP filtered indexes (Mig 19/20) + restore simple unique (TargetId, Phase, Dept, Stage) non-filtered Service rewrite (PE + Contract WorkflowService.TransitionAsync): - Phase transitions: DangSoanThao → ChoDuyet (Drafter trình, init idx=0) - ChoDuyet → ChoDuyet (advance idx per approve) - ChoDuyet → DaDuyet/DaPhatHanh (idx >= steps.Count → terminal) - ChoDuyet → DangSoanThao (Trả lại — save RejectedAtStepIndex) - ChoDuyet → TuChoi (Từ chối — khoá vĩnh viễn) - DangSoanThao + RejectedAtStepIndex → ChoDuyet jump-back to saved idx - Approver match: actor.Dept == step.Dept AND actor.PositionLevel >= step.PositionLevel (OR-of-many cùng cấp/dept = pass) OR Approvers.Any(Kind=User AND id match) OR Approvers.Any(Kind=Role AND actorRoles contains) - Admin role bypass policy. Last step done → gen mã HĐ (Contract only) App CQRS: - WorkflowStepDto + WorkflowStepInput drop InnerStep, add DepartmentId + PositionLevel fields. PE + Contract mirror. Tests rewrite: - DROP PeNStageApprovalTests.cs (6 test) + ContractNStageApprovalTests.cs (6 test) + PeTwoStageApprovalTests.cs (7 test) — legacy N-stage/2-stage no longer applicable - UPDATE PeWorkflowAdminTests signature to new flat input - 96 → 77 test pass (drop 19 legacy) Reference Domain entities removed: - WorkflowStepInnerStep (Contract) - PurchaseEvaluationWorkflowStepInnerStep (PE) - DTOs WorkflowStepInnerStepDto / CreateWorkflowStepInnerStepInput per module Memory `feedback_drastic_refactor_scope.md` validated: drastic refactor done in dedicated session với context fresh, scope ~5h actual (planned ~8-10h with 2x buffer). Verify: - dotnet build SolutionErp.slnx 0 error - dotnet ef database update Mig 21 LocalDB applied OK - dotnet test 77 pass (54 Domain + 23 Infra) - 3-file rule: Migration .cs + Designer.cs + Snapshot updated Pending Chunk B: FE Designer flat UI (PeWorkflowsPage + WorkflowsPage). Pending Chunk C: FE PeWorkflowPanel + workflow timeline display. Pending Chunk D: Docs + Skill + Memory + session log. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -17,26 +17,19 @@ public record WorkflowStepApproverDto(
|
||||
string AssignmentValue,
|
||||
string? DisplayName); // resolved role label or user fullName
|
||||
|
||||
// Mig 20 — N-stage approval inner step level con (mirror PE Mig 18)
|
||||
public record WorkflowStepInnerStepDto(
|
||||
Guid Id,
|
||||
int Order,
|
||||
Guid DepartmentId,
|
||||
string? DepartmentName,
|
||||
int PositionLevel, // 1=NV, 2=PP, 3=TP
|
||||
string? Name,
|
||||
int? SlaDays,
|
||||
bool IsRequired);
|
||||
|
||||
// Mig 21 — flat workflow step. Mỗi step = 1 (Phòng × Cấp + Approvers users)
|
||||
// sequential. Service iterate theo Order, advance Contract.CurrentWorkflowStepIndex.
|
||||
public record WorkflowStepDto(
|
||||
Guid Id,
|
||||
int Order,
|
||||
int Phase,
|
||||
int Phase, // [DEPRECATED post-Mig 21] dùng ChoDuyet=10 cho new
|
||||
string PhaseLabel,
|
||||
string Name,
|
||||
string Name, // "Phòng A — Cấp 1"
|
||||
int? SlaDays,
|
||||
List<WorkflowStepApproverDto> Approvers,
|
||||
List<WorkflowStepInnerStepDto> InnerSteps);
|
||||
Guid? DepartmentId, // Mig 21
|
||||
string? DepartmentName, // resolved display
|
||||
int? PositionLevel, // Mig 21 — 1=NV, 2=PP, 3=TP
|
||||
List<WorkflowStepApproverDto> Approvers);
|
||||
|
||||
public record WorkflowDefinitionDto(
|
||||
Guid Id,
|
||||
@ -96,15 +89,15 @@ public class GetWorkflowAdminOverviewQueryHandler(
|
||||
var definitions = await db.WorkflowDefinitions.AsNoTracking()
|
||||
.Include(d => d.Steps.OrderBy(s => s.Order))
|
||||
.ThenInclude(s => s.Approvers)
|
||||
.Include(d => d.Steps)
|
||||
.ThenInclude(s => s.InnerSteps.OrderBy(i => i.Order))
|
||||
.OrderByDescending(d => d.Version)
|
||||
.ToListAsync(ct);
|
||||
|
||||
// Resolve dept names cho InnerStep.DepartmentName display (Mig 20)
|
||||
// Resolve dept names cho step.DepartmentId display (Mig 21)
|
||||
var deptIds = definitions
|
||||
.SelectMany(d => d.Steps).SelectMany(s => s.InnerSteps)
|
||||
.Select(i => i.DepartmentId).Distinct().ToList();
|
||||
.SelectMany(d => d.Steps)
|
||||
.Where(s => s.DepartmentId != null)
|
||||
.Select(s => s.DepartmentId!.Value)
|
||||
.Distinct().ToList();
|
||||
var deptNames = deptIds.Count == 0
|
||||
? new Dictionary<Guid, string>()
|
||||
: await db.Departments.AsNoTracking()
|
||||
@ -151,19 +144,13 @@ public class GetWorkflowAdminOverviewQueryHandler(
|
||||
PhaseLabels.GetValueOrDefault(s.Phase, s.Phase.ToString()),
|
||||
s.Name,
|
||||
s.SlaDays,
|
||||
s.DepartmentId,
|
||||
s.DepartmentId != null ? deptNames.GetValueOrDefault(s.DepartmentId.Value) : null,
|
||||
s.PositionLevel != null ? (int?)s.PositionLevel : null,
|
||||
s.Approvers.Select(a => new WorkflowStepApproverDto(
|
||||
(int)a.Kind,
|
||||
a.AssignmentValue,
|
||||
ResolveDisplay(a, userNames))).ToList(),
|
||||
s.InnerSteps.OrderBy(i => i.Order).Select(i => new WorkflowStepInnerStepDto(
|
||||
i.Id,
|
||||
i.Order,
|
||||
i.DepartmentId,
|
||||
deptNames.GetValueOrDefault(i.DepartmentId),
|
||||
(int)i.PositionLevel,
|
||||
i.Name,
|
||||
i.SlaDays,
|
||||
i.IsRequired)).ToList()
|
||||
ResolveDisplay(a, userNames))).ToList()
|
||||
)).ToList());
|
||||
|
||||
var types = Enum.GetValues<ContractType>()
|
||||
@ -193,23 +180,16 @@ public class GetWorkflowAdminOverviewQueryHandler(
|
||||
|
||||
public record CreateWorkflowStepApproverInput(int Kind, string AssignmentValue);
|
||||
|
||||
// Mig 20 — Inner step input cho designer N-stage. InnerSteps optional empty
|
||||
// list → service fallback 2-stage Review/Confirm logic legacy Mig 16.
|
||||
public record CreateWorkflowStepInnerStepInput(
|
||||
int Order,
|
||||
Guid DepartmentId,
|
||||
int PositionLevel, // 1=NV, 2=PP, 3=TP
|
||||
string? Name,
|
||||
int? SlaDays,
|
||||
bool IsRequired);
|
||||
|
||||
// Mig 21 — flat workflow step input. DeptId + PositionLevel = Phòng × Cấp.
|
||||
// Phase auto-assign ChoDuyet=10 cho new definitions (legacy phase-specific deprecated).
|
||||
public record CreateWorkflowStepInput(
|
||||
int Order,
|
||||
int Phase,
|
||||
int Phase, // [DEPRECATED] caller có thể truyền 10=ChoDuyet hoặc legacy enum value
|
||||
string Name,
|
||||
int? SlaDays,
|
||||
List<CreateWorkflowStepApproverInput> Approvers,
|
||||
List<CreateWorkflowStepInnerStepInput>? InnerSteps = null);
|
||||
Guid? DepartmentId, // Mig 21
|
||||
int? PositionLevel, // Mig 21 — 1=NV, 2=PP, 3=TP
|
||||
List<CreateWorkflowStepApproverInput> Approvers);
|
||||
|
||||
public record CreateWorkflowDefinitionCommand(
|
||||
ContractType ContractType,
|
||||
@ -233,25 +213,18 @@ public class CreateWorkflowDefinitionCommandValidator : AbstractValidator<Create
|
||||
RuleForEach(x => x.Steps).ChildRules(step =>
|
||||
{
|
||||
step.RuleFor(s => s.Order).GreaterThanOrEqualTo(1);
|
||||
step.RuleFor(s => s.Phase).InclusiveBetween(1, 9);
|
||||
step.RuleFor(s => s.Phase).InclusiveBetween(1, 99); // Mig 21 accept ChoDuyet=10
|
||||
step.RuleFor(s => s.Name).NotEmpty().MaximumLength(200);
|
||||
step.RuleFor(s => s.SlaDays).GreaterThanOrEqualTo(0)
|
||||
.When(s => s.SlaDays != null);
|
||||
step.RuleFor(s => s.PositionLevel).InclusiveBetween(1, 3)
|
||||
.When(s => s.PositionLevel != null)
|
||||
.WithMessage("PositionLevel: 1=NV, 2=PP, 3=TP.");
|
||||
step.RuleForEach(s => s.Approvers).ChildRules(app =>
|
||||
{
|
||||
app.RuleFor(a => a.Kind).InclusiveBetween(1, 2);
|
||||
app.RuleFor(a => a.AssignmentValue).NotEmpty().MaximumLength(100);
|
||||
});
|
||||
step.RuleForEach(s => s.InnerSteps!).ChildRules(inner =>
|
||||
{
|
||||
inner.RuleFor(i => i.Order).GreaterThanOrEqualTo(1);
|
||||
inner.RuleFor(i => i.DepartmentId).NotEmpty();
|
||||
inner.RuleFor(i => i.PositionLevel).InclusiveBetween(1, 3)
|
||||
.WithMessage("PositionLevel: 1=NV, 2=PP, 3=TP.");
|
||||
inner.RuleFor(i => i.Name).MaximumLength(200);
|
||||
inner.RuleFor(i => i.SlaDays).GreaterThanOrEqualTo(0)
|
||||
.When(i => i.SlaDays != null);
|
||||
}).When(s => s.InnerSteps != null);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -291,20 +264,13 @@ public class CreateWorkflowDefinitionCommandHandler(IApplicationDbContext db)
|
||||
Phase = (ContractPhase)s.Phase,
|
||||
Name = s.Name,
|
||||
SlaDays = s.SlaDays,
|
||||
DepartmentId = s.DepartmentId,
|
||||
PositionLevel = s.PositionLevel != null ? (PositionLevel)s.PositionLevel : null,
|
||||
Approvers = s.Approvers.Select(a => new WorkflowStepApprover
|
||||
{
|
||||
Kind = (WorkflowApproverKind)a.Kind,
|
||||
AssignmentValue = a.AssignmentValue,
|
||||
}).ToList(),
|
||||
InnerSteps = (s.InnerSteps ?? new()).OrderBy(i => i.Order).Select(i => new WorkflowStepInnerStep
|
||||
{
|
||||
Order = i.Order,
|
||||
DepartmentId = i.DepartmentId,
|
||||
PositionLevel = (PositionLevel)i.PositionLevel,
|
||||
Name = i.Name,
|
||||
SlaDays = i.SlaDays,
|
||||
IsRequired = i.IsRequired,
|
||||
}).ToList(),
|
||||
})
|
||||
.ToList(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user