229 lines
9.0 KiB
C#
229 lines
9.0 KiB
C#
using e_suite.API.Common.repository;
|
|
using e_suite.Database.Audit;
|
|
using e_suite.Database.Core.Tables.Activity;
|
|
using e_suite.Database.Core.Tables.Domain;
|
|
using e_suite.Messaging.Common;
|
|
using e_suite.Workflow.Core;
|
|
using e_suite.Workflow.Core.Extensions;
|
|
using e_suite.Workflow.Core.Interfaces;
|
|
using eSuite.Core.Clock;
|
|
using eSuite.Core.Enums;
|
|
using eSuite.Core.Miscellaneous;
|
|
using Microsoft.Extensions.Logging;
|
|
using System.Collections;
|
|
|
|
namespace e_suite.Service.WorkflowProcessor;
|
|
|
|
public class WorkflowProcessor : IWorkflowProcessor
|
|
{
|
|
private readonly ILogger _logger;
|
|
private readonly IClock _clock;
|
|
private readonly IWorkflowTemplateRepository _workflowTemplateRepository;
|
|
private readonly IWorkflowConverter _workflowConverter;
|
|
private readonly IActivityRepository _activityRepository;
|
|
private readonly IActivityMessageSender _activityMessageSender;
|
|
|
|
public WorkflowProcessor(ILogger logger, IClock clock, IWorkflowTemplateRepository workflowTemplateRepository, IActivityRepository activityRepository, IWorkflowConverter workflowConverter, IActivityMessageSender activityMessageSender)
|
|
{
|
|
_logger = logger;
|
|
_clock = clock;
|
|
_workflowTemplateRepository = workflowTemplateRepository;
|
|
_activityRepository = activityRepository;
|
|
_workflowConverter = workflowConverter;
|
|
_activityMessageSender = activityMessageSender;
|
|
}
|
|
|
|
public async Task ProgressActivity(
|
|
AuditUserDetails auditUserDetails,
|
|
GeneralIdRef activityId,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
_logger.LogInformation("{DateTime}: Progressing Activity {messageId}", _clock.GetNow, activityId);
|
|
|
|
var activityInstance = await _activityRepository.GetActivityInstanceAsync(activityId, cancellationToken);
|
|
if (activityInstance == null)
|
|
{
|
|
_logger.LogInformation("{DateTime}: Unable to find activity {messageId}", _clock.GetNow, activityId);
|
|
throw new InvalidDataException("activityInstance not found");
|
|
}
|
|
|
|
if (activityInstance.ActivityState == ActivityState.Cancelled)
|
|
{
|
|
_logger.LogInformation("{DateTime}: Activity {messageId} is cancelled, skipping", _clock.GetNow,
|
|
activityId);
|
|
return;
|
|
}
|
|
|
|
if (activityInstance.ActivityState == ActivityState.Completed)
|
|
{
|
|
_logger.LogInformation("{DateTime}: Activity {messageId} is already completed, skipping", _clock.GetNow,
|
|
activityId);
|
|
return;
|
|
}
|
|
|
|
WorkflowVersion workflowVersion = _workflowConverter.DeserialiseFromDatabase(activityInstance.WorkflowVersion);
|
|
|
|
var hasCompletableTask = false;
|
|
|
|
await _activityRepository.TransactionAsync(async () =>
|
|
{
|
|
switch (activityInstance.ActivityState)
|
|
{
|
|
case ActivityState.Pending:
|
|
{
|
|
hasCompletableTask = await StartActivity(auditUserDetails, cancellationToken, activityInstance,
|
|
workflowVersion);
|
|
break;
|
|
}
|
|
case ActivityState.Active:
|
|
hasCompletableTask = await ProgressRunningActivity(auditUserDetails, cancellationToken, activityInstance,
|
|
workflowVersion);
|
|
|
|
if (activityInstance.Tasks.All(x => x.ActivityState == ActivityState.Completed))
|
|
{
|
|
activityInstance.ActivityState = ActivityState.Completed;
|
|
await _activityRepository.UpdateActivityInstanceAsync(auditUserDetails, activityInstance, cancellationToken);
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
|
|
if (hasCompletableTask)
|
|
{
|
|
_activityMessageSender.ProgressActivity(activityId);
|
|
}
|
|
}
|
|
|
|
private async Task<bool> ProgressRunningActivity(AuditUserDetails auditUserDetails, CancellationToken cancellationToken, Activity activityInstance, WorkflowVersion workflowVersion)
|
|
{
|
|
bool hasCompletableTask = false;
|
|
foreach (var task in activityInstance.Tasks)
|
|
{
|
|
var taskDefinition = workflowVersion.FindTask(task.TaskGuid)!;
|
|
if (await taskDefinition.ProgressTask(task, activityInstance.Tasks, workflowVersion, _clock))
|
|
{
|
|
hasCompletableTask = true;
|
|
}
|
|
}
|
|
|
|
await _activityRepository.UpdateActivityTasksAsync(auditUserDetails, activityInstance.Tasks, cancellationToken);
|
|
|
|
return hasCompletableTask;
|
|
}
|
|
|
|
private async Task<bool> StartActivity(
|
|
AuditUserDetails auditUserDetails,
|
|
CancellationToken cancellationToken,
|
|
Activity? activityInstance,
|
|
WorkflowVersion workflowVersion
|
|
)
|
|
{
|
|
bool hasCompletableTask = false;
|
|
activityInstance.ActivityState = ActivityState.Active;
|
|
await _activityRepository.UpdateActivityInstanceAsync(auditUserDetails, activityInstance,
|
|
cancellationToken);
|
|
|
|
var tasks = await PlanTaskExecution(auditUserDetails, activityInstance, workflowVersion, cancellationToken);
|
|
|
|
await StartInitialTasks(auditUserDetails, tasks, workflowVersion, cancellationToken);
|
|
|
|
if (tasks.Any(task => task.ActivityState == ActivityState.ReadyToComplete))
|
|
{
|
|
hasCompletableTask = true;
|
|
}
|
|
|
|
return hasCompletableTask;
|
|
}
|
|
|
|
private async Task StartInitialTasks(AuditUserDetails auditUserDetails, ICollection<ActivityTask> tasks, WorkflowVersion workflowVersion, CancellationToken cancellationToken)
|
|
{
|
|
var startTasks = workflowVersion.GetStartTasks().ToList();
|
|
var startTaskGuids = startTasks.Select(t => t.Guid).ToHashSet();
|
|
|
|
foreach (var task in tasks)
|
|
{
|
|
if (startTaskGuids.Contains(task.TaskGuid))
|
|
{
|
|
var taskDefinition = workflowVersion.FindTask(task.TaskGuid)!;
|
|
await taskDefinition.StartTask(task, _clock);
|
|
}
|
|
}
|
|
|
|
await _activityRepository.UpdateActivityTasksAsync(auditUserDetails, tasks, cancellationToken);
|
|
}
|
|
|
|
private async Task<ICollection<ActivityTask>> PlanTaskExecution(AuditUserDetails auditUserDetails, Activity activityInstance, WorkflowVersion workflowVersion, CancellationToken cancellationToken)
|
|
{
|
|
var activityOrdinalCounter = 1;
|
|
|
|
//Create task instances for the activity
|
|
var activityTasks = new List<ActivityTask>();
|
|
|
|
foreach (var task in workflowVersion.Tasks)
|
|
{
|
|
var activityTask = new ActivityTask
|
|
{
|
|
Guid = Guid.NewGuid(),
|
|
Activity = activityInstance,
|
|
ActivityState = ActivityState.Pending,
|
|
TaskGuid = task.Guid,
|
|
TaskType = task.GetType().FullName!,
|
|
TaskName = task.Name,
|
|
ActivityOrdinal = activityOrdinalCounter++,
|
|
};
|
|
|
|
if (task is IAssignees assignableTask)
|
|
activityTask.Assignments = await GenerateAssignmentForTask(activityTask, assignableTask, auditUserDetails, cancellationToken );
|
|
|
|
activityTasks.Add(activityTask);
|
|
|
|
}
|
|
await _activityRepository.AddActivityTasksAsync(auditUserDetails, activityTasks, cancellationToken);
|
|
|
|
return activityTasks;
|
|
}
|
|
|
|
private async Task<ICollection<ActivityAssignment>> GenerateAssignmentForTask(ActivityTask activityTask, IAssignees task, AuditUserDetails auditUserDetails, CancellationToken cancellationToken)
|
|
{
|
|
// Find the IAssignees<T> interface implemented by this instance
|
|
var typedInterface = task.GetType()
|
|
.GetInterfaces()
|
|
.FirstOrDefault(i =>
|
|
i.IsGenericType &&
|
|
i.GetGenericTypeDefinition() == typeof(IAssignees<>));
|
|
|
|
if (typedInterface == null)
|
|
throw new InvalidOperationException("Task does not implement IAssignees<T>.");
|
|
|
|
// Get the Assignees property
|
|
var assigneesProperty = typedInterface.GetProperty("Assignees");
|
|
var assignees = (IEnumerable)assigneesProperty.GetValue(task);
|
|
|
|
var results = new List<ActivityAssignment>();
|
|
|
|
foreach (var assignee in assignees)
|
|
{
|
|
var typedAssignee = (ITaskAssignee)assignee;
|
|
|
|
|
|
var activityAssignment = new ActivityAssignment
|
|
{
|
|
Guid = Guid.NewGuid(),
|
|
Raci = typedAssignee.Raci,
|
|
Role = typedAssignee.Role!,
|
|
User = typedAssignee.User!,
|
|
};
|
|
|
|
if (typedAssignee is IApprovalTaskAssignee approvalAssignee)
|
|
{
|
|
activityAssignment.Bypassable = approvalAssignee.Bypassable;
|
|
activityAssignment.AllowNoVerdict = approvalAssignee.AllowNoVerdict;
|
|
}
|
|
|
|
results.Add(activityAssignment);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
} |