Able to get a milestone task into the ready to complete phase, including storing the outcome
This commit is contained in:
parent
8afe7ac5a0
commit
e24c8e68fe
@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace e_suite.Database.Core.Tables.Activity;
|
||||
|
||||
[DisplayName("Activity")]
|
||||
[DisplayName("ActivityTask")]
|
||||
[Table("ActivityTasks", Schema = "Activity")]
|
||||
[Index(nameof(Guid), IsUnique = true)]
|
||||
[Index(nameof(ActivityId), nameof(ActivityOrdinal), IsUnique = true)]
|
||||
@ -69,4 +69,6 @@ public class ActivityTask : IGeneralId, ISoftDeletable
|
||||
public virtual ActivityTask ParentTask { get; set; } = null!;
|
||||
|
||||
public ICollection<ActivityTask> Tasks { get; set; } = [];
|
||||
|
||||
public List<string> Outcomes { get; set; } = [];
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace esuite.Database.SqlServer.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedTaskOutcomes : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Outcomes",
|
||||
schema: "Activity",
|
||||
table: "ActivityTasks",
|
||||
type: "nvarchar(max)",
|
||||
nullable: false,
|
||||
defaultValue: "[]");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Outcomes",
|
||||
schema: "Activity",
|
||||
table: "ActivityTasks");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -183,6 +183,10 @@ namespace esuite.Database.SqlServer.Migrations
|
||||
b.Property<DateTimeOffset>("LastUpdated")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.PrimitiveCollection<string>("Outcomes")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<long?>("ParentId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using e_suite.API.Common.repository;
|
||||
using e_suite.Database.Audit;
|
||||
using e_suite.Database.Core.Tables.Activity;
|
||||
using e_suite.Messaging.Common;
|
||||
using e_suite.Workflow.Core;
|
||||
using e_suite.Workflow.Core.Extensions;
|
||||
using eSuite.Core.Clock;
|
||||
@ -17,14 +18,16 @@ public class WorkflowProcessor : IWorkflowProcessor
|
||||
private readonly IWorkflowTemplateRepository _workflowTemplateRepository;
|
||||
private readonly IWorkflowConverter _workflowConverter;
|
||||
private readonly IActivityRepository _activityRepository;
|
||||
private readonly IActivityMessageSender _activityMessageSender;
|
||||
|
||||
public WorkflowProcessor(ILogger logger, IClock clock, IWorkflowTemplateRepository workflowTemplateRepository, IActivityRepository activityRepository, IWorkflowConverter workflowConverter)
|
||||
public WorkflowProcessor(ILogger logger, IClock clock, IWorkflowTemplateRepository workflowTemplateRepository, IActivityRepository activityRepository, IWorkflowConverter workflowConverter, IActivityMessageSender activityMessageSender)
|
||||
{
|
||||
_logger = logger;
|
||||
_clock = clock;
|
||||
_workflowTemplateRepository = workflowTemplateRepository;
|
||||
_activityRepository = activityRepository;
|
||||
_workflowConverter = workflowConverter;
|
||||
_activityMessageSender = activityMessageSender;
|
||||
}
|
||||
|
||||
public async Task ProgressActivity(
|
||||
@ -41,7 +44,7 @@ public class WorkflowProcessor : IWorkflowProcessor
|
||||
_logger.LogInformation("{DateTime}: Unable to find activity {messageId}", _clock.GetNow, activityId);
|
||||
throw new InvalidDataException("activityInstance not found");
|
||||
}
|
||||
|
||||
|
||||
if (activityInstance.ActivityState == ActivityState.Cancelled)
|
||||
{
|
||||
_logger.LogInformation("{DateTime}: Activity {messageId} is cancelled, skipping", _clock.GetNow,
|
||||
@ -58,34 +61,55 @@ public class WorkflowProcessor : IWorkflowProcessor
|
||||
|
||||
var workflowVersion = _workflowConverter.DeserialiseFromDatabase(activityInstance.WorkflowVersion);
|
||||
|
||||
bool hasCompletedTask = false;
|
||||
var hasCompletableTask = false;
|
||||
|
||||
await _activityRepository.TransactionAsync(async () =>
|
||||
{
|
||||
ICollection<ActivityTask> tasks;
|
||||
if (activityInstance.ActivityState == ActivityState.Pending)
|
||||
switch (activityInstance.ActivityState)
|
||||
{
|
||||
activityInstance.ActivityState = ActivityState.Active;
|
||||
await _activityRepository.UpdateActivityInstanceAsync(auditUserDetails, activityInstance,
|
||||
cancellationToken);
|
||||
|
||||
tasks = await PlanTaskExecution(auditUserDetails, activityInstance, workflowVersion, cancellationToken);
|
||||
|
||||
await StartInitialTasks(auditUserDetails, tasks, workflowVersion, cancellationToken);
|
||||
|
||||
if (tasks.Any(task => task.ActivityState == ActivityState.ReadyToComplete))
|
||||
case ActivityState.Pending:
|
||||
{
|
||||
hasCompletedTask = true;
|
||||
hasCompletableTask = await StartActivity(auditUserDetails, cancellationToken, activityInstance,
|
||||
workflowVersion);
|
||||
break;
|
||||
}
|
||||
case ActivityState.Active:
|
||||
throw new NotImplementedException("don't know how to progress a running instance.");
|
||||
case ActivityState.ReadyToComplete:
|
||||
throw new NotImplementedException("don't know how to progress a ReadyToComplete instance.");
|
||||
}
|
||||
});
|
||||
|
||||
if (hasCompletedTask)
|
||||
if (hasCompletableTask)
|
||||
{
|
||||
//send workflow progress message here.
|
||||
_activityMessageSender.ProgressActivity(activityId);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> StartActivity(
|
||||
AuditUserDetails auditUserDetails,
|
||||
CancellationToken cancellationToken,
|
||||
Activity? activityInstance,
|
||||
WorkflowVersion workflowVersion
|
||||
)
|
||||
{
|
||||
bool hasCompletableTask = false;
|
||||
activityInstance.ActivityState = ActivityState.Active;
|
||||
await _activityRepository.UpdateActivityInstanceAsync(auditUserDetails, activityInstance,
|
||||
cancellationToken);
|
||||
|
||||
var tasks = await PlanTaskExecution(auditUserDetails, activityInstance, workflowVersion, cancellationToken);
|
||||
|
||||
await StartInitialTasks(auditUserDetails, tasks, workflowVersion, cancellationToken);
|
||||
|
||||
if (tasks.Any(task => task.ActivityState == ActivityState.ReadyToComplete))
|
||||
{
|
||||
hasCompletableTask = true;
|
||||
}
|
||||
|
||||
return hasCompletableTask;
|
||||
}
|
||||
|
||||
private async Task StartInitialTasks(AuditUserDetails auditUserDetails, ICollection<ActivityTask> tasks, WorkflowVersion workflowVersion, CancellationToken cancellationToken)
|
||||
{
|
||||
var startTasks = workflowVersion.GetStartTasks().ToList();
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\e-suite.API.Common\e-suite.API.Common\e-suite.API.Common.csproj" />
|
||||
<ProjectReference Include="..\e-suite.DependencyInjection\e-suite.DependencyInjection.csproj" />
|
||||
<ProjectReference Include="..\e-suite.Messaging.Common\e-suite.Messaging.Common\e-suite.Messaging.Common.csproj" />
|
||||
<ProjectReference Include="..\e-suite.Workflow.Core\e-suite.Workflow.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
using e_suite.Database.Core.Tables.Activity;
|
||||
using eSuite.Core.Enums;
|
||||
|
||||
namespace e_suite.Workflow.Core.Extensions;
|
||||
|
||||
public static class ActivityTaskOutcomeExtensions
|
||||
{
|
||||
extension(ActivityTask task)
|
||||
{
|
||||
public void SetState(ActivityState newState)
|
||||
{
|
||||
if (newState == ActivityState.Active)
|
||||
{
|
||||
if (task.StartDateTime == null)
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot set state to Active because StartDateTime is not set for task {task.Id}."
|
||||
);
|
||||
}
|
||||
|
||||
if (newState == ActivityState.ReadyToComplete)
|
||||
{
|
||||
if (task.Outcomes == null || task.Outcomes.Count == 0)
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot set state to ReadyToComplete because no outcomes have been recorded for task {task.Id}."
|
||||
);
|
||||
}
|
||||
|
||||
if (newState == ActivityState.Completed)
|
||||
{
|
||||
if (task.FinishDateTime == null)
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot set state to Completed because FinishDateTime is not set for task {task.Id}."
|
||||
);
|
||||
}
|
||||
|
||||
task.ActivityState = newState;
|
||||
}
|
||||
|
||||
public void AddOutcome<T>(T outcome)
|
||||
{
|
||||
if (outcome == null) throw new ArgumentNullException(nameof(outcome));
|
||||
|
||||
// Explicit, predictable conversion
|
||||
var newOutcome = outcome.ToString()!.Trim();
|
||||
|
||||
if (!task.Outcomes.Contains(newOutcome))
|
||||
task.Outcomes.Add(newOutcome);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,8 +143,8 @@ public static class TaskExtensions
|
||||
|
||||
public async Task StartTask(ActivityTask activityTask, IClock clock)
|
||||
{
|
||||
activityTask.ActivityState = ActivityState.Active;
|
||||
activityTask.StartDateTime = clock.GetNow;
|
||||
activityTask.SetState(ActivityState.Active);
|
||||
|
||||
await task.OnStartedAsync(activityTask);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using e_suite.Database.Core.Tables.Activity;
|
||||
using e_suite.Workflow.Core.Attributes;
|
||||
using e_suite.Workflow.Core.Enums;
|
||||
using e_suite.Workflow.Core.Extensions;
|
||||
using e_suite.Workflow.Core.Interfaces;
|
||||
using eSuite.Core.Enums;
|
||||
|
||||
@ -17,7 +18,7 @@ public class MilestoneTask : TaskBase, IOutcome<DefaultOutcome>
|
||||
public override async Task OnStartedAsync(ActivityTask activityTask)
|
||||
{
|
||||
await base.OnStartedAsync(activityTask);
|
||||
activityTask.ActivityState = ActivityState.ReadyToComplete;
|
||||
//TODO need to set the outcome of completed here
|
||||
activityTask.AddOutcome(DefaultOutcome.Complete);
|
||||
activityTask.SetState(ActivityState.ReadyToComplete);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user