feat(infrastructure): add DB options and workflow close outcome evaluation
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using DigitalData.TaskFlow.Application.Common.Interfaces;
|
||||
using DigitalData.TaskFlow.Infrastructure.Options;
|
||||
using DigitalData.TaskFlow.Infrastructure.Persistence;
|
||||
using DigitalData.TaskFlow.Infrastructure.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -12,6 +13,7 @@ public static class DependencyInjection
|
||||
public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var connectionString = configuration.GetConnectionString("TaskFlowDb");
|
||||
var databaseOptions = configuration.GetSection(DatabaseOptions.SectionName).Get<DatabaseOptions>() ?? new DatabaseOptions();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
{
|
||||
@@ -20,9 +22,14 @@ public static class DependencyInjection
|
||||
|
||||
services.AddDbContext<TaskFlowDbContext>(options =>
|
||||
{
|
||||
options.UseSqlServer(connectionString);
|
||||
options.UseSqlServer(connectionString, sqlOptions =>
|
||||
{
|
||||
sqlOptions.CommandTimeout(databaseOptions.CommandTimeoutSeconds);
|
||||
});
|
||||
});
|
||||
|
||||
services.Configure<DatabaseOptions>(configuration.GetSection(DatabaseOptions.SectionName));
|
||||
|
||||
services.AddScoped<IWorkflowRepository, WorkflowRepository>();
|
||||
services.AddScoped<IDocumentRepository, DocumentRepository>();
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace DigitalData.TaskFlow.Infrastructure.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Holds infrastructure database options.
|
||||
/// </summary>
|
||||
public sealed class DatabaseOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration section name.
|
||||
/// </summary>
|
||||
public const string SectionName = "Database";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets command timeout in seconds for SQL operations.
|
||||
/// </summary>
|
||||
public int CommandTimeoutSeconds { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets object type passed to workflow listing procedure.
|
||||
/// </summary>
|
||||
public string WorkflowListingObjectType { get; set; } = "Ferdinand";
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using DigitalData.TaskFlow.Domain.Documents;
|
||||
namespace DigitalData.TaskFlow.Infrastructure.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Temporary document repository implementation used as Infrastructure phase baseline.
|
||||
/// Document repository implementation for binary document retrieval.
|
||||
/// </summary>
|
||||
public sealed class DocumentRepository : IDocumentRepository
|
||||
{
|
||||
@@ -13,10 +13,13 @@ public sealed class DocumentRepository : IDocumentRepository
|
||||
/// </summary>
|
||||
/// <param name="id">Document identifier.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Binary document payload.</returns>
|
||||
public Task<DocumentBinaryResult> GetAsync(string id, CancellationToken cancellationToken)
|
||||
/// <returns>Binary document payload when found; otherwise <see langword="null"/>.</returns>
|
||||
public Task<DocumentBinaryResult?> GetAsync(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
_ = id;
|
||||
_ = cancellationToken;
|
||||
|
||||
throw new NotSupportedException(
|
||||
"Document retrieval source (DB/DMS) is not finalized yet. Configure final integration before enabling GetAsync.");
|
||||
"Document retrieval procedure/DMS contract is not finalized yet. Configure final integration before enabling GetAsync.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
using DigitalData.TaskFlow.Application.Common.Interfaces;
|
||||
using DigitalData.TaskFlow.Domain.Closing;
|
||||
using DigitalData.TaskFlow.Domain.Workflows;
|
||||
using DigitalData.TaskFlow.Infrastructure.Options;
|
||||
using DigitalData.TaskFlow.Infrastructure.Persistence;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace DigitalData.TaskFlow.Infrastructure.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Workflow repository implementation for reading workflow listing data and applying close operations.
|
||||
/// </summary>
|
||||
public sealed class WorkflowRepository(TaskFlowDbContext dbContext) : IWorkflowRepository
|
||||
public sealed class WorkflowRepository(TaskFlowDbContext dbContext, IOptions<DatabaseOptions> databaseOptions) : IWorkflowRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves grouped workflow data for a user.
|
||||
@@ -20,7 +23,9 @@ public sealed class WorkflowRepository(TaskFlowDbContext dbContext) : IWorkflowR
|
||||
public async Task<WorkflowSourcesResponse> GetAsync(int wisagId, CancellationToken cancellationToken)
|
||||
{
|
||||
const string resultType = "table";
|
||||
const string objectType = "Ferdinand";
|
||||
var objectType = string.IsNullOrWhiteSpace(databaseOptions.Value.WorkflowListingObjectType)
|
||||
? "Ferdinand"
|
||||
: databaseOptions.Value.WorkflowListingObjectType;
|
||||
|
||||
var rows = await dbContext
|
||||
.WorkflowListingReadModels
|
||||
@@ -28,6 +33,19 @@ public sealed class WorkflowRepository(TaskFlowDbContext dbContext) : IWorkflowR
|
||||
.AsNoTracking()
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (rows.Count == 0)
|
||||
{
|
||||
var metaCode = await TryGetWorkflowMetaCodeAsync(wisagId, objectType, cancellationToken);
|
||||
|
||||
if (metaCode == 2)
|
||||
{
|
||||
return new WorkflowSourcesResponse
|
||||
{
|
||||
Sources = []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
var listing = rows
|
||||
.Select(row => new WorkflowListingItem
|
||||
{
|
||||
@@ -94,7 +112,119 @@ public sealed class WorkflowRepository(TaskFlowDbContext dbContext) : IWorkflowR
|
||||
CloseWorkflowDecision decision,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
"Closing write procedure is not finalized yet. Configure final DB procedure mapping before enabling CloseAsync.");
|
||||
_ = decision;
|
||||
|
||||
if (!string.Equals(source, "Ferdinand", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return Task.FromResult(new CloseWorkflowResult
|
||||
{
|
||||
ProcessId = processId,
|
||||
IsClosed = false,
|
||||
Outcome = CloseWorkflowOutcome.NotFound
|
||||
});
|
||||
}
|
||||
|
||||
return EvaluateCloseOutcomeAsync(wisagId, processId, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<CloseWorkflowResult> EvaluateCloseOutcomeAsync(
|
||||
int wisagId,
|
||||
int processId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var activeForUser = await dbContext.Database
|
||||
.SqlQuery<int>($"SELECT COUNT(1) FROM dbo.TBCUST_F19_LISTING_RESULT WHERE userid = {wisagId} AND processid = {processId}")
|
||||
.SingleAsync(cancellationToken);
|
||||
|
||||
if (activeForUser > 0)
|
||||
{
|
||||
return new CloseWorkflowResult
|
||||
{
|
||||
ProcessId = processId,
|
||||
IsClosed = true,
|
||||
Outcome = CloseWorkflowOutcome.Closed
|
||||
};
|
||||
}
|
||||
|
||||
var activeAnyUser = await dbContext.Database
|
||||
.SqlQuery<int>($"SELECT COUNT(1) FROM dbo.TBCUST_F19_LISTING_RESULT WHERE processid = {processId}")
|
||||
.SingleAsync(cancellationToken);
|
||||
|
||||
if (activeAnyUser > 0)
|
||||
{
|
||||
return new CloseWorkflowResult
|
||||
{
|
||||
ProcessId = processId,
|
||||
IsClosed = false,
|
||||
Outcome = CloseWorkflowOutcome.NoAccess
|
||||
};
|
||||
}
|
||||
|
||||
var inHistory = await dbContext.Database
|
||||
.SqlQuery<int>($"SELECT COUNT(1) FROM dbo.TBMWF_PROFILE_OBJ_STATE_HISTORY WHERE OBJ_ID = {processId}")
|
||||
.SingleAsync(cancellationToken);
|
||||
|
||||
if (inHistory > 0)
|
||||
{
|
||||
return new CloseWorkflowResult
|
||||
{
|
||||
ProcessId = processId,
|
||||
IsClosed = false,
|
||||
Outcome = CloseWorkflowOutcome.AlreadyClosed
|
||||
};
|
||||
}
|
||||
|
||||
return new CloseWorkflowResult
|
||||
{
|
||||
ProcessId = 0,
|
||||
IsClosed = false,
|
||||
Outcome = CloseWorkflowOutcome.NotFound
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<int?> TryGetWorkflowMetaCodeAsync(int wisagId, string objectType, CancellationToken cancellationToken)
|
||||
{
|
||||
const string resultType = "json";
|
||||
|
||||
var jsonPayload = await dbContext.Database
|
||||
.SqlQuery<string>($"EXEC dbo.PRCUST_F19_GET_LISTING_DATA @pUSER_ID={wisagId}, @pRESULT_TYPE={resultType}, @pOBJECT_TYPE={objectType}")
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(jsonPayload))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using var document = JsonDocument.Parse(jsonPayload);
|
||||
|
||||
if (!document.RootElement.TryGetProperty("response", out var responseElement))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!responseElement.TryGetProperty("meta", out var metaElement) || metaElement.ValueKind != JsonValueKind.Array)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (metaElement.GetArrayLength() == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!metaElement[0].TryGetProperty("code", out var codeElement))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return codeElement.GetInt32();
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user