diff --git a/e-suite.API.Common/e-suite.API.Common/IWorkflowTemplateManager.cs b/e-suite.API.Common/e-suite.API.Common/IWorkflowTemplateManager.cs index d2e1f10..dde909f 100644 --- a/e-suite.API.Common/e-suite.API.Common/IWorkflowTemplateManager.cs +++ b/e-suite.API.Common/e-suite.API.Common/IWorkflowTemplateManager.cs @@ -82,16 +82,14 @@ public class PatchWorkflowTemplateVersion public class CreateWorkflowTemplateVersion { public List Tasks { get; set; } = []; - - public required GeneralIdRef WorkflowId { get; set; } - - public long Version { get; set; } - + + public required string WorkflowName { get; set; } + public required GeneralIdRef DomainId { get; set; } public string Description { get; set; } = String.Empty; - public string ActivityNameTemplate { get; set; } = String.Empty; + public required string ActivityNameTemplate { get; set; } = String.Empty; } public class TaskMetadata diff --git a/e-suite.API.Common/e-suite.API.Common/repository/IRoleManagerRepository.cs b/e-suite.API.Common/e-suite.API.Common/repository/IRoleManagerRepository.cs index 5feb5d6..c032ac1 100644 --- a/e-suite.API.Common/e-suite.API.Common/repository/IRoleManagerRepository.cs +++ b/e-suite.API.Common/e-suite.API.Common/repository/IRoleManagerRepository.cs @@ -9,7 +9,8 @@ namespace e_suite.API.Common.repository; public interface IRoleManagerRepository : IRepository { IQueryable GetRolesList(); - Task GetRoleById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken); + Role? GetRoleById(IGeneralIdRef generalIdRef); + Task GetRoleByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken); Task GetRoleByName(Domain domain, string sequenceName, CancellationToken cancellationToken); Task EditRole(AuditUserDetails auditUserDetails, Role existingSequence, CancellationToken cancellationToken); Task AddRole(AuditUserDetails auditUserDetails, Role existingSequence, CancellationToken cancellationToken); diff --git a/e-suite.API.Common/e-suite.API.Common/repository/IUserManagerRepository.cs b/e-suite.API.Common/e-suite.API.Common/repository/IUserManagerRepository.cs index ca1b7c4..9eab522 100644 --- a/e-suite.API.Common/e-suite.API.Common/repository/IUserManagerRepository.cs +++ b/e-suite.API.Common/e-suite.API.Common/repository/IUserManagerRepository.cs @@ -21,7 +21,8 @@ public interface IUserManagerRepository : IRepository Task GetCurrentEmailUserAction(long userId, EmailUserActionType emailUserActionType, CancellationToken cancellationToken); Task DeleteEmailUserAction(AuditUserDetails auditUserDetails, EmailUserAction emailUserAction, CancellationToken cancellationToken); IQueryable GetUsers(); - Task GetUserById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken); + User? GetUserById(IGeneralIdRef generalIdRef); + Task GetUserByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken); Task GetSsoProviderById(long id, CancellationToken cancellationToken); IQueryable GetSsoProviders(); Task SaveSingleUseGuidForUser(SingleUseGuid singleUseGuid, CancellationToken cancellationToken); 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 6e16d87..a80ca75 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 @@ -9,4 +9,6 @@ public interface IWorkflowTemplateRepository : IRepository public IQueryable GetWorkflows(); IQueryable GetWorkflowVersions(); Task EditWorkflowVersionAsync(AuditUserDetails auditUserDetails, WorkflowVersion workflowVersion, CancellationToken cancellationToken); + Task AddWorkflow(AuditUserDetails auditUserDetails, Workflow workflow, CancellationToken cancellationToken); + Task AddWorkflowVersion(AuditUserDetails auditUserDetails, WorkflowVersion workflowVersion, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager.UnitTests/Helpers/FakeRoleManagerRepository.cs b/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager.UnitTests/Helpers/FakeRoleManagerRepository.cs index a2477ab..e15dcb0 100644 --- a/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager.UnitTests/Helpers/FakeRoleManagerRepository.cs +++ b/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager.UnitTests/Helpers/FakeRoleManagerRepository.cs @@ -27,7 +27,12 @@ public class FakeRoleManagerRepository : FakeRepository, IRoleManagerRepository return Roles.BuildMock(); } - public Task GetRoleById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) + public Role? GetRoleById(IGeneralIdRef generalIdRef) + { + throw new NotImplementedException(); + } + + public Task GetRoleByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) { return Task.FromResult(GetRolesList().FindByGeneralIdRef(generalIdRef)); } diff --git a/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager.UnitTests/Helpers/FakeUserManagerRepository.cs b/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager.UnitTests/Helpers/FakeUserManagerRepository.cs index 5418e1b..747107b 100644 --- a/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager.UnitTests/Helpers/FakeUserManagerRepository.cs +++ b/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager.UnitTests/Helpers/FakeUserManagerRepository.cs @@ -75,7 +75,12 @@ public class FakeUserManagerRepository : FakeRepository, IUserManagerRepository return Users.BuildMock(); } - public Task GetUserById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) + public User? GetUserById(IGeneralIdRef generalIdRef) + { + throw new NotImplementedException(); + } + + public Task GetUserByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) { return Task.FromResult(Users.SingleOrDefault(x => x.Id == generalIdRef.Id || x.Guid == generalIdRef.Guid)); } diff --git a/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager/Repository/RoleManagerRepository.cs b/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager/Repository/RoleManagerRepository.cs index 0823237..0cbad13 100644 --- a/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager/Repository/RoleManagerRepository.cs +++ b/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager/Repository/RoleManagerRepository.cs @@ -21,8 +21,14 @@ public class RoleManagerRepository : RepositoryBase, IRoleManagerRepository .Include(nameof(Role.Domain)) .Where(x => !x.Deleted); } + public Role? GetRoleById(IGeneralIdRef generalIdRef) + { + return DatabaseDbContext.Roles + .Include(nameof(Domain)).AsEnumerable() + .FindByGeneralIdRef(generalIdRef); + } - public async Task GetRoleById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) + public async Task GetRoleByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) { return await DatabaseDbContext.Roles .Include(nameof(Domain)) diff --git a/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager/RoleManager.cs b/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager/RoleManager.cs index ec5c882..a2f2a48 100644 --- a/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager/RoleManager.cs +++ b/e-suite.Modules.RoleManager/e-suite.Modules.RoleManager/RoleManager.cs @@ -86,7 +86,7 @@ public class RoleManager : IRoleManager public async Task GetRole(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) { - var role = await _roleManagerRepository.GetRoleById(generalIdRef, cancellationToken) ?? + var role = await _roleManagerRepository.GetRoleByIdAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("Role not found"); if (role.Deleted) @@ -104,7 +104,7 @@ public class RoleManager : IRoleManager { var existingRole = role.Guid == null ? null - : await _roleManagerRepository.GetRoleById(new GeneralIdRef + : await _roleManagerRepository.GetRoleByIdAsync(new GeneralIdRef { Guid = role.Guid }, cancellationToken); @@ -157,7 +157,7 @@ public class RoleManager : IRoleManager throw new InvalidOperationException("GeneralIdRef cannot be null"); var existingRole = - await _roleManagerRepository.GetRoleById(editRole.GeneralIdRef, cancellationToken); + await _roleManagerRepository.GetRoleByIdAsync(editRole.GeneralIdRef, cancellationToken); if (existingRole == null || existingRole.Deleted) throw new NotFoundException("A role with this Id doesn't exist"); @@ -179,7 +179,7 @@ public class RoleManager : IRoleManager { await _roleManagerRepository.TransactionAsync(async () => { - var existingRole = await _roleManagerRepository.GetRoleById(generalIdRef, cancellationToken); + var existingRole = await _roleManagerRepository.GetRoleByIdAsync(generalIdRef, cancellationToken); if (existingRole == null || existingRole.Deleted) throw new NotFoundException("A role with this Id does not exist"); @@ -191,7 +191,7 @@ public class RoleManager : IRoleManager public async Task> GetRoleUsers(Paging paging, GeneralIdRef roleId, CancellationToken cancellationToken) { - var role = await _roleManagerRepository.GetRoleById(roleId, cancellationToken); + var role = await _roleManagerRepository.GetRoleByIdAsync(roleId, cancellationToken); var roleUsers = _roleManagerRepository.GetUserRoles(); @@ -240,12 +240,12 @@ public class RoleManager : IRoleManager { await _roleManagerRepository.TransactionAsync(async () => { - var existingRole = await _roleManagerRepository.GetRoleById(userRoleIds.RoleId, cancellationToken); + var existingRole = await _roleManagerRepository.GetRoleByIdAsync(userRoleIds.RoleId, cancellationToken); if (existingRole == null || existingRole.Deleted) throw new NotFoundException("Role Not Found"); - var existingUser = await _userManagerRepository.GetUserById(userRoleIds.UserId, cancellationToken); + var existingUser = await _userManagerRepository.GetUserByIdAsync(userRoleIds.UserId, cancellationToken); if (existingUser == null || !existingUser.Active) throw new NotFoundException("User not Found"); @@ -259,12 +259,12 @@ public class RoleManager : IRoleManager { await _roleManagerRepository.TransactionAsync(async () => { - var existingRole = await _roleManagerRepository.GetRoleById(userRoleIds.RoleId, cancellationToken); + var existingRole = await _roleManagerRepository.GetRoleByIdAsync(userRoleIds.RoleId, cancellationToken); if (existingRole == null || existingRole.Deleted) throw new NotFoundException("Role Not Found"); - var existingUser = await _userManagerRepository.GetUserById(userRoleIds.UserId, cancellationToken); + var existingUser = await _userManagerRepository.GetUserByIdAsync(userRoleIds.UserId, cancellationToken); if (existingUser == null || !existingUser.Active) throw new NotFoundException("User not Found"); @@ -331,7 +331,7 @@ public class RoleManager : IRoleManager public async Task AddRoleSecurityAccess(AuditUserDetails auditUserDetails, AddRoleSecurityAccess accessToAdd, CancellationToken cancellationToken) { - var role = await _roleManagerRepository.GetRoleById(accessToAdd.RoleId, cancellationToken) ?? + var role = await _roleManagerRepository.GetRoleByIdAsync(accessToAdd.RoleId, cancellationToken) ?? throw new NotFoundException("Role Not Found"); var rollAccessToAdd = accessToAdd.SecurityAccess.Select( @@ -351,7 +351,7 @@ public class RoleManager : IRoleManager CancellationToken cancellationToken ) { - var role = await _roleManagerRepository.GetRoleById(accessToRemove.RoleId, cancellationToken) ?? + var role = await _roleManagerRepository.GetRoleByIdAsync(accessToRemove.RoleId, cancellationToken) ?? throw new NotFoundException("Role Not Found"); var rollAccessToDelete = _roleManagerRepository.GetAccessForRole() @@ -400,7 +400,7 @@ public class RoleManager : IRoleManager Id = userId }; - var user = await _userManagerRepository.GetUserById(generalIdRef, CancellationToken.None) ?? + var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, CancellationToken.None) ?? throw new NotFoundException("User Not Found"); if (!user.Active) @@ -441,7 +441,7 @@ public class RoleManager : IRoleManager domainToCheck = await _domainRepository.GetDomainById(specificDomain, cancellationToken); else { - var user = await _userManagerRepository.GetUserById(new GeneralIdRef + var user = await _userManagerRepository.GetUserByIdAsync(new GeneralIdRef { Id = userId }, cancellationToken); diff --git a/e-suite.Modules.UserManager/UserManager.UnitTests/Repository/FakeUserManagerRepository.cs b/e-suite.Modules.UserManager/UserManager.UnitTests/Repository/FakeUserManagerRepository.cs index 4157f57..f9f9bc0 100644 --- a/e-suite.Modules.UserManager/UserManager.UnitTests/Repository/FakeUserManagerRepository.cs +++ b/e-suite.Modules.UserManager/UserManager.UnitTests/Repository/FakeUserManagerRepository.cs @@ -145,7 +145,12 @@ public class FakeUserManagerRepository : FakeRepository, IUserManagerRepository return Users.BuildMock(); } - public Task GetUserById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) + public User? GetUserById(IGeneralIdRef generalIdRef) + { + throw new NotImplementedException(); + } + + public Task GetUserByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) { return Task.FromResult(Users.SingleOrDefault(x => x.Id == generalIdRef.Id || x.Guid == generalIdRef.Guid )); } diff --git a/e-suite.Modules.UserManager/e-suite.Modules.UserManager/Repository/UserManagerRepository.cs b/e-suite.Modules.UserManager/e-suite.Modules.UserManager/Repository/UserManagerRepository.cs index b43d0f7..1827f12 100644 --- a/e-suite.Modules.UserManager/e-suite.Modules.UserManager/Repository/UserManagerRepository.cs +++ b/e-suite.Modules.UserManager/e-suite.Modules.UserManager/Repository/UserManagerRepository.cs @@ -47,7 +47,19 @@ public class UserManagerRepository : RepositoryBase, IUserManagerRepository return users.FirstOrDefault(); } - public async Task GetUserById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) + + public User? GetUserById(IGeneralIdRef generalIdRef) + { + var user = GetUsers().AsEnumerable().FindByGeneralIdRef(generalIdRef); + if (user != null) + { + DatabaseDbContext.Entry(user).Reload(); // re-fetch from DB + } + + return user; + } + + public async Task GetUserByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken) { var user = await GetUsers().FindByGeneralIdRefAsync(generalIdRef, cancellationToken); if (user != null) diff --git a/e-suite.Modules.UserManager/e-suite.Modules.UserManager/UserManager.cs b/e-suite.Modules.UserManager/e-suite.Modules.UserManager/UserManager.cs index 2aa3f58..4445262 100644 --- a/e-suite.Modules.UserManager/e-suite.Modules.UserManager/UserManager.cs +++ b/e-suite.Modules.UserManager/e-suite.Modules.UserManager/UserManager.cs @@ -261,7 +261,7 @@ public class UserManager : IUserManager ValidateEmail(userRegistration.Email); await CheckForExistingUserByEmail(userRegistration.Email, cancellationToken); - var currentUser = await _userManagerRepository.GetUserById(new GeneralIdRef + var currentUser = await _userManagerRepository.GetUserByIdAsync(new GeneralIdRef { Id = auditUserDetails.UserId }, cancellationToken); @@ -328,7 +328,7 @@ public class UserManager : IUserManager public async Task RefreshToken(IGeneralIdRef id, CancellationToken cancellationToken) { - var user = await _userManagerRepository.GetUserById(id, cancellationToken); + var user = await _userManagerRepository.GetUserByIdAsync(id, cancellationToken); return RefreshToken(user); } @@ -418,7 +418,7 @@ public class UserManager : IUserManager { await _userManagerRepository.TransactionAsync(async () => { - var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken) + var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("Unable to find user"); await DeactivateUser(auditUserDetails, user, cancellationToken); @@ -627,7 +627,7 @@ public class UserManager : IUserManager { await _userManagerRepository.TransactionAsync(async () => { - var user = await _userManagerRepository.GetUserById(userId, cancellationToken) + var user = await _userManagerRepository.GetUserByIdAsync(userId, cancellationToken) ?? throw new NotFoundException("unable to find user"); if (!user.Active) @@ -674,7 +674,7 @@ public class UserManager : IUserManager public async Task GetUserAsync(GeneralIdRef generalIdRef, CancellationToken cancellationToken) { - var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken) + var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found"); return MapUser(user); @@ -747,7 +747,7 @@ public class UserManager : IUserManager CancellationToken cancellationToken ) { - var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken) + var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found"); await SendEmailUserAction(auditUserDetails, user, EmailUserActionType.ConfirmEmailAddress, cancellationToken); @@ -807,7 +807,7 @@ public class UserManager : IUserManager public async Task TurnOfSsoForUser(AuditUserDetails auditUserDetails, GeneralIdRef generalIdRef, CancellationToken cancellationToken) { - var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found"); + var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found"); user.SsoProviderId = null; user.SsoSubject = string.Empty; @@ -822,7 +822,7 @@ public class UserManager : IUserManager CancellationToken cancellationToken ) { - var user = await _userManagerRepository.GetUserById(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found"); + var user = await _userManagerRepository.GetUserByIdAsync(generalIdRef, cancellationToken) ?? throw new NotFoundException("User not found"); var singleUseGuid = new SingleUseGuid { @@ -854,7 +854,7 @@ public class UserManager : IUserManager { await _userManagerRepository.TransactionAsync(async () => { - var user = await _userManagerRepository.GetUserById(userAuthenticationDetails.Id, cancellationToken) + var user = await _userManagerRepository.GetUserByIdAsync(userAuthenticationDetails.Id, cancellationToken) ?? throw new NotFoundException("User not found"); var userProfile = new UpdatedUserProfile diff --git a/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowTemplateRepository.cs b/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowTemplateRepository.cs index a5dd106..9f8d836 100644 --- a/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowTemplateRepository.cs +++ b/e-suite.Modules.WorkflowTemplatesManager/Repository/WorkflowTemplateRepository.cs @@ -2,18 +2,63 @@ 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 { - WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion); + Workflow.Core.WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion); Task SerialiseToDatabase( - WorkflowVersion runtime, + Workflow.Core.WorkflowVersion runtime, e_suite.Database.Core.Tables.Workflow.WorkflowVersion? dbObject = null, CancellationToken cancellationToken = default ); @@ -21,15 +66,31 @@ public interface IWorkflowConverter public class WorkflowConverter : IWorkflowConverter { private readonly IDomainRepository _domainRepository; + private readonly IRoleManagerRepository _roleManagerRepository; + private readonly IUserManagerRepository _userManagerRepository; - public WorkflowConverter(IDomainRepository domainRepository) + 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 WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion) + public Workflow.Core.WorkflowVersion DeserialiseFromDatabase(e_suite.Database.Core.Tables.Workflow.WorkflowVersion dbVersion) { - var runtime = new WorkflowVersion + var runtime = new Workflow.Core.WorkflowVersion { Id = dbVersion.Id, Guid = dbVersion.Guid, @@ -47,14 +108,14 @@ public class WorkflowConverter : IWorkflowConverter foreach (var def in dbVersion.Tasks) { - var task = def.ToTask(); + var task = def.ToTask(_jsonSerializerOptions); runtime.Tasks.Add(task); } return runtime; } - - public async Task SerialiseToDatabase(WorkflowVersion runtime, e_suite.Database.Core.Tables.Workflow.WorkflowVersion? dbObject = null, CancellationToken cancellationToken = default) + + 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(); @@ -111,4 +172,20 @@ public class WorkflowTemplateRepository : RepositoryBase, IWorkflowTemplateRepos { await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken); } + + public async Task AddWorkflow(AuditUserDetails auditUserDetails, Database.Core.Tables.Workflow.Workflow workflow, CancellationToken cancellationToken) + { + DatabaseDbContext.Workflows.Add(workflow); + await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken); + } + + public async Task AddWorkflowVersion( + AuditUserDetails auditUserDetails, + Database.Core.Tables.Workflow.WorkflowVersion workflowVersion, + CancellationToken cancellationToken + ) + { + DatabaseDbContext.WorkflowVersions.Add(workflowVersion); + await DatabaseDbContext.SaveChangesAsync(auditUserDetails, cancellationToken); + } } \ No newline at end of file diff --git a/e-suite.Modules.WorkflowTemplatesManager/WorkflowTemplateManager.cs b/e-suite.Modules.WorkflowTemplatesManager/WorkflowTemplateManager.cs index 70384d9..c83588c 100644 --- a/e-suite.Modules.WorkflowTemplatesManager/WorkflowTemplateManager.cs +++ b/e-suite.Modules.WorkflowTemplatesManager/WorkflowTemplateManager.cs @@ -11,6 +11,8 @@ using e_suite.Workflow.Core.Interfaces; using eSuite.Core.Miscellaneous; using System.Linq.Expressions; using System.Reflection; +using e_suite.Database.Core.Tables.Workflow; +using Microsoft.AspNetCore.Mvc; //using WorkflowVersion = e_suite.Workflow.Core.WorkflowVersion; @@ -23,11 +25,12 @@ public class WorkflowTemplateManager : IWorkflowTemplateManager private readonly IDomainRepository _domainRepository; private readonly IPatchFactory _patchFactory; - public WorkflowTemplateManager(IWorkflowTemplateRepository workflowTemplateRepository, IPatchFactory patchFactory, IDomainRepository domainRepository) + public WorkflowTemplateManager(IWorkflowTemplateRepository workflowTemplateRepository, IPatchFactory patchFactory, IDomainRepository domainRepository, IWorkflowConverter workflowConverter) { _workflowTemplateRepository = workflowTemplateRepository; _patchFactory = patchFactory; _domainRepository = domainRepository; + _workflowConverter = workflowConverter; } public async Task> GetWorkflowTemplatesAsync(Paging paging, CancellationToken cancellationToken) @@ -123,10 +126,45 @@ public class WorkflowTemplateManager : IWorkflowTemplateManager CancellationToken cancellationToken ) { + await _workflowTemplateRepository.TransactionAsync(async () => + { + if (_workflowTemplateRepository.GetWorkflows().FirstOrDefault(x => x.Name == template.WorkflowName) != + null) + { + throw new ExistsException("Workflow already exists"); + } - //var workflowTemplate = _workflowConverter.DeserialiseFromDatabase(dbWorkflowTemplate); + var workflow = new Database.Core.Tables.Workflow.Workflow + { + Name = template.WorkflowName, + Guid = Guid.NewGuid() + }; - throw new NotImplementedException(); + await _workflowTemplateRepository.AddWorkflow(auditUserDetails, workflow, cancellationToken); + + var workflowDomain = await _domainRepository.GetDomainById(template.DomainId, cancellationToken); + + var dbWorkflowTemplate = new WorkflowVersion + { + Guid = Guid.NewGuid(), + ActivityNameTemplate = template.ActivityNameTemplate, + Description = template.Description, + Domain = workflowDomain, + Tasks = template.Tasks, + Workflow = workflow, + }; + + Workflow.Core.WorkflowVersion? workflowTemplate = _workflowConverter.DeserialiseFromDatabase(dbWorkflowTemplate); + if (workflowTemplate is null) + { + throw new InvalidDataException("The workflow template is not valid"); + } + + await _workflowTemplateRepository.AddWorkflowVersion(auditUserDetails, dbWorkflowTemplate, cancellationToken); + + + } + ); } public async Task DeleteTemplateVersion(AuditUserDetails auditUserDetails, IGeneralIdRef templateId, CancellationToken cancellationToken) diff --git a/e-suite.Workflow.Core/Extensions/TaskExtensions.cs b/e-suite.Workflow.Core/Extensions/TaskExtensions.cs index c6b3f09..d0eb4e5 100644 --- a/e-suite.Workflow.Core/Extensions/TaskExtensions.cs +++ b/e-suite.Workflow.Core/Extensions/TaskExtensions.cs @@ -1,7 +1,12 @@ using e_suite.Database.Core.Models; +using e_suite.Database.Core.Tables.Contacts; +using e_suite.Database.Core.Tables.Domain; using e_suite.Workflow.Core.Attributes; using e_suite.Workflow.Core.Interfaces; +using eSuite.Core.Miscellaneous; using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; namespace e_suite.Workflow.Core.Extensions; @@ -30,7 +35,7 @@ public static class TaskExtensions return dictionary; } - public static ITask ToTask(this TaskDefinition definition) + public static ITask ToTask(this TaskDefinition definition, JsonSerializerOptions jsonSerializerOptions) { var type = Type.GetType(definition.Type); @@ -42,28 +47,41 @@ public static class TaskExtensions if (instance == null) throw new InvalidOperationException($"Type '{definition.Type}' does not implement ITask."); - FromConfigDictionary(instance, definition.Config); + FromConfigDictionary(instance, definition.Config, jsonSerializerOptions); return instance; } - public static void FromConfigDictionary(this object obj, Dictionary dict) + public static void FromConfigDictionary(this object obj, Dictionary dict, JsonSerializerOptions jsonSerializerOptions) { var type = obj.GetType(); foreach (var kvp in dict) { - var prop = type.GetProperty(kvp.Key); + var prop = type.GetProperty( + kvp.Key, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase + ); + if (prop == null || !prop.CanWrite) continue; var targetType = prop.PropertyType; var value = kvp.Value; - // Handle enums (the only case where JSON gives you a string) - if (value != null && targetType.IsEnum) + // If the value is a JsonElement, convert it to the target type + if (value is JsonElement je) { - value = Enum.Parse(targetType, value.ToString()!, ignoreCase: true); + if (targetType.IsEnum) + { + // Enums come through as strings + value = Enum.Parse(targetType, je.GetString()!, ignoreCase: true); + } + else + { + // Deserialize JSON into the target type (handles lists, objects, primitives) + value = JsonSerializer.Deserialize(je.GetRawText(), targetType, jsonSerializerOptions); + } } prop.SetValue(obj, value); diff --git a/e-suite.Workflow.Core/Interfaces/IAssignees.cs b/e-suite.Workflow.Core/Interfaces/IAssignees.cs index 591a6f2..859321f 100644 --- a/e-suite.Workflow.Core/Interfaces/IAssignees.cs +++ b/e-suite.Workflow.Core/Interfaces/IAssignees.cs @@ -7,6 +7,6 @@ namespace e_suite.Workflow.Core.Interfaces; public interface IAssignees where T : ITaskAssignee { [Required] - IList Assignees { get; } + List Assignees { get; set; } } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Interfaces/ITaskAssignee.cs b/e-suite.Workflow.Core/Interfaces/ITaskAssignee.cs index a0cd7c1..9ef6d20 100644 --- a/e-suite.Workflow.Core/Interfaces/ITaskAssignee.cs +++ b/e-suite.Workflow.Core/Interfaces/ITaskAssignee.cs @@ -1,5 +1,5 @@ -using e_suite.Database.Core.Tables.Contacts; -using e_suite.Database.Core.Tables.Domain; +using e_suite.Database.Core.Tables.Domain; +using e_suite.Database.Core.Tables.UserManager; using e_suite.Workflow.Core.Attributes; using e_suite.Workflow.Core.Enums; @@ -9,7 +9,7 @@ namespace e_suite.Workflow.Core.Interfaces; public interface ITaskAssignee { public Role? Role { get; set; } - public Contact? Contact { get; set; } + public User? User { get; set; } public Raci Raci { get; set; } } \ No newline at end of file diff --git a/e-suite.Workflow.Core/TaskAssignee.cs b/e-suite.Workflow.Core/TaskAssignee.cs index 977a9fb..e31e8f9 100644 --- a/e-suite.Workflow.Core/TaskAssignee.cs +++ b/e-suite.Workflow.Core/TaskAssignee.cs @@ -1,6 +1,6 @@ -using e_suite.Database.Core.Tables.Contacts; -using e_suite.Database.Core.Tables.Domain; +using e_suite.Database.Core.Tables.Domain; using System.ComponentModel.DataAnnotations; +using e_suite.Database.Core.Tables.UserManager; using e_suite.Workflow.Core.Enums; using e_suite.Workflow.Core.Interfaces; @@ -9,16 +9,16 @@ namespace e_suite.Workflow.Core; public class TaskAssignee : ITaskAssignee, IValidatableObject { public Role? Role { get; set; } - public Contact? Contact { get; set; } + public User? User { get; set; } public Raci Raci { get; set; } public IEnumerable Validate(ValidationContext validationContext) { - if (!((Role != null) ^ (Contact != null))) //Role Xor Contact means either must be set, but not both the same + if (!((Role != null) ^ (User != null))) //Role Xor Contact means either must be set, but not both the same { yield return new ValidationResult( "Either Role or Contact must be set, but not both.", - [nameof(Role), nameof(Contact)] + [nameof(Role), nameof(User)] ); } } diff --git a/e-suite.Workflow.Core/Tasks/AdhocApprovalTask.cs b/e-suite.Workflow.Core/Tasks/AdhocApprovalTask.cs index aec5eb2..f0f4be3 100644 --- a/e-suite.Workflow.Core/Tasks/AdhocApprovalTask.cs +++ b/e-suite.Workflow.Core/Tasks/AdhocApprovalTask.cs @@ -5,9 +5,9 @@ using e_suite.Workflow.Core.Interfaces; namespace e_suite.Workflow.Core.Tasks; [GeneralTask] -public class AdhocApprovalTask : TaskBase, IAssignees, IOutcome +public class AdhocApprovalTask : TaskBase, IAssignees, IOutcome { - public IList Assignees { get; } = new List(); + public List Assignees { get; set; } = []; public ApprovalVerdict TaskOutcome { get; set; } public Dictionary OutcomeActions { get; set; } public bool OverrideDefaultTaskProgression { get; set; } diff --git a/e-suite.Workflow.Core/Tasks/ApprovalStep.cs b/e-suite.Workflow.Core/Tasks/ApprovalStep.cs index 9f816f0..3c52f03 100644 --- a/e-suite.Workflow.Core/Tasks/ApprovalStep.cs +++ b/e-suite.Workflow.Core/Tasks/ApprovalStep.cs @@ -4,8 +4,8 @@ using e_suite.Workflow.Core.Interfaces; namespace e_suite.Workflow.Core.Tasks; [ApprovalTask] -public class ApprovalStep : TaskBase, IAssignees +public class ApprovalStep : TaskBase, IAssignees { - public IList Assignees { get; } = []; + public List Assignees { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/BasicTask.cs b/e-suite.Workflow.Core/Tasks/BasicTask.cs index 6ff17c4..04748b6 100644 --- a/e-suite.Workflow.Core/Tasks/BasicTask.cs +++ b/e-suite.Workflow.Core/Tasks/BasicTask.cs @@ -8,7 +8,7 @@ namespace e_suite.Workflow.Core.Tasks; /// A user has to open this task, manually set it to ready to complete /// [GeneralTask] -public class BasicTask : TaskBase, IAssignees +public class BasicTask : TaskBase, IAssignees { - public IList Assignees { get; } + public List Assignees { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/ContentCollationTask.cs b/e-suite.Workflow.Core/Tasks/ContentCollationTask.cs index dc07b9e..eb5722f 100644 --- a/e-suite.Workflow.Core/Tasks/ContentCollationTask.cs +++ b/e-suite.Workflow.Core/Tasks/ContentCollationTask.cs @@ -7,7 +7,7 @@ namespace e_suite.Workflow.Core.Tasks; /// Create a table of field data for output to PDF or AdobeIllustrator /// [GeneralTask] -public class ContentCollationTask : TaskBase, IAssignees +public class ContentCollationTask : TaskBase, IAssignees { - public IList Assignees { get; } = []; + public List Assignees { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/FormDataInputTask.cs b/e-suite.Workflow.Core/Tasks/FormDataInputTask.cs index a2fc7f2..c5a9586 100644 --- a/e-suite.Workflow.Core/Tasks/FormDataInputTask.cs +++ b/e-suite.Workflow.Core/Tasks/FormDataInputTask.cs @@ -5,9 +5,9 @@ using eSuite.Core.Miscellaneous; namespace e_suite.Workflow.Core.Tasks; [GeneralTask] -public class FormDataInputTask : TaskBase, IAssignees, IFormTemplate +public class FormDataInputTask : TaskBase, IAssignees, IFormTemplate { - public IList Assignees { get; } = new List(); + public List Assignees { get; set; } = []; public bool IsMultiple { get; set; } public required IGeneralIdRef FormIdRef { get; set; } diff --git a/eSuite.sln.DotSettings.user b/eSuite.sln.DotSettings.user index 028352f..76c8d52 100644 --- a/eSuite.sln.DotSettings.user +++ b/eSuite.sln.DotSettings.user @@ -9,10 +9,10 @@ </TestAncestor> </SessionState> True - (Doc Ln 140 Col 8) - 4A704FA7-4E3A-4CFA-B043-434A0C49AF89/d:Translation/f:TranslatorFactory.cs + (Doc Ln 85 Col 88) + 92811343-6BCB-D7E5-63D9-2F6A56AE182C/d:Extensions/f:TaskExtensions.cs NumberedBookmarkManager True - (Doc Ln 606 Col 8) - A00B7AED-96DF-49A5-BA8F-9BE74021F3CF/f:UserManager.cs + (Doc Ln 81 Col 0) + 92811343-6BCB-D7E5-63D9-2F6A56AE182C/d:Extensions/f:TaskExtensions.cs NumberedBookmarkManager \ No newline at end of file