From 04cf2a0385ea43bb2fff916ae275a91934b43bbf Mon Sep 17 00:00:00 2001 From: pqhuy1987 Date: Thu, 7 May 2026 19:02:10 +0700 Subject: [PATCH] [CLAUDE] App: Contract workflow inner steps DTO + designer mirror PE (Chunk B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DTO + Validator + Handler mở rộng cho N-stage admin designer Contract (Mig 20). Mirror PE Mig 18 pattern (PeWorkflowAdminFeatures Chunk B). WorkflowAdminFeatures: - WorkflowStepInnerStepDto record (Id, Order, DeptId, DeptName, PositionLevel, Name, SlaDays, IsRequired) - WorkflowStepDto extend +InnerSteps List - GetWorkflowAdminOverviewQueryHandler — Include InnerSteps OrderBy + resolve DeptNames cho display - CreateWorkflowStepInnerStepInput record - CreateWorkflowStepInput extend +InnerSteps (nullable, default null — backward compat existing test code positional new()) - Validator child rules cho InnerSteps (Order ≥1, DeptId not empty, PositionLevel 1-3, SlaDays ≥0) - CreateWorkflowDefinitionCommandHandler — convert InnerSteps khi build entity (atomic batch insert qua nav collection) Verify: dotnet build 0 error, 89 test pass (no regression). Pending Chunk C: ContractWorkflowService.TransitionAsync N-stage logic + legacy 2-stage fallback mirror PE pattern. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Contracts/WorkflowAdminFeatures.cs | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/src/Backend/SolutionErp.Application/Contracts/WorkflowAdminFeatures.cs b/src/Backend/SolutionErp.Application/Contracts/WorkflowAdminFeatures.cs index 40d197a..2a3725f 100644 --- a/src/Backend/SolutionErp.Application/Contracts/WorkflowAdminFeatures.cs +++ b/src/Backend/SolutionErp.Application/Contracts/WorkflowAdminFeatures.cs @@ -17,6 +17,17 @@ 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); + public record WorkflowStepDto( Guid Id, int Order, @@ -24,7 +35,8 @@ public record WorkflowStepDto( string PhaseLabel, string Name, int? SlaDays, - List Approvers); + List Approvers, + List InnerSteps); public record WorkflowDefinitionDto( Guid Id, @@ -84,9 +96,21 @@ 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) + var deptIds = definitions + .SelectMany(d => d.Steps).SelectMany(s => s.InnerSteps) + .Select(i => i.DepartmentId).Distinct().ToList(); + var deptNames = deptIds.Count == 0 + ? new Dictionary() + : await db.Departments.AsNoTracking() + .Where(d => deptIds.Contains(d.Id)) + .ToDictionaryAsync(d => d.Id, d => d.Name, ct); + // Resolve user names for User-kind approvers var userIds = definitions .SelectMany(d => d.Steps) @@ -130,7 +154,16 @@ public class GetWorkflowAdminOverviewQueryHandler( s.Approvers.Select(a => new WorkflowStepApproverDto( (int)a.Kind, a.AssignmentValue, - ResolveDisplay(a, userNames))).ToList() + 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() )).ToList()); var types = Enum.GetValues() @@ -160,12 +193,23 @@ 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); + public record CreateWorkflowStepInput( int Order, int Phase, string Name, int? SlaDays, - List Approvers); + List Approvers, + List? InnerSteps = null); public record CreateWorkflowDefinitionCommand( ContractType ContractType, @@ -198,6 +242,16 @@ public class CreateWorkflowDefinitionCommandValidator : AbstractValidator 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); }); } } @@ -242,6 +296,15 @@ public class CreateWorkflowDefinitionCommandHandler(IApplicationDbContext db) 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(), };