From 4bf1ab73faff2d41388ec11c6605e56c3fb18ba1 Mon Sep 17 00:00:00 2001 From: Colin Dawson Date: Tue, 10 Mar 2026 22:23:07 +0000 Subject: [PATCH] Started working on the engine for the workflow processor --- .../e-suite.API.Common/GetWorkflowTemplate.cs | 2 +- .../e-suite.API.Common.csproj | 1 + .../repository/IActivityRepository.cs | 17 ++- .../repository/IWorkflowConverter.cs | 12 ++ .../repository/IWorkflowTemplateRepository.cs | 4 +- .../Handlers/ActivityQueueHandler.cs | 16 +- .../e-suite.MessageProcessor.csproj | 4 + .../repository/ActivityRepository.cs | 26 ++++ .../Repository/GeneralIdRefConverter.cs | 42 +++++ .../Repository/WorkflowConverter.cs | 97 ++++++++++++ .../Repository/WorkflowTemplateRepository.cs | 144 ------------------ .../IWorkflowProcessor.cs | 9 ++ .../IocRegistration.cs | 13 ++ .../WorkflowProcessor.cs | 107 +++++++++++++ .../e-suite.Service.WorkflowProcessor.csproj | 20 +++ eSuite.sln | 7 + 16 files changed, 370 insertions(+), 151 deletions(-) create mode 100644 e-suite.API.Common/e-suite.API.Common/repository/IWorkflowConverter.cs create mode 100644 e-suite.Modules.WorkflowTemplatesManager/Repository/GeneralIdRefConverter.cs create mode 100644 e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowConverter.cs create mode 100644 e-suite.Service.WorkflowProcessor/IWorkflowProcessor.cs create mode 100644 e-suite.Service.WorkflowProcessor/IocRegistration.cs create mode 100644 e-suite.Service.WorkflowProcessor/WorkflowProcessor.cs create mode 100644 e-suite.Service.WorkflowProcessor/e-suite.Service.WorkflowProcessor.csproj diff --git a/e-suite.API.Common/e-suite.API.Common/GetWorkflowTemplate.cs b/e-suite.API.Common/e-suite.API.Common/GetWorkflowTemplate.cs index 15350aa..49240a7 100644 --- a/e-suite.API.Common/e-suite.API.Common/GetWorkflowTemplate.cs +++ b/e-suite.API.Common/e-suite.API.Common/GetWorkflowTemplate.cs @@ -5,7 +5,7 @@ namespace e_suite.API.Common; public class GetWorkflowTemplate : IGeneralId { - public GetWorkflowTemplate( Workflow workflow) + public GetWorkflowTemplate( Database.Core.Tables.Workflow.Workflow workflow) { Id = workflow.Id; Guid = workflow.Guid; diff --git a/e-suite.API.Common/e-suite.API.Common/e-suite.API.Common.csproj b/e-suite.API.Common/e-suite.API.Common/e-suite.API.Common.csproj index 003ec27..3881911 100644 --- a/e-suite.API.Common/e-suite.API.Common/e-suite.API.Common.csproj +++ b/e-suite.API.Common/e-suite.API.Common/e-suite.API.Common.csproj @@ -15,6 +15,7 @@ + diff --git a/e-suite.API.Common/e-suite.API.Common/repository/IActivityRepository.cs b/e-suite.API.Common/e-suite.API.Common/repository/IActivityRepository.cs index f4e8ee7..71ff588 100644 --- a/e-suite.API.Common/e-suite.API.Common/repository/IActivityRepository.cs +++ b/e-suite.API.Common/e-suite.API.Common/repository/IActivityRepository.cs @@ -1,10 +1,25 @@ using e_suite.Database.Audit; using e_suite.Database.Core; using e_suite.Database.Core.Tables.Activity; +using eSuite.Core.Miscellaneous; namespace e_suite.API.Common.repository; public interface IActivityRepository : IRepository { - Task CreateActivityInstanceAsync(AuditUserDetails auditUserDetails, Activity template, CancellationToken cancellationToken); + Task CreateActivityInstanceAsync( + AuditUserDetails auditUserDetails, + Activity template, + CancellationToken cancellationToken + ); + + Task GetActivityInstanceAsync(GeneralIdRef activityId, CancellationToken cancellationToken); + + Task UpdateActivityInstanceAsync( + AuditUserDetails auditUserDetails, + Activity activityInstance, + CancellationToken cancellationToken + ); + + Task AddActivityTasksAsync(AuditUserDetails auditUserDetails, IEnumerable activityTasks, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/e-suite.API.Common/e-suite.API.Common/repository/IWorkflowConverter.cs b/e-suite.API.Common/e-suite.API.Common/repository/IWorkflowConverter.cs new file mode 100644 index 0000000..3d18ade --- /dev/null +++ b/e-suite.API.Common/e-suite.API.Common/repository/IWorkflowConverter.cs @@ -0,0 +1,12 @@ +namespace e_suite.API.Common.repository; + +public interface IWorkflowConverter +{ + Workflow.Core.WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion); + + Task SerialiseToDatabase( + Workflow.Core.WorkflowVersion runtime, + e_suite.Database.Core.Tables.Workflow.WorkflowVersion? dbObject = null, + CancellationToken cancellationToken = default + ); +} \ No newline at end of file diff --git a/e-suite.API.Common/e-suite.API.Common/repository/IWorkflowTemplateRepository.cs b/e-suite.API.Common/e-suite.API.Common/repository/IWorkflowTemplateRepository.cs index a80ca75..e69b3ac 100644 --- a/e-suite.API.Common/e-suite.API.Common/repository/IWorkflowTemplateRepository.cs +++ b/e-suite.API.Common/e-suite.API.Common/repository/IWorkflowTemplateRepository.cs @@ -6,9 +6,9 @@ namespace e_suite.API.Common.repository; public interface IWorkflowTemplateRepository : IRepository { - public IQueryable GetWorkflows(); + public IQueryable GetWorkflows(); IQueryable GetWorkflowVersions(); Task EditWorkflowVersionAsync(AuditUserDetails auditUserDetails, WorkflowVersion workflowVersion, CancellationToken cancellationToken); - Task AddWorkflow(AuditUserDetails auditUserDetails, Workflow workflow, CancellationToken cancellationToken); + Task AddWorkflow(AuditUserDetails auditUserDetails, Database.Core.Tables.Workflow.Workflow workflow, CancellationToken cancellationToken); Task AddWorkflowVersion(AuditUserDetails auditUserDetails, WorkflowVersion workflowVersion, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/e-suite.MessageProcessor/e-suite.MessageProcessor/Handlers/ActivityQueueHandler.cs b/e-suite.MessageProcessor/e-suite.MessageProcessor/Handlers/ActivityQueueHandler.cs index 76f40b4..a86ec92 100644 --- a/e-suite.MessageProcessor/e-suite.MessageProcessor/Handlers/ActivityQueueHandler.cs +++ b/e-suite.MessageProcessor/e-suite.MessageProcessor/Handlers/ActivityQueueHandler.cs @@ -1,4 +1,6 @@ -using e_suite.Messaging.Common.models; +using e_suite.Database.Audit; +using e_suite.Messaging.Common.models; +using e_suite.Service.WorkflowProcessor; using eSuite.Core.Clock; using Microsoft.Extensions.Logging; using RabbitMQ.Client.Events; @@ -9,13 +11,21 @@ public class ActivityQueueHandler : QueueHandlerBase { private readonly ILogger _logger; private readonly IClock _clock; + private readonly IWorkflowProcessor _workflowProgressor; - public ActivityQueueHandler(ILogger logger, IClock clock) + public ActivityQueueHandler(ILogger logger, IClock clock, IWorkflowProcessor workflowProgressor) { _logger = logger; _clock = clock; + _workflowProgressor = workflowProgressor; } + private readonly AuditUserDetails _auditUserDetails = new() + { + Comment = "ActivityQueueHandler", + UserDisplayName = "Workflow processor" + }; + public override async Task OnReceived(string queueName, BasicDeliverEventArgs basicDeliverEventArgs) { var activityMessage = await TranslateMessage(basicDeliverEventArgs); @@ -24,7 +34,7 @@ public class ActivityQueueHandler : QueueHandlerBase { case ActivityMessageTypes.ProgressActivity: var progressActivityMessage = await TranslateMessage(basicDeliverEventArgs); - _logger.LogInformation("{DateTime}: Progressing Activity {messageId}", _clock.GetNow, progressActivityMessage.ActivityId); + await _workflowProgressor.ProgressActivity(_auditUserDetails, progressActivityMessage.ActivityId, CancellationToken.None); break; default: #pragma warning disable CA2208 // Instantiate argument exceptions correctly diff --git a/e-suite.MessageProcessor/e-suite.MessageProcessor/e-suite.MessageProcessor.csproj b/e-suite.MessageProcessor/e-suite.MessageProcessor/e-suite.MessageProcessor.csproj index 9837177..f5d9c64 100644 --- a/e-suite.MessageProcessor/e-suite.MessageProcessor/e-suite.MessageProcessor.csproj +++ b/e-suite.MessageProcessor/e-suite.MessageProcessor/e-suite.MessageProcessor.csproj @@ -43,16 +43,20 @@ + + + + diff --git a/e-suite.Modules.RunningActivityManager/repository/ActivityRepository.cs b/e-suite.Modules.RunningActivityManager/repository/ActivityRepository.cs index 3c1cdbf..79a45d1 100644 --- a/e-suite.Modules.RunningActivityManager/repository/ActivityRepository.cs +++ b/e-suite.Modules.RunningActivityManager/repository/ActivityRepository.cs @@ -1,7 +1,10 @@ using e_suite.API.Common.repository; using e_suite.Database.Audit; using e_suite.Database.Core; +using e_suite.Database.Core.Extensions; using e_suite.Database.Core.Tables.Activity; +using eSuite.Core.Miscellaneous; +using Microsoft.EntityFrameworkCore; namespace e_suite.Modules.RunningActivityManager.repository; @@ -20,4 +23,27 @@ public class ActivityRepository : RepositoryBase, IActivityRepository DatabaseDbContext.Activities.Add(activity); await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken); } + + public async Task GetActivityInstanceAsync(GeneralIdRef activityId, CancellationToken cancellationToken) + { + return await DatabaseDbContext.Activities + .Include( x=> x.WorkflowVersion) + .Include( x => x.Tasks) + .FindByGeneralIdRefAsync(activityId, cancellationToken); + } + + public async Task UpdateActivityInstanceAsync(AuditUserDetails auditUserDetails, Activity activityInstance, CancellationToken cancellationToken) + { + await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken); + } + + public async Task AddActivityTasksAsync( + AuditUserDetails auditUserDetails, + IEnumerable activityTasks, + CancellationToken cancellationToken + ) + { + DatabaseDbContext.ActivityTasks.AddRange(activityTasks); + await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken); + } } \ No newline at end of file diff --git a/e-suite.Modules.WorkflowTemplatesManager/Repository/GeneralIdRefConverter.cs b/e-suite.Modules.WorkflowTemplatesManager/Repository/GeneralIdRefConverter.cs new file mode 100644 index 0000000..2a97c34 --- /dev/null +++ b/e-suite.Modules.WorkflowTemplatesManager/Repository/GeneralIdRefConverter.cs @@ -0,0 +1,42 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using eSuite.Core.Miscellaneous; + +namespace e_suite.Modules.WorkflowTemplatesManager.Repository; + +public class GeneralIdRefConverter : JsonConverter +{ + private readonly Func _lookup; + + public GeneralIdRefConverter(Func lookup) + { + _lookup = lookup; + } + public override bool CanConvert(Type typeToConvert) + { + // Only convert actual domain types, not enums or primitives + return typeToConvert == typeof(T) && + !typeToConvert.IsEnum && + !typeToConvert.IsPrimitive && + typeToConvert != typeof(string); + } + + public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // Parse the incoming JSON into a GeneralIdRef + using var doc = JsonDocument.ParseValue(ref reader); + var json = doc.RootElement.GetRawText(); + + var idRef = JsonSerializer.Deserialize(json, options); + + if (idRef == null) + return default; + + return _lookup(idRef); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + throw new NotImplementedException("Writing not needed."); + } +} \ No newline at end of file diff --git a/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowConverter.cs b/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowConverter.cs new file mode 100644 index 0000000..d184be7 --- /dev/null +++ b/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowConverter.cs @@ -0,0 +1,97 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using e_suite.API.Common.repository; +using e_suite.Database.Core.Extensions; +using e_suite.Database.Core.Tables.Domain; +using e_suite.Database.Core.Tables.UserManager; +using e_suite.Workflow.Core; +using e_suite.Workflow.Core.Extensions; + +namespace e_suite.Modules.WorkflowTemplatesManager.Repository; + +public class WorkflowConverter : IWorkflowConverter +{ + private readonly IDomainRepository _domainRepository; + private readonly IRoleManagerRepository _roleManagerRepository; + private readonly IUserManagerRepository _userManagerRepository; + + private readonly JsonSerializerOptions _jsonSerializerOptions; + + public WorkflowConverter(IDomainRepository domainRepository, IRoleManagerRepository roleManagerRepository, IUserManagerRepository userManagerRepository) + { + _domainRepository = domainRepository; + _roleManagerRepository = roleManagerRepository; + _userManagerRepository = userManagerRepository; + + + _jsonSerializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + + _jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + _jsonSerializerOptions.Converters.Add(new GeneralIdRefConverter(id => _roleManagerRepository.GetRoleById(id))); + _jsonSerializerOptions.Converters.Add(new GeneralIdRefConverter(id => _userManagerRepository.GetUserById(id))); + } + + public Workflow.Core.WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion) + { + var runtime = new Workflow.Core.WorkflowVersion + { + Id = dbVersion.Id, + Guid = dbVersion.Guid, + Version = dbVersion.Version, + ActivityNameTemplate = dbVersion.ActivityNameTemplate, + Description = dbVersion.Description, + Domain = dbVersion.Domain.ToGeneralIdRef()!, + Template = new WorkflowTemplate + { + Name = "Need to fix", + Id = 1, + Guid = Guid.Empty + } // however you load templates + }; + + foreach (var def in dbVersion.Tasks) + { + var task = def.ToTask(_jsonSerializerOptions); + runtime.Tasks.Add(task); + } + + return runtime; + } + + public async Task SerialiseToDatabase(Workflow.Core.WorkflowVersion runtime, e_suite.Database.Core.Tables.Workflow.WorkflowVersion? dbObject = null, CancellationToken cancellationToken = default) + { + if (runtime is null) + throw new NullReferenceException(); + + var domain = await _domainRepository.GetDomainById(runtime.Domain, cancellationToken); + if (domain is null) + throw new Exception($"Domain with id {runtime.Domain} not found."); + + var dbVersion = dbObject ?? new e_suite.Database.Core.Tables.Workflow.WorkflowVersion(); + + if (dbObject == null) + { + dbVersion.Id = runtime.Id; + dbVersion.Guid = runtime.Guid; + } + else + { + //note cannot move a version from one workflow to another, that requires a new version. + //todo make sure that the Workflow is populated here. + //dbVersion.Workflow = runtime.Template.ToGeneralIdRef() + } + + dbVersion.Version = runtime.Version; //todo make sure that the version number get incremented somewhere logical. + dbVersion.ActivityNameTemplate = runtime.ActivityNameTemplate; + dbVersion.Description = runtime.Description; + dbVersion.DomainId = domain.Id; + dbVersion.Tasks = runtime.Tasks + .Select(t => t.ToDefinition()) + .ToList(); + + return dbVersion; + } +} \ No newline at end of file diff --git a/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowTemplateRepository.cs b/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowTemplateRepository.cs index 9706987..2b6e315 100644 --- a/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowTemplateRepository.cs +++ b/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowTemplateRepository.cs @@ -1,156 +1,12 @@ using e_suite.API.Common.repository; using e_suite.Database.Audit; using e_suite.Database.Core; -using e_suite.Database.Core.Extensions; using e_suite.Database.Core.Tables.Contacts; -using e_suite.Database.Core.Tables.Domain; -using e_suite.Database.Core.Tables.UserManager; -using e_suite.Workflow.Core; -using e_suite.Workflow.Core.Extensions; -using eSuite.Core.Miscellaneous; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; -using System.Text.Json; -using System.Text.Json.Serialization; namespace e_suite.Modules.WorkflowTemplatesManager.Repository; - -public class GeneralIdRefConverter : JsonConverter -{ - private readonly Func _lookup; - - public GeneralIdRefConverter(Func lookup) - { - _lookup = lookup; - } - public override bool CanConvert(Type typeToConvert) - { - // Only convert actual domain types, not enums or primitives - return typeToConvert == typeof(T) && - !typeToConvert.IsEnum && - !typeToConvert.IsPrimitive && - typeToConvert != typeof(string); - } - - public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - // Parse the incoming JSON into a GeneralIdRef - using var doc = JsonDocument.ParseValue(ref reader); - var json = doc.RootElement.GetRawText(); - - var idRef = JsonSerializer.Deserialize(json, options); - - if (idRef == null) - return default; - - return _lookup(idRef); - } - - public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) - { - throw new NotImplementedException("Writing not needed."); - } -} - -public interface IWorkflowConverter -{ - Workflow.Core.WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion); - - Task SerialiseToDatabase( - Workflow.Core.WorkflowVersion runtime, - e_suite.Database.Core.Tables.Workflow.WorkflowVersion? dbObject = null, - CancellationToken cancellationToken = default - ); -} -public class WorkflowConverter : IWorkflowConverter -{ - private readonly IDomainRepository _domainRepository; - private readonly IRoleManagerRepository _roleManagerRepository; - private readonly IUserManagerRepository _userManagerRepository; - - private readonly JsonSerializerOptions _jsonSerializerOptions; - - public WorkflowConverter(IDomainRepository domainRepository, IRoleManagerRepository roleManagerRepository, IUserManagerRepository userManagerRepository) - { - _domainRepository = domainRepository; - _roleManagerRepository = roleManagerRepository; - _userManagerRepository = userManagerRepository; - - - _jsonSerializerOptions = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - - _jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - _jsonSerializerOptions.Converters.Add(new GeneralIdRefConverter(id => _roleManagerRepository.GetRoleById(id))); - _jsonSerializerOptions.Converters.Add(new GeneralIdRefConverter(id => _userManagerRepository.GetUserById(id))); - } - - public Workflow.Core.WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion) - { - var runtime = new Workflow.Core.WorkflowVersion - { - Id = dbVersion.Id, - Guid = dbVersion.Guid, - Version = dbVersion.Version, - ActivityNameTemplate = dbVersion.ActivityNameTemplate, - Description = dbVersion.Description, - Domain = dbVersion.Domain.ToGeneralIdRef()!, - Template = new WorkflowTemplate - { - Name = "Need to fix", - Id = 1, - Guid = Guid.Empty - } // however you load templates - }; - - foreach (var def in dbVersion.Tasks) - { - var task = def.ToTask(_jsonSerializerOptions); - runtime.Tasks.Add(task); - } - - return runtime; - } - - public async Task SerialiseToDatabase(Workflow.Core.WorkflowVersion runtime, e_suite.Database.Core.Tables.Workflow.WorkflowVersion? dbObject = null, CancellationToken cancellationToken = default) - { - if (runtime is null) - throw new NullReferenceException(); - - var domain = await _domainRepository.GetDomainById(runtime.Domain, cancellationToken); - if (domain is null) - throw new Exception($"Domain with id {runtime.Domain} not found."); - - var dbVersion = dbObject ?? new e_suite.Database.Core.Tables.Workflow.WorkflowVersion(); - - if (dbObject == null) - { - dbVersion.Id = runtime.Id; - dbVersion.Guid = runtime.Guid; - } - else - { - //note cannot move a version from one workflow to another, that requires a new version. - //todo make sure that the Workflow is populated here. - //dbVersion.Workflow = runtime.Template.ToGeneralIdRef() - } - - dbVersion.Version = runtime.Version; //todo make sure that the version number get incremented somewhere logical. - dbVersion.ActivityNameTemplate = runtime.ActivityNameTemplate; - dbVersion.Description = runtime.Description; - dbVersion.DomainId = domain.Id; - dbVersion.Tasks = runtime.Tasks - .Select(t => t.ToDefinition()) - .ToList(); - - return dbVersion; - } -} - - public class WorkflowTemplateRepository : RepositoryBase, IWorkflowTemplateRepository { public WorkflowTemplateRepository(IEsuiteDatabaseDbContext databaseDbContext) : base(databaseDbContext) diff --git a/e-suite.Service.WorkflowProcessor/IWorkflowProcessor.cs b/e-suite.Service.WorkflowProcessor/IWorkflowProcessor.cs new file mode 100644 index 0000000..e440df2 --- /dev/null +++ b/e-suite.Service.WorkflowProcessor/IWorkflowProcessor.cs @@ -0,0 +1,9 @@ +using e_suite.Database.Audit; +using eSuite.Core.Miscellaneous; + +namespace e_suite.Service.WorkflowProcessor; + +public interface IWorkflowProcessor +{ + Task ProgressActivity(AuditUserDetails auditUserDetails, GeneralIdRef activityId, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/e-suite.Service.WorkflowProcessor/IocRegistration.cs b/e-suite.Service.WorkflowProcessor/IocRegistration.cs new file mode 100644 index 0000000..3c21065 --- /dev/null +++ b/e-suite.Service.WorkflowProcessor/IocRegistration.cs @@ -0,0 +1,13 @@ +using Autofac; +using e_suite.DependencyInjection; + +namespace e_suite.Service.WorkflowProcessor; + +public class IocRegistration : IIocRegistration +{ + public void RegisterTypes(ContainerBuilder builder) + { + builder.RegisterType().As().InstancePerLifetimeScope(); + + } +} \ No newline at end of file diff --git a/e-suite.Service.WorkflowProcessor/WorkflowProcessor.cs b/e-suite.Service.WorkflowProcessor/WorkflowProcessor.cs new file mode 100644 index 0000000..e2d3d8c --- /dev/null +++ b/e-suite.Service.WorkflowProcessor/WorkflowProcessor.cs @@ -0,0 +1,107 @@ +using e_suite.API.Common.repository; +using e_suite.Database.Audit; +using e_suite.Database.Core.Tables.Activity; +using e_suite.Workflow.Core; +using e_suite.Workflow.Core.Interfaces; +using eSuite.Core.Clock; +using eSuite.Core.Enums; +using eSuite.Core.Miscellaneous; +using Microsoft.Extensions.Logging; + +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; + + public WorkflowProcessor(ILogger logger, IClock clock, IWorkflowTemplateRepository workflowTemplateRepository, IActivityRepository activityRepository, IWorkflowConverter workflowConverter) + { + _logger = logger; + _clock = clock; + _workflowTemplateRepository = workflowTemplateRepository; + _activityRepository = activityRepository; + _workflowConverter = workflowConverter; + } + + 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; + } + + var workflowVersion = _workflowConverter.DeserialiseFromDatabase(activityInstance.WorkflowVersion); + + await _activityRepository.TransactionAsync(async () => + { + + ICollection tasks; + if (activityInstance.ActivityState == ActivityState.Pending) + { + activityInstance.ActivityState = ActivityState.Active; + await _activityRepository.UpdateActivityInstanceAsync(auditUserDetails, activityInstance, + cancellationToken); + + tasks = await PlanTaskExecution(auditUserDetails, activityInstance, workflowVersion, cancellationToken); + } + else + { + tasks = activityInstance.Tasks; + } + + //Invoke startable tasks. + }); + } + + private async Task> PlanTaskExecution(AuditUserDetails auditUserDetails, Activity activityInstance, WorkflowVersion workflowVersion, CancellationToken cancellationToken) + { + var activityOrdinalCounter = 1; + + //Create task instances for the activity + var activityTasks = new List(); + + foreach (var task in workflowVersion.Tasks) + { + var activityTask = new ActivityTask + { + Guid = Guid.NewGuid(), + Activity = activityInstance, + ActivityState = ActivityState.Pending, + TaskGuid = task.Guid, + ActivityOrdinal = activityOrdinalCounter++, + TaskName = task.Name, + }; + activityTasks.Add(activityTask); + + } + await _activityRepository.AddActivityTasksAsync(auditUserDetails, activityTasks, cancellationToken); + + return activityTasks; + } +} \ No newline at end of file diff --git a/e-suite.Service.WorkflowProcessor/e-suite.Service.WorkflowProcessor.csproj b/e-suite.Service.WorkflowProcessor/e-suite.Service.WorkflowProcessor.csproj new file mode 100644 index 0000000..87e1697 --- /dev/null +++ b/e-suite.Service.WorkflowProcessor/e-suite.Service.WorkflowProcessor.csproj @@ -0,0 +1,20 @@ + + + + net10.0 + e_suite.Service.WorkflowProcessor + enable + enable + + + + + + + + + + + + + diff --git a/eSuite.sln b/eSuite.sln index a704504..af1a044 100644 --- a/eSuite.sln +++ b/eSuite.sln @@ -167,6 +167,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eSuite.Translator", "eSuite EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "e-suite.Modules.RunningActivityManager", "e-suite.Modules.RunningActivityManager\e-suite.Modules.RunningActivityManager.csproj", "{DE96B3AE-986A-43FE-9C30-14553D0CFCE7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "e-suite.Service.WorkflowProcessor", "e-suite.Service.WorkflowProcessor\e-suite.Service.WorkflowProcessor.csproj", "{F32346BC-26BF-4BD5-9691-DD23BEA58E99}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -469,6 +471,10 @@ Global {DE96B3AE-986A-43FE-9C30-14553D0CFCE7}.Debug|Any CPU.Build.0 = Debug|Any CPU {DE96B3AE-986A-43FE-9C30-14553D0CFCE7}.Release|Any CPU.ActiveCfg = Release|Any CPU {DE96B3AE-986A-43FE-9C30-14553D0CFCE7}.Release|Any CPU.Build.0 = Release|Any CPU + {F32346BC-26BF-4BD5-9691-DD23BEA58E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F32346BC-26BF-4BD5-9691-DD23BEA58E99}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F32346BC-26BF-4BD5-9691-DD23BEA58E99}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F32346BC-26BF-4BD5-9691-DD23BEA58E99}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -543,6 +549,7 @@ Global {8D343418-7E3A-40E5-A5CF-6497221F3F7E} = {27EA902C-3CD0-4A8F-BA75-8D1AF87902AC} {ABF3CD19-D1CF-4407-9102-3FC31DAC04AB} = {B0DE567F-EA4E-43FA-8A16-A0D571852024} {DE96B3AE-986A-43FE-9C30-14553D0CFCE7} = {B0DE567F-EA4E-43FA-8A16-A0D571852024} + {F32346BC-26BF-4BD5-9691-DD23BEA58E99} = {726E46E8-E6E2-44D1-AB3B-85481330A84E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C5175258-F776-4FF9-A9EE-386312E47061}