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; /// /// Workflow repository implementation for reading workflow listing data and applying close operations. /// public sealed class WorkflowRepository(TaskFlowDbContext dbContext, IOptions databaseOptions) : IWorkflowRepository { /// /// Retrieves grouped workflow data for a user. /// /// WISAG user identifier. /// Cancellation token. /// Grouped workflow response. public async Task 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(); 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 }; } /// /// Applies a closing decision to a workflow item. /// /// WISAG user identifier. /// Source system name. /// Process identifier. /// Closing decision payload. /// Cancellation token. /// Result of the close operation. public Task 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 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 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> 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 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 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(); } }