From ad8363b5d61fa0c7f055fb57fab3eef7e8409615 Mon Sep 17 00:00:00 2001 From: Colin Dawson Date: Mon, 9 Mar 2026 22:39:51 +0000 Subject: [PATCH] Now able to create an activity instance. Will send a message to progress the instance after creation. --- .../e-suite.API.Common/CreateActivity.cs | 6 ++- .../e-suite.API.Common/IActivityManager.cs | 8 +++ .../repository/IActivityRepository.cs | 10 ++++ .../InternalMessagingControllerTestBase.cs | 4 +- .../Controllers/ActivityController.cs | 11 +++- .../InternalMessagingController.cs | 23 +++++++- e-suite.API/eSuite.API/eSuite.API.csproj | 1 + .../CoreRegistrationModule.cs | 1 + .../QueueProcessor/QueueProcessor.cs | 9 +++- .../ActivityMessageSender.cs | 26 ++++++++++ .../IActivityMessageSender.cs | 8 +++ .../IDatabaseMessageSender.cs | 2 +- .../IocRegistration.cs | 1 + .../models/ActivityMessage.cs | 2 + .../ActivityManager.cs | 52 +++++++++++++++++++ .../IocRegistration.cs | 16 ++++++ ...uite.Modules.RunningActivityManager.csproj | 15 ++++++ .../repository/ActivityRepository.cs | 23 ++++++++ eSuite.sln | 7 +++ 19 files changed, 217 insertions(+), 8 deletions(-) create mode 100644 e-suite.API.Common/e-suite.API.Common/IActivityManager.cs create mode 100644 e-suite.API.Common/e-suite.API.Common/repository/IActivityRepository.cs create mode 100644 e-suite.Messaging.Common/e-suite.Messaging.Common/ActivityMessageSender.cs create mode 100644 e-suite.Messaging.Common/e-suite.Messaging.Common/IActivityMessageSender.cs create mode 100644 e-suite.Modules.RunningActivityManager/ActivityManager.cs create mode 100644 e-suite.Modules.RunningActivityManager/IocRegistration.cs create mode 100644 e-suite.Modules.RunningActivityManager/e-suite.Modules.RunningActivityManager.csproj create mode 100644 e-suite.Modules.RunningActivityManager/repository/ActivityRepository.cs diff --git a/e-suite.API.Common/e-suite.API.Common/CreateActivity.cs b/e-suite.API.Common/e-suite.API.Common/CreateActivity.cs index 6557eda..3282ca8 100644 --- a/e-suite.API.Common/e-suite.API.Common/CreateActivity.cs +++ b/e-suite.API.Common/e-suite.API.Common/CreateActivity.cs @@ -1,10 +1,12 @@ using eSuite.Core.Miscellaneous; +using System.ComponentModel.DataAnnotations; namespace e_suite.API.Common; public class CreateActivity { - public GeneralIdRef WorkflowTemplateVersionId { get; set; } + public GeneralIdRef WorkflowTemplateId { get; set; } - public string ActivityName { get; set; } + [Required] + public string ActivityName { get; set; } = string.Empty; } \ No newline at end of file diff --git a/e-suite.API.Common/e-suite.API.Common/IActivityManager.cs b/e-suite.API.Common/e-suite.API.Common/IActivityManager.cs new file mode 100644 index 0000000..86ab271 --- /dev/null +++ b/e-suite.API.Common/e-suite.API.Common/IActivityManager.cs @@ -0,0 +1,8 @@ +using e_suite.Database.Audit; + +namespace e_suite.API.Common; + +public interface IActivityManager +{ + Task CreateActivity(AuditUserDetails auditUserDetails, CreateActivity template, CancellationToken cancellationToken); +} \ No newline at end of file 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 new file mode 100644 index 0000000..f4e8ee7 --- /dev/null +++ b/e-suite.API.Common/e-suite.API.Common/repository/IActivityRepository.cs @@ -0,0 +1,10 @@ +using e_suite.Database.Audit; +using e_suite.Database.Core; +using e_suite.Database.Core.Tables.Activity; + +namespace e_suite.API.Common.repository; + +public interface IActivityRepository : IRepository +{ + Task CreateActivityInstanceAsync(AuditUserDetails auditUserDetails, Activity template, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/e-suite.API/eSuite.API.UnitTests/Controllers/InternalMessagingControllerUnitTests/InternalMessagingControllerTestBase.cs b/e-suite.API/eSuite.API.UnitTests/Controllers/InternalMessagingControllerUnitTests/InternalMessagingControllerTestBase.cs index ea97f64..8751045 100644 --- a/e-suite.API/eSuite.API.UnitTests/Controllers/InternalMessagingControllerUnitTests/InternalMessagingControllerTestBase.cs +++ b/e-suite.API/eSuite.API.UnitTests/Controllers/InternalMessagingControllerUnitTests/InternalMessagingControllerTestBase.cs @@ -13,6 +13,7 @@ public abstract class InternalMessagingControllerTestBase : TestBase protected Mock _sigmaImportMessageSenderMock = null!; protected Mock _databaseMessageSenderMock = null!; protected Mock _eFlowSyncMessageSenderMock = null!; + protected Mock _activityMessageSenderMock = null!; protected InternalMessagingController _internalMessagingController = null!; public override async Task Setup() @@ -22,9 +23,10 @@ public abstract class InternalMessagingControllerTestBase : TestBase _sigmaImportMessageSenderMock = new Mock(); _databaseMessageSenderMock = new Mock(); _eFlowSyncMessageSenderMock = new Mock(); + _activityMessageSenderMock = new Mock(); _internalMessagingController = - new InternalMessagingController(_sigmaImportMessageSenderMock.Object, _databaseMessageSenderMock.Object, _eFlowSyncMessageSenderMock.Object); + new InternalMessagingController(_sigmaImportMessageSenderMock.Object, _databaseMessageSenderMock.Object, _eFlowSyncMessageSenderMock.Object, _activityMessageSenderMock.Object); const long id = -1; const string email = "email@mail.test"; diff --git a/e-suite.API/eSuite.API/Controllers/ActivityController.cs b/e-suite.API/eSuite.API/Controllers/ActivityController.cs index 30ed9e0..76527a7 100644 --- a/e-suite.API/eSuite.API/Controllers/ActivityController.cs +++ b/e-suite.API/eSuite.API/Controllers/ActivityController.cs @@ -10,6 +10,13 @@ namespace eSuite.API.Controllers; [ApiController] public class ActivityController : ESuiteControllerBase { + private readonly IActivityManager _activityManager; + + public ActivityController(IActivityManager activityManager) + { + _activityManager = activityManager; + } + /// /// Create a new workflow template /// @@ -17,7 +24,7 @@ public class ActivityController : ESuiteControllerBase /// Contains the details that need to be supplied to create the user. /// /// - [Route("create")] + [Route("activity")] [HttpPost] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] @@ -27,7 +34,7 @@ public class ActivityController : ESuiteControllerBase CancellationToken cancellationToken = default! ) { - //await _workflowTemplateManager.PostTemplateVersion(AuditUserDetails, template, cancellationToken); + await _activityManager.CreateActivity(AuditUserDetails, template, cancellationToken); return Ok(); } } \ No newline at end of file diff --git a/e-suite.API/eSuite.API/Controllers/InternalMessagingController.cs b/e-suite.API/eSuite.API/Controllers/InternalMessagingController.cs index 184221b..62cff6f 100644 --- a/e-suite.API/eSuite.API/Controllers/InternalMessagingController.cs +++ b/e-suite.API/eSuite.API/Controllers/InternalMessagingController.cs @@ -1,6 +1,7 @@ using e_suite.Messaging.Common; using eSuite.API.security; using eSuite.API.Utilities; +using eSuite.Core.Miscellaneous; using eSuite.Core.Security; using Microsoft.AspNetCore.Mvc; @@ -15,6 +16,7 @@ public class InternalMessagingController : ESuiteControllerBase private readonly ISigmaImportMessageSender _sigmaImportMessageSender; private readonly IDatabaseMessageSender _databaseMessageSender; private readonly IEFlowSyncMessageSender _eFlowSyncMessageSender; + private readonly IActivityMessageSender _activityMessageSender; /// /// @@ -22,11 +24,12 @@ public class InternalMessagingController : ESuiteControllerBase /// /// /// - public InternalMessagingController(ISigmaImportMessageSender sigmaImportMessageSender, IDatabaseMessageSender databaseMessageSender, IEFlowSyncMessageSender eFlowSyncMessageSender) + public InternalMessagingController(ISigmaImportMessageSender sigmaImportMessageSender, IDatabaseMessageSender databaseMessageSender, IEFlowSyncMessageSender eFlowSyncMessageSender, IActivityMessageSender activityMessageSender) { _sigmaImportMessageSender = sigmaImportMessageSender; _databaseMessageSender = databaseMessageSender; _eFlowSyncMessageSender = eFlowSyncMessageSender; + _activityMessageSender = activityMessageSender; } /// @@ -141,4 +144,22 @@ public class InternalMessagingController : ESuiteControllerBase _eFlowSyncMessageSender.SyncEFlowPrinterCategories(); return Task.CompletedTask; } + + /// + /// This message is used to trigger progress on an activity. This is needed to make sure that activities that are waiting for a message to progress will continue to progress even if the message that should trigger the progress was not received (e.g. because the service was down when the message was sent). + /// + /// + /// + /// + [Route("ProgressActivity")] + [HttpPost] + [ProducesResponseType(StatusCodes.Status200OK)] + [AccessKey(SecurityAccess.CreateActivity)] +#pragma warning disable IDE0060 // Remove unused parameter + public Task ProgressActivity( [FromBody] GeneralIdRef activityGeneralIdRef, CancellationToken cancellationToken) +#pragma warning restore IDE0060 // Remove unused parameter + { + _activityMessageSender.ProgressActivity(activityGeneralIdRef); + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/e-suite.API/eSuite.API/eSuite.API.csproj b/e-suite.API/eSuite.API/eSuite.API.csproj index 99ec5bb..fd14dd2 100644 --- a/e-suite.API/eSuite.API/eSuite.API.csproj +++ b/e-suite.API/eSuite.API/eSuite.API.csproj @@ -55,6 +55,7 @@ + diff --git a/e-suite.MessageProcessor/e-suite.MessageProcessor/DependencyInjection/CoreRegistrationModule.cs b/e-suite.MessageProcessor/e-suite.MessageProcessor/DependencyInjection/CoreRegistrationModule.cs index 364a5c0..dcfa0e5 100644 --- a/e-suite.MessageProcessor/e-suite.MessageProcessor/DependencyInjection/CoreRegistrationModule.cs +++ b/e-suite.MessageProcessor/e-suite.MessageProcessor/DependencyInjection/CoreRegistrationModule.cs @@ -41,5 +41,6 @@ public class CoreRegistrationModule : ESuiteModule builder.RegisterType().InstancePerLifetimeScope(); builder.RegisterType().InstancePerLifetimeScope(); builder.RegisterType().InstancePerLifetimeScope(); + builder.RegisterType().InstancePerLifetimeScope(); } } \ No newline at end of file diff --git a/e-suite.MessageProcessor/e-suite.MessageProcessor/QueueProcessor/QueueProcessor.cs b/e-suite.MessageProcessor/e-suite.MessageProcessor/QueueProcessor/QueueProcessor.cs index 482f213..2619e04 100644 --- a/e-suite.MessageProcessor/e-suite.MessageProcessor/QueueProcessor/QueueProcessor.cs +++ b/e-suite.MessageProcessor/e-suite.MessageProcessor/QueueProcessor/QueueProcessor.cs @@ -112,7 +112,7 @@ public class QueueProcessorService : IHostedService } catch (AlreadyClosedException) { - //Do nothing here MqRaabbit has already shut things down. + //Do nothing here RabbitMq has already shut things down. } } }; @@ -120,6 +120,13 @@ public class QueueProcessorService : IHostedService autoAck: false, consumer: consumer1); + channel.ChannelShutdownAsync += (sender, args) => + { + _logger.LogWarning("Channel shutdown: {Reason}", args.ReplyText); + return Task.CompletedTask; + }; + + return channel; } } \ No newline at end of file diff --git a/e-suite.Messaging.Common/e-suite.Messaging.Common/ActivityMessageSender.cs b/e-suite.Messaging.Common/e-suite.Messaging.Common/ActivityMessageSender.cs new file mode 100644 index 0000000..203b4e5 --- /dev/null +++ b/e-suite.Messaging.Common/e-suite.Messaging.Common/ActivityMessageSender.cs @@ -0,0 +1,26 @@ +using e_suite.Messaging.Common.models; +using eSuite.Core.Miscellaneous; +using Microsoft.Extensions.Configuration; + +namespace e_suite.Messaging.Common; + +public class ActivityMessageSender : MessageSenderBase, IActivityMessageSender +{ + public ActivityMessageSender(IConfiguration configuration, IRabbitMqConnectionFactory connectionFactory) : base(configuration, connectionFactory, MessageQueueNames.Activity) + { + } + + public void ProgressActivity(GeneralIdRef generalIdRef) + { + var progressActivityMessage = new ProgressActivityMessage + { + MessageType = ActivityMessageTypes.ProgressActivity, + ActivityId = generalIdRef + }; + + Task.Run(async () => + { + await PostMessage(progressActivityMessage); + }).Wait(); + } +} \ No newline at end of file diff --git a/e-suite.Messaging.Common/e-suite.Messaging.Common/IActivityMessageSender.cs b/e-suite.Messaging.Common/e-suite.Messaging.Common/IActivityMessageSender.cs new file mode 100644 index 0000000..93d07f3 --- /dev/null +++ b/e-suite.Messaging.Common/e-suite.Messaging.Common/IActivityMessageSender.cs @@ -0,0 +1,8 @@ +using eSuite.Core.Miscellaneous; + +namespace e_suite.Messaging.Common; + +public interface IActivityMessageSender +{ + void ProgressActivity(GeneralIdRef generalIdRef); +} \ No newline at end of file diff --git a/e-suite.Messaging.Common/e-suite.Messaging.Common/IDatabaseMessageSender.cs b/e-suite.Messaging.Common/e-suite.Messaging.Common/IDatabaseMessageSender.cs index b6b0f88..99250ed 100644 --- a/e-suite.Messaging.Common/e-suite.Messaging.Common/IDatabaseMessageSender.cs +++ b/e-suite.Messaging.Common/e-suite.Messaging.Common/IDatabaseMessageSender.cs @@ -9,4 +9,4 @@ public interface IDatabaseMessageSender void PostClearOldEmailActions(); void PostClearOldSingleUserGuids(); -} \ No newline at end of file +} diff --git a/e-suite.Messaging.Common/e-suite.Messaging.Common/IocRegistration.cs b/e-suite.Messaging.Common/e-suite.Messaging.Common/IocRegistration.cs index eef9793..1ee07ce 100644 --- a/e-suite.Messaging.Common/e-suite.Messaging.Common/IocRegistration.cs +++ b/e-suite.Messaging.Common/e-suite.Messaging.Common/IocRegistration.cs @@ -12,5 +12,6 @@ public class IocRegistration : IIocRegistration builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); + builder.RegisterType().As(); } } \ No newline at end of file diff --git a/e-suite.Messaging.Common/e-suite.Messaging.Common/models/ActivityMessage.cs b/e-suite.Messaging.Common/e-suite.Messaging.Common/models/ActivityMessage.cs index a789c85..81d5394 100644 --- a/e-suite.Messaging.Common/e-suite.Messaging.Common/models/ActivityMessage.cs +++ b/e-suite.Messaging.Common/e-suite.Messaging.Common/models/ActivityMessage.cs @@ -9,5 +9,7 @@ public class ActivityMessage public class ProgressActivityMessage : ActivityMessage { + + public GeneralIdRef ActivityId { get; set; } } \ No newline at end of file diff --git a/e-suite.Modules.RunningActivityManager/ActivityManager.cs b/e-suite.Modules.RunningActivityManager/ActivityManager.cs new file mode 100644 index 0000000..08210bf --- /dev/null +++ b/e-suite.Modules.RunningActivityManager/ActivityManager.cs @@ -0,0 +1,52 @@ +using e_suite.API.Common; +using e_suite.API.Common.repository; +using e_suite.Database.Audit; +using e_suite.Database.Core.Extensions; +using e_suite.Database.Core.Tables.Activity; +using e_suite.Messaging.Common; +using eSuite.Core.Miscellaneous; +using Microsoft.EntityFrameworkCore; + +namespace e_suite.Modules.RunningActivityManager; + +public class ActivityManager : IActivityManager +{ + private readonly IActivityMessageSender _activityMessageSender; + private readonly IActivityRepository _activityRepository; + private readonly IWorkflowTemplateRepository _workflowTemplateRepository; + + public ActivityManager(IActivityMessageSender activityMessageSender, IActivityRepository activityRepository, IWorkflowTemplateRepository workflowTemplateRepository) + { + _activityMessageSender = activityMessageSender; + _activityRepository = activityRepository; + _workflowTemplateRepository = workflowTemplateRepository; + } + + public async Task CreateActivity( + AuditUserDetails auditUserDetails, + CreateActivity template, + CancellationToken cancellationToken + ) + { + GeneralIdRef? progressActivityRef = null; + await _activityRepository.TransactionAsync(async () => + { + var workflowTemplate = _workflowTemplateRepository.GetWorkflows().Include( x => x.Versions).FindByGeneralIdRef(template.WorkflowTemplateId); + var workflowTemplateVersion = workflowTemplate!.Versions.OrderByDescending(x => x.Version).First(); + + Activity activity = new Activity + { + Guid = Guid.NewGuid(), + Name = template.ActivityName, + WorkflowVersion = workflowTemplateVersion, + }; + + await _activityRepository.CreateActivityInstanceAsync(auditUserDetails, activity, cancellationToken); + + progressActivityRef = activity.ToGeneralIdRef(); + }); + + if (progressActivityRef != null) + _activityMessageSender.ProgressActivity(progressActivityRef); + } +} \ No newline at end of file diff --git a/e-suite.Modules.RunningActivityManager/IocRegistration.cs b/e-suite.Modules.RunningActivityManager/IocRegistration.cs new file mode 100644 index 0000000..fb6f359 --- /dev/null +++ b/e-suite.Modules.RunningActivityManager/IocRegistration.cs @@ -0,0 +1,16 @@ +using Autofac; +using e_suite.API.Common; +using e_suite.API.Common.repository; +using e_suite.DependencyInjection; +using e_suite.Modules.RunningActivityManager.repository; + +namespace e_suite.Modules.RunningActivityManager; + +public class IocRegistration : IIocRegistration +{ + public void RegisterTypes(ContainerBuilder builder) + { + builder.RegisterType().As().InstancePerLifetimeScope(); + builder.RegisterType().As().InstancePerLifetimeScope(); + } +} \ No newline at end of file diff --git a/e-suite.Modules.RunningActivityManager/e-suite.Modules.RunningActivityManager.csproj b/e-suite.Modules.RunningActivityManager/e-suite.Modules.RunningActivityManager.csproj new file mode 100644 index 0000000..6e33881 --- /dev/null +++ b/e-suite.Modules.RunningActivityManager/e-suite.Modules.RunningActivityManager.csproj @@ -0,0 +1,15 @@ + + + + net10.0 + e_suite.Modules.RunningActivityManager + enable + enable + + + + + + + + diff --git a/e-suite.Modules.RunningActivityManager/repository/ActivityRepository.cs b/e-suite.Modules.RunningActivityManager/repository/ActivityRepository.cs new file mode 100644 index 0000000..3c1cdbf --- /dev/null +++ b/e-suite.Modules.RunningActivityManager/repository/ActivityRepository.cs @@ -0,0 +1,23 @@ +using e_suite.API.Common.repository; +using e_suite.Database.Audit; +using e_suite.Database.Core; +using e_suite.Database.Core.Tables.Activity; + +namespace e_suite.Modules.RunningActivityManager.repository; + +public class ActivityRepository : RepositoryBase, IActivityRepository +{ + public ActivityRepository(IEsuiteDatabaseDbContext databaseDbContext) : base(databaseDbContext) + { + } + + public async Task CreateActivityInstanceAsync( + AuditUserDetails auditUserDetails, + Activity activity, + CancellationToken cancellationToken + ) + { + DatabaseDbContext.Activities.Add(activity); + await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken); + } +} \ No newline at end of file diff --git a/eSuite.sln b/eSuite.sln index a6e7cba..a704504 100644 --- a/eSuite.sln +++ b/eSuite.sln @@ -165,6 +165,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "e-suite.Modules.WorkflowTem EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eSuite.Translator", "eSuite.Translator\eSuite.Translator.csproj", "{2D02CADA-C004-4B0C-B6DE-097647CB6BA3}" 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -463,6 +465,10 @@ Global {2D02CADA-C004-4B0C-B6DE-097647CB6BA3}.Debug|Any CPU.Build.0 = Debug|Any CPU {2D02CADA-C004-4B0C-B6DE-097647CB6BA3}.Release|Any CPU.ActiveCfg = Release|Any CPU {2D02CADA-C004-4B0C-B6DE-097647CB6BA3}.Release|Any CPU.Build.0 = Release|Any CPU + {DE96B3AE-986A-43FE-9C30-14553D0CFCE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -536,6 +542,7 @@ Global {1D974411-767E-4242-96F7-E545D285B356} = {A0787221-622B-4D00-A566-BF7297378322} {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C5175258-F776-4FF9-A9EE-386312E47061}