[CLAUDE] App: Contract workflow inner steps DTO + designer mirror PE (Chunk B)
Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Has been cancelled

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) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-05-07 19:02:10 +07:00
parent 951ffa3ed8
commit 04cf2a0385

View File

@ -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<WorkflowStepApproverDto> Approvers);
List<WorkflowStepApproverDto> Approvers,
List<WorkflowStepInnerStepDto> 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<Guid, string>()
: 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<ContractType>()
@ -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<CreateWorkflowStepApproverInput> Approvers);
List<CreateWorkflowStepApproverInput> Approvers,
List<CreateWorkflowStepInnerStepInput>? InnerSteps = null);
public record CreateWorkflowDefinitionCommand(
ContractType ContractType,
@ -198,6 +242,16 @@ public class CreateWorkflowDefinitionCommandValidator : AbstractValidator<Create
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);
});
}
}
@ -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(),
};