Files
jube-fork/Jube.Engine/BackgroundTasks/TaskStarters/Case/CaseProcessing.cs
richard-churchman 811c938462 Improved Case Automation POST body conext and other minor issues:
* Pushed Case Workflow Display HTML rendering logic down into a controller for improved security and consistent tokenisation of the Jube token format against context. Included a migration to update the default demo entry to use proper tokenisation. Updated the repository to support fetching CaseWorkflowDisplay with appropriate security.
* Implemented improved tokenisation of the EntityAnalysisModelInstanceEntryPayload model via a common extension method on EntityAnalysisModelInstanceEntryPayload, used consistently across the system for serialisation and storage.
* Included a migration to update the tokenisation format for full CaseWorkflowDisplay support.
* Fixed an issue where CaseWorkflowFilter was incorrectly filtering on CaseWorkflowStatus.Guid instead of CaseWorkflowStatus.Name, causing the GUID to be returned rather than the display name on the template/demo model. (The remainder was fixed in GitHub issue #6.) Included a migration with a corrective SQL statement.
* Fixed an issue where tokens were not being stored alongside CaseWorkflowFilter definition configuration. It now behaves identically to CaseSearch. Note: migrating missing tokens is not feasible as it requires JS-side parsing via JQueryBuilder.
* Reduced the size of the CaseWorkflowForm invocation to include only the required fields given the new POST body context being rendered server side. Improved API serialisation by using a dictionary for form payload keys and values.
* Consolidated JSON serialisation settings into DI and updated usages throughout.
* Updated EntityAnalysisModelInstanceEntryPayload write serialisers to handle deserialisation, which is required to support tokenisation of new Case Automation POST bodies.
* Created an extension method on the EntityAnalysisModelInstanceEntryPayload to centralise tokenisation of strings across Activation and Case Automation on a consistent basis. Simplified Case Notification and Case HTTP dispatch by removing tokenisation logic from those classes.
* Removed a redundant field from the CaseWorkflowFormEntry POCO (database left unchanged for now).
* Fixed a bug in Case session SQL rebuilding where the rebuild date was not being correctly checked for null or default value, which caused a failure in the last migration.
* Made minor modifications to the Create Case process and method signature to skip serialisation when the payload object required for tokenisation is already available on the thread (e.g. during synchronous reprocessing).
* Updated Case Workflow Macro processing to fetch the archive payload for the case from the database, improving security of that invocation endpoint.
* Removed what appeared to be a duplicate EntityAnalysisModelInstanceEntryPayload model introduced during refactoring.
* Fixed a but in Case Worfkflow Display Role assigment owing to volatile Guid in its update.
2026-02-22 13:08:42 +02:00

202 lines
9.0 KiB
C#

namespace Jube.Engine.BackgroundTasks.TaskStarters.Case
{
using System;
using System.Threading;
using System.Threading.Tasks;
using Data.Context;
using Data.Poco;
using Data.Query;
using Data.Repository;
using DynamicEnvironment;
using EntityAnalysisModelInvoke.Models.CaseManagement;
using EntityAnalysisModelInvoke.Models.Payload.EntityAnalysisModelInstanceEntryPayload;
using EntityAnalysisModelInvoke.Models.Payload.EntityAnalysisModelInstanceEntryPayload.Extensions;
using Helpers;
using Jube.Case;
using log4net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
public static class CaseProcessing
{
public static async Task CreateAsync(DynamicEnvironment dynamicEnvironment,
CreateCase createCase,
ILog log,
JsonSerializationHelper jsonSerializationHelper,
EntityAnalysisModelInstanceEntryPayload payload = null,
CancellationToken token = default)
{
var dbContext =
DataConnectionDbContext.GetDbContextDataConnection(dynamicEnvironment.AppSettings("ConnectionString"));
try
{
if (log.IsInfoEnabled)
{
log.Info(
$"Case Creation: has received a case creation message with case entry GUID of {createCase.EntityAnalysisModelInstanceEntryGuid}.");
}
var repositoryCase = new CaseRepository(dbContext);
var query = new GetExistingCasePriorityQuery(dbContext);
if (log.IsInfoEnabled)
{
log.Info(
$"Case Creation: connection to the database established for case entry GUID of {createCase.EntityAnalysisModelInstanceEntryGuid}.");
}
var model = new Case
{
EntityAnalysisModelInstanceEntryGuid = createCase.EntityAnalysisModelInstanceEntryGuid,
CaseWorkflowGuid = createCase.CaseWorkflowGuid,
CaseWorkflowStatusGuid = createCase.CaseWorkflowStatusGuid,
CaseKey = createCase.CaseKey,
CaseKeyValue = createCase.CaseKeyValue,
Locked = 0,
Rating = 0,
CreatedDate = DateTime.Now
};
if (createCase.SuspendBypass)
{
model.Diary = 1;
model.DiaryDate = createCase.SuspendBypassDate;
model.ClosedStatusId = 4;
}
else
{
model.Diary = 0;
model.DiaryDate = createCase.SuspendBypassDate;
model.ClosedStatusId = 0;
model.DiaryDate = DateTime.Now;
}
model.Json = createCase.Json;
if (log.IsInfoEnabled)
{
log.Info(
$"Case Creation: Have created a case creation SQL command with Case Entry GUID of {createCase.EntityAnalysisModelInstanceEntryGuid}, " +
$"Case Workflow ID of {createCase.CaseWorkflowGuid}, " +
$"Case Workflow Status ID of {createCase.CaseWorkflowStatusGuid}, Case Key of {createCase.CaseKeyValue}, " +
$"Case XML Bytes {createCase.Json.Length}");
}
var existing = await query.ExecuteAsync(model.CaseWorkflowGuid, model.CaseKey, model.CaseKeyValue, token).ConfigureAwait(false);
var repositoryCasesWorkflowsStatus = new CaseWorkflowStatusRepository(dbContext, createCase.TenantRegistryId);
CaseWorkflowStatus finalCasesWorkflowsStatus = null;
if (existing == null)
{
finalCasesWorkflowsStatus =
await repositoryCasesWorkflowsStatus.GetByGuidAsync(model.CaseWorkflowStatusGuid, token).ConfigureAwait(false);
await repositoryCase.InsertAsync(model, token).ConfigureAwait(false);
}
else
{
var existingCasesWorkflowsStatus =
await repositoryCasesWorkflowsStatus.GetByGuidAsync(model.CaseWorkflowStatusGuid, token).ConfigureAwait(false);
if (existingCasesWorkflowsStatus.Priority < existing.Priority)
{
finalCasesWorkflowsStatus =
await repositoryCasesWorkflowsStatus.GetByGuidAsync(model.CaseWorkflowStatusGuid, token).ConfigureAwait(false);
model.Id = existing.CaseId;
model.Locked = 0;
model.CaseWorkflowStatusGuid = createCase.CaseWorkflowStatusGuid;
await repositoryCase.UpdateCaseAsync(model, token).ConfigureAwait(false);
}
}
var caseBytes = 0;
if (createCase.Json != null)
{
if (finalCasesWorkflowsStatus != null)
{
caseBytes = createCase.Json.Length;
if (finalCasesWorkflowsStatus.EnableNotification == 1 ||
finalCasesWorkflowsStatus.EnableHttpEndpoint == 1)
{
var context = payload ?? JsonConvert.DeserializeObject<EntityAnalysisModelInstanceEntryPayload>(createCase.Json, jsonSerializationHelper.DefaultJsonSerializerSettingsSettings);
if (finalCasesWorkflowsStatus.EnableNotification == 1)
{
var notificationSubject = context.ReplaceTokens(finalCasesWorkflowsStatus.NotificationSubject);
var notificationDestination = context.ReplaceTokens(finalCasesWorkflowsStatus.NotificationDestination);
var notificationBody = context.ReplaceTokens(finalCasesWorkflowsStatus.NotificationBody);
var notification = new Notification(log, dynamicEnvironment);
await notification.SendAsync(finalCasesWorkflowsStatus.NotificationTypeId ?? 1,
notificationDestination,
notificationSubject,
notificationBody, token);
}
if (finalCasesWorkflowsStatus.EnableHttpEndpoint == 1)
{
var endpoint = context.ReplaceTokens(finalCasesWorkflowsStatus.HttpEndpoint);
if (finalCasesWorkflowsStatus.HttpEndpointTypeId == 1)
{
await SendHttpEndpoint.PostAsync(endpoint, PreparePostBodyString(model, jsonSerializationHelper.ArchiveJsonSerializer, finalCasesWorkflowsStatus), log);
}
else
{
await SendHttpEndpoint.GetAsync(endpoint, log);
}
}
}
}
}
if (log.IsInfoEnabled)
{
log.Info(
$"Case Creation: Executed Case Entry GUID of {createCase.EntityAnalysisModelInstanceEntryGuid}, " +
$"Case Workflow ID of {createCase.EntityAnalysisModelInstanceEntryGuid}, " +
$"Case Workflow Status ID of {createCase.CaseWorkflowStatusGuid}, " +
$"Case Key of {createCase.CaseKeyValue}, " +
$"Case JSON Bytes {caseBytes}");
}
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
log.Error($"Case Creation: error processing payload as {ex}.");
}
finally
{
await dbContext.CloseAsync(token).ConfigureAwait(false);
await dbContext.DisposeAsync(token).ConfigureAwait(false);
if (log.IsInfoEnabled)
{
log.Info("Case Creation: closed the database connection.");
}
}
}
private static string PreparePostBodyString(Case createCase, JsonSerializer jsonSerializer, CaseWorkflowStatus finalCasesWorkflowsStatus, EntityAnalysisModelInstanceEntryPayload payload = null)
{
var jObject = JObject.FromObject(createCase, jsonSerializer);
if (payload == null)
{
jObject["context"] = JObject.Parse(createCase.Json);
}
else
{
jObject["context"] = JObject.FromObject(payload);
}
jObject["caseWorkflowStatus"] = finalCasesWorkflowsStatus.Name;
jObject.Remove("json");
return jObject.ToString();
}
}
}