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;
|
namespace e_suite.Database.Core.Tables.Activity;
|
||||||
|
|
||||||
[DisplayName("Activity")]
|
[DisplayName("ActivityTask")]
|
||||||
[Table("ActivityTasks", Schema = "Activity")]
|
[Table("ActivityTasks", Schema = "Activity")]
|
||||||
[Index(nameof(Guid), IsUnique = true)]
|
[Index(nameof(Guid), IsUnique = true)]
|
||||||
[Index(nameof(ActivityId), nameof(ActivityOrdinal), 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 virtual ActivityTask ParentTask { get; set; } = null!;
|
||||||
|
|
||||||
public ICollection<ActivityTask> Tasks { get; set; } = [];
|
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")
|
b.Property<DateTimeOffset>("LastUpdated")
|
||||||
.HasColumnType("datetimeoffset");
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("Outcomes")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<long?>("ParentId")
|
b.Property<long?>("ParentId")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using e_suite.API.Common.repository;
|
using e_suite.API.Common.repository;
|
||||||
using e_suite.Database.Audit;
|
using e_suite.Database.Audit;
|
||||||
using e_suite.Database.Core.Tables.Activity;
|
using e_suite.Database.Core.Tables.Activity;
|
||||||
|
using e_suite.Messaging.Common;
|
||||||
using e_suite.Workflow.Core;
|
using e_suite.Workflow.Core;
|
||||||
using e_suite.Workflow.Core.Extensions;
|
using e_suite.Workflow.Core.Extensions;
|
||||||
using eSuite.Core.Clock;
|
using eSuite.Core.Clock;
|
||||||
@ -17,14 +18,16 @@ public class WorkflowProcessor : IWorkflowProcessor
|
|||||||
private readonly IWorkflowTemplateRepository _workflowTemplateRepository;
|
private readonly IWorkflowTemplateRepository _workflowTemplateRepository;
|
||||||
private readonly IWorkflowConverter _workflowConverter;
|
private readonly IWorkflowConverter _workflowConverter;
|
||||||
private readonly IActivityRepository _activityRepository;
|
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;
|
_logger = logger;
|
||||||
_clock = clock;
|
_clock = clock;
|
||||||
_workflowTemplateRepository = workflowTemplateRepository;
|
_workflowTemplateRepository = workflowTemplateRepository;
|
||||||
_activityRepository = activityRepository;
|
_activityRepository = activityRepository;
|
||||||
_workflowConverter = workflowConverter;
|
_workflowConverter = workflowConverter;
|
||||||
|
_activityMessageSender = activityMessageSender;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ProgressActivity(
|
public async Task ProgressActivity(
|
||||||
@ -41,7 +44,7 @@ public class WorkflowProcessor : IWorkflowProcessor
|
|||||||
_logger.LogInformation("{DateTime}: Unable to find activity {messageId}", _clock.GetNow, activityId);
|
_logger.LogInformation("{DateTime}: Unable to find activity {messageId}", _clock.GetNow, activityId);
|
||||||
throw new InvalidDataException("activityInstance not found");
|
throw new InvalidDataException("activityInstance not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activityInstance.ActivityState == ActivityState.Cancelled)
|
if (activityInstance.ActivityState == ActivityState.Cancelled)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("{DateTime}: Activity {messageId} is cancelled, skipping", _clock.GetNow,
|
_logger.LogInformation("{DateTime}: Activity {messageId} is cancelled, skipping", _clock.GetNow,
|
||||||
@ -58,34 +61,55 @@ public class WorkflowProcessor : IWorkflowProcessor
|
|||||||
|
|
||||||
var workflowVersion = _workflowConverter.DeserialiseFromDatabase(activityInstance.WorkflowVersion);
|
var workflowVersion = _workflowConverter.DeserialiseFromDatabase(activityInstance.WorkflowVersion);
|
||||||
|
|
||||||
bool hasCompletedTask = false;
|
var hasCompletableTask = false;
|
||||||
|
|
||||||
await _activityRepository.TransactionAsync(async () =>
|
await _activityRepository.TransactionAsync(async () =>
|
||||||
{
|
{
|
||||||
ICollection<ActivityTask> tasks;
|
switch (activityInstance.ActivityState)
|
||||||
if (activityInstance.ActivityState == ActivityState.Pending)
|
|
||||||
{
|
{
|
||||||
activityInstance.ActivityState = ActivityState.Active;
|
case ActivityState.Pending:
|
||||||
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))
|
|
||||||
{
|
{
|
||||||
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)
|
private async Task StartInitialTasks(AuditUserDetails auditUserDetails, ICollection<ActivityTask> tasks, WorkflowVersion workflowVersion, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var startTasks = workflowVersion.GetStartTasks().ToList();
|
var startTasks = workflowVersion.GetStartTasks().ToList();
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\e-suite.API.Common\e-suite.API.Common\e-suite.API.Common.csproj" />
|
<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.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" />
|
<ProjectReference Include="..\e-suite.Workflow.Core\e-suite.Workflow.Core.csproj" />
|
||||||
</ItemGroup>
|
</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)
|
public async Task StartTask(ActivityTask activityTask, IClock clock)
|
||||||
{
|
{
|
||||||
activityTask.ActivityState = ActivityState.Active;
|
|
||||||
activityTask.StartDateTime = clock.GetNow;
|
activityTask.StartDateTime = clock.GetNow;
|
||||||
|
activityTask.SetState(ActivityState.Active);
|
||||||
|
|
||||||
await task.OnStartedAsync(activityTask);
|
await task.OnStartedAsync(activityTask);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using e_suite.Database.Core.Tables.Activity;
|
using e_suite.Database.Core.Tables.Activity;
|
||||||
using e_suite.Workflow.Core.Attributes;
|
using e_suite.Workflow.Core.Attributes;
|
||||||
using e_suite.Workflow.Core.Enums;
|
using e_suite.Workflow.Core.Enums;
|
||||||
|
using e_suite.Workflow.Core.Extensions;
|
||||||
using e_suite.Workflow.Core.Interfaces;
|
using e_suite.Workflow.Core.Interfaces;
|
||||||
using eSuite.Core.Enums;
|
using eSuite.Core.Enums;
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ public class MilestoneTask : TaskBase, IOutcome<DefaultOutcome>
|
|||||||
public override async Task OnStartedAsync(ActivityTask activityTask)
|
public override async Task OnStartedAsync(ActivityTask activityTask)
|
||||||
{
|
{
|
||||||
await base.OnStartedAsync(activityTask);
|
await base.OnStartedAsync(activityTask);
|
||||||
activityTask.ActivityState = ActivityState.ReadyToComplete;
|
activityTask.AddOutcome(DefaultOutcome.Complete);
|
||||||
//TODO need to set the outcome of completed here
|
activityTask.SetState(ActivityState.ReadyToComplete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user