Now able to post a workflow template version

This commit is contained in:
Colin Dawson 2026-02-26 20:30:29 +00:00
parent e7eaed742e
commit 1d8004ccaa
23 changed files with 244 additions and 76 deletions

View File

@ -82,16 +82,14 @@ public class PatchWorkflowTemplateVersion
public class CreateWorkflowTemplateVersion
{
public List<TaskDefinition> 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

View File

@ -9,7 +9,8 @@ namespace e_suite.API.Common.repository;
public interface IRoleManagerRepository : IRepository
{
IQueryable<Role> GetRolesList();
Task<Role?> GetRoleById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken);
Role? GetRoleById(IGeneralIdRef generalIdRef);
Task<Role?> GetRoleByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken);
Task<Role?> GetRoleByName(Domain domain, string sequenceName, CancellationToken cancellationToken);
Task EditRole(AuditUserDetails auditUserDetails, Role existingSequence, CancellationToken cancellationToken);
Task AddRole(AuditUserDetails auditUserDetails, Role existingSequence, CancellationToken cancellationToken);

View File

@ -21,7 +21,8 @@ public interface IUserManagerRepository : IRepository
Task<EmailUserAction?> GetCurrentEmailUserAction(long userId, EmailUserActionType emailUserActionType, CancellationToken cancellationToken);
Task DeleteEmailUserAction(AuditUserDetails auditUserDetails, EmailUserAction emailUserAction, CancellationToken cancellationToken);
IQueryable<User> GetUsers();
Task<User?> GetUserById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken);
User? GetUserById(IGeneralIdRef generalIdRef);
Task<User?> GetUserByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken);
Task<SsoProvider?> GetSsoProviderById(long id, CancellationToken cancellationToken);
IQueryable<SsoProvider> GetSsoProviders();
Task SaveSingleUseGuidForUser(SingleUseGuid singleUseGuid, CancellationToken cancellationToken);

View File

@ -9,4 +9,6 @@ public interface IWorkflowTemplateRepository : IRepository
public IQueryable<Workflow> GetWorkflows();
IQueryable<WorkflowVersion> 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);
}

View File

@ -27,7 +27,12 @@ public class FakeRoleManagerRepository : FakeRepository, IRoleManagerRepository
return Roles.BuildMock();
}
public Task<Role?> GetRoleById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
public Role? GetRoleById(IGeneralIdRef generalIdRef)
{
throw new NotImplementedException();
}
public Task<Role?> GetRoleByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
{
return Task.FromResult(GetRolesList().FindByGeneralIdRef(generalIdRef));
}

View File

@ -75,7 +75,12 @@ public class FakeUserManagerRepository : FakeRepository, IUserManagerRepository
return Users.BuildMock();
}
public Task<User?> GetUserById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
public User? GetUserById(IGeneralIdRef generalIdRef)
{
throw new NotImplementedException();
}
public Task<User?> GetUserByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
{
return Task.FromResult(Users.SingleOrDefault(x => x.Id == generalIdRef.Id || x.Guid == generalIdRef.Guid));
}

View File

@ -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<Role?> GetRoleById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
public async Task<Role?> GetRoleByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
{
return await DatabaseDbContext.Roles
.Include(nameof(Domain))

View File

@ -86,7 +86,7 @@ public class RoleManager : IRoleManager
public async Task<ReadRole> 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<IPaginatedData<RoleUser>> 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);

View File

@ -145,7 +145,12 @@ public class FakeUserManagerRepository : FakeRepository, IUserManagerRepository
return Users.BuildMock();
}
public Task<User?> GetUserById(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
public User? GetUserById(IGeneralIdRef generalIdRef)
{
throw new NotImplementedException();
}
public Task<User?> GetUserByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
{
return Task.FromResult(Users.SingleOrDefault(x => x.Id == generalIdRef.Id || x.Guid == generalIdRef.Guid ));
}

View File

@ -47,7 +47,19 @@ public class UserManagerRepository : RepositoryBase, IUserManagerRepository
return users.FirstOrDefault();
}
public async Task<User?> 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<User?> GetUserByIdAsync(IGeneralIdRef generalIdRef, CancellationToken cancellationToken)
{
var user = await GetUsers().FindByGeneralIdRefAsync(generalIdRef, cancellationToken);
if (user != null)

View File

@ -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<LoginResponse> 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<GetUser?> 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

View File

@ -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<T> : JsonConverter<T>
{
private readonly Func<GeneralIdRef, T?> _lookup;
public GeneralIdRefConverter(Func<GeneralIdRef, T?> 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<GeneralIdRef>(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<e_suite.Database.Core.Tables.Workflow.WorkflowVersion> 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<Role>(id => _roleManagerRepository.GetRoleById(id)));
_jsonSerializerOptions.Converters.Add(new GeneralIdRefConverter<User>(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<e_suite.Database.Core.Tables.Workflow.WorkflowVersion> SerialiseToDatabase(WorkflowVersion runtime, e_suite.Database.Core.Tables.Workflow.WorkflowVersion? dbObject = null, CancellationToken cancellationToken = default)
public async Task<e_suite.Database.Core.Tables.Workflow.WorkflowVersion> 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);
}
}

View File

@ -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<PaginatedData<GetWorkflowTemplate>> 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)

View File

@ -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<string, object?> dict)
public static void FromConfigDictionary(this object obj, Dictionary<string, object?> 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);

View File

@ -7,6 +7,6 @@ namespace e_suite.Workflow.Core.Interfaces;
public interface IAssignees<T> where T : ITaskAssignee
{
[Required]
IList<T> Assignees { get; }
List<T> Assignees { get; set; }
}

View File

@ -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; }
}

View File

@ -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<ValidationResult> 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)]
);
}
}

View File

@ -5,9 +5,9 @@ using e_suite.Workflow.Core.Interfaces;
namespace e_suite.Workflow.Core.Tasks;
[GeneralTask]
public class AdhocApprovalTask : TaskBase, IAssignees<IApprovalTaskAssignee>, IOutcome<ApprovalVerdict>
public class AdhocApprovalTask : TaskBase, IAssignees<ApprovalTaskAssignee>, IOutcome<ApprovalVerdict>
{
public IList<IApprovalTaskAssignee> Assignees { get; } = new List<IApprovalTaskAssignee>();
public List<ApprovalTaskAssignee> Assignees { get; set; } = [];
public ApprovalVerdict TaskOutcome { get; set; }
public Dictionary<ApprovalVerdict, Guid> OutcomeActions { get; set; }
public bool OverrideDefaultTaskProgression { get; set; }

View File

@ -4,8 +4,8 @@ using e_suite.Workflow.Core.Interfaces;
namespace e_suite.Workflow.Core.Tasks;
[ApprovalTask]
public class ApprovalStep : TaskBase, IAssignees<IApprovalTaskAssignee>
public class ApprovalStep : TaskBase, IAssignees<ApprovalTaskAssignee>
{
public IList<IApprovalTaskAssignee> Assignees { get; } = [];
public List<ApprovalTaskAssignee> Assignees { get; set; } = [];
}

View File

@ -8,7 +8,7 @@ namespace e_suite.Workflow.Core.Tasks;
/// A user has to open this task, manually set it to ready to complete
/// </summary>
[GeneralTask]
public class BasicTask : TaskBase, IAssignees<ITaskAssignee>
public class BasicTask : TaskBase, IAssignees<TaskAssignee>
{
public IList<ITaskAssignee> Assignees { get; }
public List<TaskAssignee> Assignees { get; set; } = [];
}

View File

@ -7,7 +7,7 @@ namespace e_suite.Workflow.Core.Tasks;
/// Create a table of field data for output to PDF or AdobeIllustrator
/// </summary>
[GeneralTask]
public class ContentCollationTask : TaskBase, IAssignees<ITaskAssignee>
public class ContentCollationTask : TaskBase, IAssignees<TaskAssignee>
{
public IList<ITaskAssignee> Assignees { get; } = [];
public List<TaskAssignee> Assignees { get; set; } = [];
}

View File

@ -5,9 +5,9 @@ using eSuite.Core.Miscellaneous;
namespace e_suite.Workflow.Core.Tasks;
[GeneralTask]
public class FormDataInputTask : TaskBase, IAssignees<ITaskAssignee>, IFormTemplate
public class FormDataInputTask : TaskBase, IAssignees<TaskAssignee>, IFormTemplate
{
public IList<ITaskAssignee> Assignees { get; } = new List<ITaskAssignee>();
public List<TaskAssignee> Assignees { get; set; } = [];
public bool IsMultiple { get; set; }
public required IGeneralIdRef FormIdRef { get; set; }

View File

@ -9,10 +9,10 @@
&lt;/TestAncestor&gt;&#xD;
&lt;/SessionState&gt;</s:String>
<s:Boolean x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark1/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark1/Coords/@EntryValue">(Doc Ln 140 Col 8)</s:String>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark1/FileId/@EntryValue">4A704FA7-4E3A-4CFA-B043-434A0C49AF89/d:Translation/f:TranslatorFactory.cs</s:String>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark1/Coords/@EntryValue">(Doc Ln 85 Col 88)</s:String>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark1/FileId/@EntryValue">92811343-6BCB-D7E5-63D9-2F6A56AE182C/d:Extensions/f:TaskExtensions.cs</s:String>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark1/Owner/@EntryValue">NumberedBookmarkManager</s:String>
<s:Boolean x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark2/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark2/Coords/@EntryValue">(Doc Ln 606 Col 8)</s:String>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark2/FileId/@EntryValue">A00B7AED-96DF-49A5-BA8F-9BE74021F3CF/f:UserManager.cs</s:String>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark2/Coords/@EntryValue">(Doc Ln 81 Col 0)</s:String>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark2/FileId/@EntryValue">92811343-6BCB-D7E5-63D9-2F6A56AE182C/d:Extensions/f:TaskExtensions.cs</s:String>
<s:String x:Key="/Default/Housekeeping/Bookmarks/NumberedBookmarks/=Bookmark2/Owner/@EntryValue">NumberedBookmarkManager</s:String></wpf:ResourceDictionary>