From 44ada3f332af5375acd7d26b202aaf7c75d7323d Mon Sep 17 00:00:00 2001 From: Colin Dawson Date: Thu, 12 Mar 2026 18:23:10 +0000 Subject: [PATCH] Refactored the IOutcomes to be completely data driven on the UI now. --- .../IWorkflowTemplateManager.cs | 3 + .../Controllers/WorkflowTemplateController.cs | 2 +- .../WorkflowTemplateManager.cs | 14 ++++- .../WorkflowProcessor.cs | 13 +++- .../Enums/ApprovalVerdict.cs | 5 +- e-suite.Workflow.Core/Enums/DefaultOutcome.cs | 5 +- .../Extensions/TaskExtensions.cs | 59 ++++++++++--------- e-suite.Workflow.Core/Interfaces/IOutcome.cs | 13 +++- .../Tasks/AdhocApprovalTask.cs | 3 +- e-suite.Workflow.Core/Tasks/ApprovalStep.cs | 3 +- e-suite.Workflow.Core/Tasks/ApprovalTask.cs | 4 +- .../Tasks/AssetUploadTask.cs | 2 +- e-suite.Workflow.Core/Tasks/BasicTask.cs | 3 +- .../Tasks/ContentCollationTask.cs | 3 +- .../Tasks/FileReleaseTask.cs | 2 +- .../Tasks/FormDataInputTask.cs | 3 +- .../Tasks/LinkActivityTask.cs | 2 +- e-suite.Workflow.Core/Tasks/MilestoneTask.cs | 4 +- e-suite.Workflow.Core/Tasks/StageTask.cs | 3 +- .../Tasks/VisualBriefReviewTask.cs | 2 +- .../Tasks/VisualBriefUploadTask.cs | 2 +- 21 files changed, 99 insertions(+), 51 deletions(-) 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 99f05bb..cb395c1 100644 --- a/e-suite.API.Common/e-suite.API.Common/IWorkflowTemplateManager.cs +++ b/e-suite.API.Common/e-suite.API.Common/IWorkflowTemplateManager.cs @@ -81,6 +81,9 @@ public class TaskMetadata public string TaskType { get; set; } public string DisplayName { get; set; } public List Capabilities { get; set; } = []; + + public string OutcomeLabel { get; set; } + public List Outcomes { get; set; } = []; } public interface IWorkflowTemplateManager diff --git a/e-suite.API/eSuite.API/Controllers/WorkflowTemplateController.cs b/e-suite.API/eSuite.API/Controllers/WorkflowTemplateController.cs index fe6b62c..3cc583d 100644 --- a/e-suite.API/eSuite.API/Controllers/WorkflowTemplateController.cs +++ b/e-suite.API/eSuite.API/Controllers/WorkflowTemplateController.cs @@ -143,7 +143,7 @@ public class WorkflowTemplateController : ESuiteControllerBase [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))] [AccessKey(SecurityAccess.DeleteWorkflowTemplate)] public async Task DeleteWorkflowTemplateVersion( - [FromBody] IGeneralIdRef templateId, + [FromBody] GeneralIdRef templateId, CancellationToken cancellationToken = default! ) { diff --git a/e-suite.Modules.WorkflowTemplatesManager/WorkflowTemplateManager.cs b/e-suite.Modules.WorkflowTemplatesManager/WorkflowTemplateManager.cs index af1895a..69a6cd1 100644 --- a/e-suite.Modules.WorkflowTemplatesManager/WorkflowTemplateManager.cs +++ b/e-suite.Modules.WorkflowTemplatesManager/WorkflowTemplateManager.cs @@ -1,4 +1,5 @@ -using e_suite.API.Common; +using System.ComponentModel; +using e_suite.API.Common; using e_suite.API.Common.exceptions; using e_suite.API.Common.repository; using e_suite.Database.Audit; @@ -251,6 +252,17 @@ public class WorkflowTemplateManager : IWorkflowTemplateManager }; newTaskMetadata.Capabilities.AddRange(capabilities); + var outcomeInterface = interfaces + .FirstOrDefault(i => i.IsGenericType && + i.GetGenericTypeDefinition() == typeof(IOutcome<>)); + var outcomeEnumType = outcomeInterface.GetGenericArguments()[0]; + + newTaskMetadata.OutcomeLabel = outcomeEnumType + .GetCustomAttribute()? + .Description ?? outcomeEnumType.Name; + + newTaskMetadata.Outcomes = Enum.GetNames(outcomeEnumType).Select( x => $"{outcomeEnumType.Name}.{x}").ToList(); + return newTaskMetadata; }) .ToList(); diff --git a/e-suite.Service.WorkflowProcessor/WorkflowProcessor.cs b/e-suite.Service.WorkflowProcessor/WorkflowProcessor.cs index f0733d5..dd4e8b1 100644 --- a/e-suite.Service.WorkflowProcessor/WorkflowProcessor.cs +++ b/e-suite.Service.WorkflowProcessor/WorkflowProcessor.cs @@ -4,7 +4,6 @@ using e_suite.Database.Core.Tables.Activity; using e_suite.Messaging.Common; using e_suite.Workflow.Core; using e_suite.Workflow.Core.Extensions; -using e_suite.Workflow.Core.Interfaces; using eSuite.Core.Clock; using eSuite.Core.Enums; using eSuite.Core.Miscellaneous; @@ -60,7 +59,17 @@ public class WorkflowProcessor : IWorkflowProcessor return; } - var workflowVersion = _workflowConverter.DeserialiseFromDatabase(activityInstance.WorkflowVersion); + WorkflowVersion workflowVersion; + try + { + workflowVersion = _workflowConverter.DeserialiseFromDatabase(activityInstance.WorkflowVersion); + } + catch (Exception e) + { + _logger.LogError("{DateTime}: Failed to Deserialise Json {messageId} - {messageId}", _clock.GetNow, activityId, e.ToString()); + throw; + } + var hasCompletableTask = false; diff --git a/e-suite.Workflow.Core/Enums/ApprovalVerdict.cs b/e-suite.Workflow.Core/Enums/ApprovalVerdict.cs index 8af3511..ee8cf61 100644 --- a/e-suite.Workflow.Core/Enums/ApprovalVerdict.cs +++ b/e-suite.Workflow.Core/Enums/ApprovalVerdict.cs @@ -1,5 +1,8 @@ -namespace e_suite.Workflow.Core.Enums; +using System.ComponentModel; +namespace e_suite.Workflow.Core.Enums; + +[Description("Verdict")] public enum ApprovalVerdict { None, diff --git a/e-suite.Workflow.Core/Enums/DefaultOutcome.cs b/e-suite.Workflow.Core/Enums/DefaultOutcome.cs index bfc0009..dec9235 100644 --- a/e-suite.Workflow.Core/Enums/DefaultOutcome.cs +++ b/e-suite.Workflow.Core/Enums/DefaultOutcome.cs @@ -1,5 +1,8 @@ -namespace e_suite.Workflow.Core.Enums; +using System.ComponentModel; +namespace e_suite.Workflow.Core.Enums; + +[Description("Outcome")] public enum DefaultOutcome { Complete diff --git a/e-suite.Workflow.Core/Extensions/TaskExtensions.cs b/e-suite.Workflow.Core/Extensions/TaskExtensions.cs index 225757f..f40fc68 100644 --- a/e-suite.Workflow.Core/Extensions/TaskExtensions.cs +++ b/e-suite.Workflow.Core/Extensions/TaskExtensions.cs @@ -83,7 +83,14 @@ public static class TaskExtensions else { // Deserialize JSON into the target type (handles lists, objects, primitives) - value = JsonSerializer.Deserialize(je.GetRawText(), targetType, jsonSerializerOptions); + try + { + value = JsonSerializer.Deserialize(je.GetRawText(), targetType, jsonSerializerOptions); + } + catch (Exception e) + { + throw; + } } } @@ -125,7 +132,7 @@ public static class TaskExtensions }; } - private (Type outcomeType, IDictionary dict)? GetOutcomeDictionary() + private (Type outcomeType, IEnumerable outcomeList)? GetOutcomeList() { var outcomeInterface = task.GetType() .GetInterfaces() @@ -137,49 +144,45 @@ public static class TaskExtensions var outcomeType = outcomeInterface.GetGenericArguments()[0]; var prop = outcomeInterface.GetProperty("OutcomeActions"); - var dict = (IDictionary)prop!.GetValue(task)!; - return (outcomeType, dict); + var list = (IEnumerable)prop!.GetValue(task)!; + + return (outcomeType, list); } public IEnumerable GetTargetGuids() { - var info = task.GetOutcomeDictionary(); + var info = task.GetOutcomeList(); if (info == null) - return Enumerable.Empty(); + yield break; - return info.Value.dict.Values.Cast(); + foreach (var item in info.Value.outcomeList.Cast()) + yield return item.Task; } public IEnumerable GetTargetGuids(IEnumerable outcomes) { - var info = task.GetOutcomeDictionary(); + var info = task.GetOutcomeList(); if (info == null) return Enumerable.Empty(); - var (outcomeType, dict) = info.Value; - var results = new List(); + var (outcomeType, list) = info.Value; - foreach (var outcomeString in outcomes) - { - object? typedKey; + var typedOutcomes = outcomes.Select(o => + outcomeType.IsEnum + ? Enum.Parse(outcomeType, o, true) + : Convert.ChangeType(o, outcomeType) + ).ToHashSet(); - if (outcomeType.IsEnum) + return list + .Cast() + .Where(item => { - typedKey = Enum.Parse(outcomeType, outcomeString); - } - else - { - typedKey = Convert.ChangeType(outcomeString, outcomeType); - } - - if (dict.Contains(typedKey)) - { - results.Add((Guid)dict[typedKey]!); - } - } - - return results; + var outcomeProp = item.GetType().GetProperty("Outcome"); + var outcomeValue = outcomeProp!.GetValue(item); + return typedOutcomes.Contains(outcomeValue); + }) + .Select(item => ((IOutcomeAction)item).Task); } public async Task StartTask(ActivityTask activityTask, IClock clock) diff --git a/e-suite.Workflow.Core/Interfaces/IOutcome.cs b/e-suite.Workflow.Core/Interfaces/IOutcome.cs index eb6c9d5..02d45b4 100644 --- a/e-suite.Workflow.Core/Interfaces/IOutcome.cs +++ b/e-suite.Workflow.Core/Interfaces/IOutcome.cs @@ -2,11 +2,22 @@ namespace e_suite.Workflow.Core.Interfaces; +public interface IOutcomeAction +{ + Guid Task { get; } +} + +public class OutcomeAction : IOutcomeAction +{ + public T Outcome { get; set; } + public Guid Task { get; set; } +} + [TaskCapability] public interface IOutcome { //Todo runtime only property. //public T? TaskOutcome { get; set; } - Dictionary OutcomeActions { get; set; } + List> OutcomeActions { get; set; } } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/AdhocApprovalTask.cs b/e-suite.Workflow.Core/Tasks/AdhocApprovalTask.cs index 9ae9999..f4744d7 100644 --- a/e-suite.Workflow.Core/Tasks/AdhocApprovalTask.cs +++ b/e-suite.Workflow.Core/Tasks/AdhocApprovalTask.cs @@ -9,6 +9,5 @@ public class AdhocApprovalTask : TaskBase, IAssignees, IOu { public List Assignees { get; set; } = []; public ApprovalVerdict TaskOutcome { get; set; } - public Dictionary OutcomeActions { get; set; } = []; - public bool OverrideDefaultTaskProgression { get; set; } + public List> OutcomeActions { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/ApprovalStep.cs b/e-suite.Workflow.Core/Tasks/ApprovalStep.cs index febadb2..af1d627 100644 --- a/e-suite.Workflow.Core/Tasks/ApprovalStep.cs +++ b/e-suite.Workflow.Core/Tasks/ApprovalStep.cs @@ -9,5 +9,6 @@ public class ApprovalStep : TaskBase, IAssignees, IOutcome { public List Assignees { get; set; } = []; - public Dictionary OutcomeActions { get; set; } = []; + + public List> OutcomeActions { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/ApprovalTask.cs b/e-suite.Workflow.Core/Tasks/ApprovalTask.cs index 4bd4ff1..6ae1cf3 100644 --- a/e-suite.Workflow.Core/Tasks/ApprovalTask.cs +++ b/e-suite.Workflow.Core/Tasks/ApprovalTask.cs @@ -9,6 +9,6 @@ public class ApprovalTask : TaskBase, IStage, IOutcome Tasks { get; } = new List(); public ApprovalVerdict TaskOutcome { get; set; } - public Dictionary OutcomeActions { get; set; } = []; - public bool OverrideDefaultTaskProgression { get; set; } + + public List> OutcomeActions { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/AssetUploadTask.cs b/e-suite.Workflow.Core/Tasks/AssetUploadTask.cs index 7925313..dd7daef 100644 --- a/e-suite.Workflow.Core/Tasks/AssetUploadTask.cs +++ b/e-suite.Workflow.Core/Tasks/AssetUploadTask.cs @@ -7,5 +7,5 @@ namespace e_suite.Workflow.Core.Tasks; [GeneralTask] public class AssetUploadTask : TaskBase, IOutcome { - public Dictionary OutcomeActions { get; set; } = []; + public List> OutcomeActions { 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 cbde244..fdaaa52 100644 --- a/e-suite.Workflow.Core/Tasks/BasicTask.cs +++ b/e-suite.Workflow.Core/Tasks/BasicTask.cs @@ -12,5 +12,6 @@ namespace e_suite.Workflow.Core.Tasks; public class BasicTask : TaskBase, IAssignees, IOutcome { public List Assignees { get; set; } = []; - public Dictionary OutcomeActions { get; set; } = []; + + public List> OutcomeActions { 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 563c983..d25c38c 100644 --- a/e-suite.Workflow.Core/Tasks/ContentCollationTask.cs +++ b/e-suite.Workflow.Core/Tasks/ContentCollationTask.cs @@ -11,5 +11,6 @@ namespace e_suite.Workflow.Core.Tasks; public class ContentCollationTask : TaskBase, IAssignees, IOutcome { public List Assignees { get; set; } = []; - public Dictionary OutcomeActions { get; set; } = []; + + public List> OutcomeActions { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/FileReleaseTask.cs b/e-suite.Workflow.Core/Tasks/FileReleaseTask.cs index 00d031b..05cf2dc 100644 --- a/e-suite.Workflow.Core/Tasks/FileReleaseTask.cs +++ b/e-suite.Workflow.Core/Tasks/FileReleaseTask.cs @@ -10,5 +10,5 @@ namespace e_suite.Workflow.Core.Tasks; [GeneralTask(allowMultiple: false)] public class FileReleaseTask : TaskBase, IOutcome { - public Dictionary OutcomeActions { get; set; } = []; + public List> OutcomeActions { 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 85b343b..813cb24 100644 --- a/e-suite.Workflow.Core/Tasks/FormDataInputTask.cs +++ b/e-suite.Workflow.Core/Tasks/FormDataInputTask.cs @@ -17,6 +17,5 @@ public class FormDataInputTask : TaskBase, IAssignees, IFormTempla if (FormIdRef == null) yield return "FormIdRef is required."; } - - public Dictionary OutcomeActions { get; set; } = []; + public List> OutcomeActions { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/LinkActivityTask.cs b/e-suite.Workflow.Core/Tasks/LinkActivityTask.cs index d71c625..c0de8eb 100644 --- a/e-suite.Workflow.Core/Tasks/LinkActivityTask.cs +++ b/e-suite.Workflow.Core/Tasks/LinkActivityTask.cs @@ -7,5 +7,5 @@ namespace e_suite.Workflow.Core.Tasks; [GeneralTask] public class LinkActivityTask : TaskBase, IOutcome { - public Dictionary OutcomeActions { get; set; } = []; + public List> OutcomeActions { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/MilestoneTask.cs b/e-suite.Workflow.Core/Tasks/MilestoneTask.cs index 6d6d074..3a89bfa 100644 --- a/e-suite.Workflow.Core/Tasks/MilestoneTask.cs +++ b/e-suite.Workflow.Core/Tasks/MilestoneTask.cs @@ -13,7 +13,7 @@ namespace e_suite.Workflow.Core.Tasks; [GeneralTask] public class MilestoneTask : TaskBase, IOutcome { - public Dictionary OutcomeActions { get; set; } = []; + public List> OutcomeActions { get; set; } = []; public override async Task OnStartedAsync(ActivityTask activityTask) { @@ -21,4 +21,6 @@ public class MilestoneTask : TaskBase, IOutcome activityTask.AddOutcome(DefaultOutcome.Complete); activityTask.SetState(ActivityState.ReadyToComplete); } + + } diff --git a/e-suite.Workflow.Core/Tasks/StageTask.cs b/e-suite.Workflow.Core/Tasks/StageTask.cs index ce7d2fa..0d41eca 100644 --- a/e-suite.Workflow.Core/Tasks/StageTask.cs +++ b/e-suite.Workflow.Core/Tasks/StageTask.cs @@ -10,5 +10,6 @@ public class StageTask : TaskBase, IStage, IBypassable, IO public ICollection Tasks { get; } = new List(); public bool Bypassable { get; set; } - public Dictionary OutcomeActions { get; set; } = []; + + public List> OutcomeActions { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/VisualBriefReviewTask.cs b/e-suite.Workflow.Core/Tasks/VisualBriefReviewTask.cs index 663a8c7..6d577f7 100644 --- a/e-suite.Workflow.Core/Tasks/VisualBriefReviewTask.cs +++ b/e-suite.Workflow.Core/Tasks/VisualBriefReviewTask.cs @@ -7,5 +7,5 @@ namespace e_suite.Workflow.Core.Tasks; [GeneralTask] public class VisualBriefReviewTask : TaskBase, IOutcome { - public Dictionary OutcomeActions { get; set; } = []; + public List> OutcomeActions { get; set; } = []; } \ No newline at end of file diff --git a/e-suite.Workflow.Core/Tasks/VisualBriefUploadTask.cs b/e-suite.Workflow.Core/Tasks/VisualBriefUploadTask.cs index 840b112..533616c 100644 --- a/e-suite.Workflow.Core/Tasks/VisualBriefUploadTask.cs +++ b/e-suite.Workflow.Core/Tasks/VisualBriefUploadTask.cs @@ -7,5 +7,5 @@ namespace e_suite.Workflow.Core.Tasks; [GeneralTask] public class VisualBriefUploadTask : TaskBase, IOutcome { - public Dictionary OutcomeActions { get; set; } = []; + public List> OutcomeActions { get; set; } = []; } \ No newline at end of file