- Added `BackToPerMethodId` constant and updated validation rules in `CloseWorkflowCommandValidator`. - Refactored `GetWorkflowsQueryHandler` for simpler `hasListingData` logic. - Replaced strongly-typed properties with `JsonElement` in `ContractsSourceBlock` and `FerdinandSourceBlock`. - Updated `WorkflowListingReadModel` to use smaller data types for efficiency. - Refactored `WorkflowRepository` to use JSON-based data retrieval and parsing. - Introduced `MetaOnlyResponse` and `WorkflowsResponse` for consistent API responses. - Updated `DocumentsController` and `WorkflowsController` to use new response types. - Added `ApiKeyAuthMiddleware` for API key validation and integrated it into the middleware pipeline. - Refactored `ExceptionHandlingMiddleware` to use `MetaOnlyResponse` for error handling. - Performed general cleanup and modernization for improved readability and maintainability.
255 lines
9.1 KiB
C#
255 lines
9.1 KiB
C#
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 DigitalData.TaskFlow.Infrastructure.Persistence.Models;
|
|
using Microsoft.Data.SqlClient;
|
|
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, IOptions<DatabaseOptions> databaseOptions) : IWorkflowRepository
|
|
{
|
|
/// <summary>
|
|
/// Retrieves grouped workflow data for a user.
|
|
/// </summary>
|
|
/// <param name="wisagId">WISAG user identifier.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>Grouped workflow response.</returns>
|
|
public async Task<WorkflowSourcesResponse> GetAsync(int wisagId, CancellationToken cancellationToken)
|
|
{
|
|
var objectType = string.IsNullOrWhiteSpace(databaseOptions.Value.WorkflowListingObjectType)
|
|
? "Ferdinand"
|
|
: databaseOptions.Value.WorkflowListingObjectType;
|
|
|
|
var jsonPayload = await ExecuteListingJsonAsync(wisagId, objectType, cancellationToken);
|
|
if (string.IsNullOrWhiteSpace(jsonPayload))
|
|
{
|
|
return new WorkflowSourcesResponse { Sources = [] };
|
|
}
|
|
|
|
using var document = JsonDocument.Parse(jsonPayload);
|
|
if (!document.RootElement.TryGetProperty("response", out var responseElement))
|
|
{
|
|
return new WorkflowSourcesResponse { Sources = [] };
|
|
}
|
|
|
|
if (TryReadMetaCode(responseElement) == 2)
|
|
{
|
|
return new WorkflowSourcesResponse { Sources = [] };
|
|
}
|
|
|
|
var sources = new List<SourceBlock>();
|
|
if (!responseElement.TryGetProperty("sources", out var sourcesElement) || sourcesElement.ValueKind != JsonValueKind.Array)
|
|
{
|
|
return new WorkflowSourcesResponse { Sources = sources };
|
|
}
|
|
|
|
foreach (var sourceElement in sourcesElement.EnumerateArray())
|
|
{
|
|
if (!sourceElement.TryGetProperty("source", out var sourceNameElement))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var sourceName = sourceNameElement.GetString();
|
|
if (string.Equals(sourceName, "Ferdinand", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
sources.Add(new FerdinandSourceBlock
|
|
{
|
|
Source = "Ferdinand",
|
|
Profile = ReadArray(sourceElement, "profile"),
|
|
Listing = ReadArray(sourceElement, "Listing"),
|
|
Approvals = ReadArray(sourceElement, "approvals"),
|
|
Document = ReadArray(sourceElement, "document"),
|
|
ClosingMethod = ReadArray(sourceElement, "closing_method"),
|
|
RejectionReason = ReadArray(sourceElement, "rejection_reason")
|
|
});
|
|
|
|
continue;
|
|
}
|
|
|
|
if (string.Equals(sourceName, "Contracts", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
sources.Add(new ContractsSourceBlock
|
|
{
|
|
Source = "Contracts",
|
|
Profile = ReadArray(sourceElement, "profile"),
|
|
Listing = ReadArray(sourceElement, "Listing"),
|
|
Approvals = ReadArray(sourceElement, "approvals"),
|
|
Document = ReadArray(sourceElement, "document"),
|
|
ClosingMethod = ReadArray(sourceElement, "closing_method")
|
|
});
|
|
}
|
|
}
|
|
|
|
return new WorkflowSourcesResponse { Sources = sources };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies a closing decision to a workflow item.
|
|
/// </summary>
|
|
/// <param name="wisagId">WISAG user identifier.</param>
|
|
/// <param name="source">Source system name.</param>
|
|
/// <param name="processId">Process identifier.</param>
|
|
/// <param name="decision">Closing decision payload.</param>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
/// <returns>Result of the close operation.</returns>
|
|
public Task<CloseWorkflowResult> CloseAsync(
|
|
int wisagId,
|
|
string source,
|
|
int processId,
|
|
CloseWorkflowDecision decision,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
_ = decision;
|
|
|
|
if (!string.Equals(source, "Ferdinand", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return Task.FromResult(new CloseWorkflowResult
|
|
{
|
|
ProcessId = processId,
|
|
IsClosed = false,
|
|
Outcome = CloseWorkflowOutcome.NotFound
|
|
});
|
|
}
|
|
|
|
return EvaluateCloseOutcomeAsync(wisagId, source, processId, cancellationToken);
|
|
}
|
|
|
|
private async Task<CloseWorkflowResult> EvaluateCloseOutcomeAsync(
|
|
int wisagId,
|
|
string source,
|
|
int processId,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var rows = await ExecuteListingTableAsync(wisagId, source, cancellationToken);
|
|
if (rows.Any(row => row.ProcessId == processId))
|
|
{
|
|
return new CloseWorkflowResult
|
|
{
|
|
ProcessId = processId,
|
|
IsClosed = true,
|
|
Outcome = CloseWorkflowOutcome.Closed
|
|
};
|
|
}
|
|
|
|
var metaCode = await TryGetWorkflowMetaCodeAsync(wisagId, source, cancellationToken);
|
|
if (metaCode == 2)
|
|
{
|
|
return new CloseWorkflowResult
|
|
{
|
|
ProcessId = processId,
|
|
IsClosed = false,
|
|
Outcome = CloseWorkflowOutcome.NoAccess
|
|
};
|
|
}
|
|
|
|
return new CloseWorkflowResult
|
|
{
|
|
ProcessId = 0,
|
|
IsClosed = false,
|
|
Outcome = CloseWorkflowOutcome.NotFound
|
|
};
|
|
}
|
|
|
|
private async Task<int?> TryGetWorkflowMetaCodeAsync(int wisagId, string objectType, CancellationToken cancellationToken)
|
|
{
|
|
var jsonPayload = await ExecuteListingJsonAsync(wisagId, objectType, cancellationToken);
|
|
|
|
if (string.IsNullOrWhiteSpace(jsonPayload))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
using var document = JsonDocument.Parse(jsonPayload);
|
|
|
|
return document.RootElement.TryGetProperty("response", out var responseElement)
|
|
? TryReadMetaCode(responseElement)
|
|
: null;
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private async Task<IReadOnlyList<WorkflowListingReadModel>> ExecuteListingTableAsync(
|
|
int wisagId,
|
|
string objectType,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
const string resultType = "table";
|
|
|
|
return await dbContext
|
|
.WorkflowListingReadModels
|
|
.FromSqlInterpolated($"EXEC dbo.PRCUST_F19_GET_LISTING_DATA @pUSER_ID={wisagId}, @pRESULT_TYPE={resultType}, @pOBJECT_TYPE={objectType}")
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
private async Task<string?> ExecuteListingJsonAsync(int wisagId, string objectType, CancellationToken cancellationToken)
|
|
{
|
|
const string commandText = "EXEC dbo.PRCUST_F19_GET_LISTING_DATA @pUSER_ID, @pRESULT_TYPE, @pOBJECT_TYPE";
|
|
const string resultType = "json";
|
|
|
|
await using var command = dbContext.Database.GetDbConnection().CreateCommand();
|
|
command.CommandText = commandText;
|
|
|
|
command.Parameters.Add(new SqlParameter("@pUSER_ID", wisagId));
|
|
command.Parameters.Add(new SqlParameter("@pRESULT_TYPE", resultType));
|
|
command.Parameters.Add(new SqlParameter("@pOBJECT_TYPE", objectType));
|
|
|
|
if (command.Connection is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (command.Connection.State != System.Data.ConnectionState.Open)
|
|
{
|
|
await command.Connection.OpenAsync(cancellationToken);
|
|
}
|
|
|
|
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
if (!await reader.ReadAsync(cancellationToken) || reader.FieldCount == 0 || reader.IsDBNull(0))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return reader.GetString(0);
|
|
}
|
|
|
|
private static int? TryReadMetaCode(JsonElement responseElement)
|
|
{
|
|
if (!responseElement.TryGetProperty("meta", out var metaElement) || metaElement.ValueKind != JsonValueKind.Array)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (metaElement.GetArrayLength() == 0 || !metaElement[0].TryGetProperty("code", out var codeElement))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return codeElement.GetInt32();
|
|
}
|
|
|
|
private static IReadOnlyList<JsonElement> ReadArray(JsonElement sourceElement, string propertyName)
|
|
{
|
|
if (!sourceElement.TryGetProperty(propertyName, out var propertyElement) || propertyElement.ValueKind != JsonValueKind.Array)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
return propertyElement.EnumerateArray().Select(item => item.Clone()).ToList();
|
|
}
|
|
}
|