Refactored all EnvelopeGenerator.Jobs files to use the EnvelopeGenerator.Jobs namespace instead of EnvelopeGenerator.CommonServices.Jobs. Updated the .csproj to remove custom content and compile includes for the Jobs folder. Switched FinalizeDocumentJob to use dependency injection for PDFBurner, PDFMerger, and ReportCreator. Improved image annotation logic in PDFBurner for better placement and scaling, and refactored form field value rendering for conditional font styling. Aliased Document as LayoutDocument in ReportCreator to avoid ambiguity. Removed the obsolete Class1.cs file and made minor type safety improvements. These changes modernize the codebase and enhance maintainability.
230 lines
10 KiB
C#
230 lines
10 KiB
C#
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using EnvelopeGenerator.Domain.Constants;
|
|
using EnvelopeGenerator.Domain.Entities;
|
|
using Microsoft.Data.SqlClient;
|
|
using Microsoft.Extensions.Logging;
|
|
using Quartz;
|
|
using static EnvelopeGenerator.Jobs.FinalizeDocument.FinalizeDocumentExceptions;
|
|
|
|
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
|
|
public class FinalizeDocumentJob : IJob
|
|
{
|
|
private readonly ILogger<FinalizeDocumentJob> _logger;
|
|
private readonly PDFBurner _pdfBurner;
|
|
private readonly PDFMerger _pdfMerger;
|
|
private readonly ReportCreator _reportCreator;
|
|
|
|
private record ConfigSettings(string DocumentPath, string DocumentPathOrigin, string ExportPath);
|
|
|
|
public FinalizeDocumentJob(
|
|
ILogger<FinalizeDocumentJob> logger,
|
|
PDFBurner pdfBurner,
|
|
PDFMerger pdfMerger,
|
|
ReportCreator reportCreator)
|
|
{
|
|
_logger = logger;
|
|
_pdfBurner = pdfBurner;
|
|
_pdfMerger = pdfMerger;
|
|
_reportCreator = reportCreator;
|
|
}
|
|
|
|
public async Task Execute(IJobExecutionContext context)
|
|
{
|
|
var jobId = context.JobDetail.Key.ToString();
|
|
_logger.LogDebug("Starting job {JobId}", jobId);
|
|
|
|
try
|
|
{
|
|
var connectionString = context.MergedJobDataMap.GetString(Value.DATABASE);
|
|
if (string.IsNullOrWhiteSpace(connectionString))
|
|
{
|
|
_logger.LogWarning("FinalizeDocument - Connection string missing");
|
|
return;
|
|
}
|
|
|
|
await using var connection = new SqlConnection(connectionString);
|
|
await connection.OpenAsync(context.CancellationToken);
|
|
|
|
var config = await LoadConfigurationAsync(connection, context.CancellationToken);
|
|
var envelopes = await LoadCompletedEnvelopesAsync(connection, context.CancellationToken);
|
|
|
|
if (envelopes.Count == 0)
|
|
{
|
|
_logger.LogInformation("No completed envelopes found");
|
|
return;
|
|
}
|
|
|
|
var total = envelopes.Count;
|
|
var current = 1;
|
|
|
|
foreach (var envelopeId in envelopes)
|
|
{
|
|
_logger.LogInformation("Finalizing Envelope {EnvelopeId} ({Current}/{Total})", envelopeId, current, total);
|
|
try
|
|
{
|
|
var envelopeData = await GetEnvelopeDataAsync(connection, envelopeId, context.CancellationToken);
|
|
if (envelopeData is null)
|
|
{
|
|
_logger.LogWarning("Envelope data not found for {EnvelopeId}", envelopeId);
|
|
continue;
|
|
}
|
|
|
|
var data = envelopeData.Value;
|
|
|
|
var envelope = new Envelope
|
|
{
|
|
Id = envelopeId,
|
|
Uuid = data.EnvelopeUuid ?? string.Empty,
|
|
Title = data.Title ?? string.Empty,
|
|
FinalEmailToCreator = (int)FinalEmailType.No,
|
|
FinalEmailToReceivers = (int)FinalEmailType.No
|
|
};
|
|
|
|
var burned = _pdfBurner.BurnAnnotsToPDF(data.DocumentBytes, data.AnnotationData, envelopeId);
|
|
var report = _reportCreator.CreateReport(connection, envelope);
|
|
var merged = _pdfMerger.MergeDocuments(burned, report);
|
|
|
|
var outputDirectory = Path.Combine(config.ExportPath, data.ParentFolderUid);
|
|
Directory.CreateDirectory(outputDirectory);
|
|
var outputPath = Path.Combine(outputDirectory, $"{envelope.Uuid}.pdf");
|
|
await File.WriteAllBytesAsync(outputPath, merged, context.CancellationToken);
|
|
|
|
await UpdateDocumentResultAsync(connection, envelopeId, merged, context.CancellationToken);
|
|
await ArchiveEnvelopeAsync(connection, envelopeId, context.CancellationToken);
|
|
}
|
|
catch (MergeDocumentException ex)
|
|
{
|
|
_logger.LogWarning(ex, "Certificate Document job failed at merging documents");
|
|
}
|
|
catch (ExportDocumentException ex)
|
|
{
|
|
_logger.LogWarning(ex, "Certificate Document job failed at exporting document");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unhandled exception while working envelope {EnvelopeId}", envelopeId);
|
|
}
|
|
|
|
current++;
|
|
_logger.LogInformation("Envelope {EnvelopeId} finalized", envelopeId);
|
|
}
|
|
|
|
_logger.LogDebug("Completed job {JobId} successfully", jobId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Certificate Document job failed");
|
|
}
|
|
finally
|
|
{
|
|
_logger.LogDebug("Job execution for {JobId} ended", jobId);
|
|
}
|
|
}
|
|
|
|
private async Task<ConfigSettings> LoadConfigurationAsync(SqlConnection connection, CancellationToken cancellationToken)
|
|
{
|
|
const string sql = "SELECT TOP 1 DOCUMENT_PATH, EXPORT_PATH FROM TBSIG_CONFIG";
|
|
await using var command = new SqlCommand(sql, connection);
|
|
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
if (await reader.ReadAsync(cancellationToken))
|
|
{
|
|
var documentPath = reader.IsDBNull(0) ? string.Empty : reader.GetString(0);
|
|
var exportPath = reader.IsDBNull(1) ? string.Empty : reader.GetString(1);
|
|
return new ConfigSettings(documentPath, documentPath, exportPath);
|
|
}
|
|
|
|
return new ConfigSettings(string.Empty, string.Empty, Path.GetTempPath());
|
|
}
|
|
|
|
private async Task<List<int>> LoadCompletedEnvelopesAsync(SqlConnection connection, CancellationToken cancellationToken)
|
|
{
|
|
const string sql = "SELECT GUID FROM TBSIG_ENVELOPE WHERE STATUS = @Status AND DATEDIFF(minute, CHANGED_WHEN, GETDATE()) >= 1 ORDER BY GUID";
|
|
var ids = new List<int>();
|
|
await using var command = new SqlCommand(sql, connection);
|
|
command.Parameters.AddWithValue("@Status", (int)EnvelopeStatus.EnvelopeCompletelySigned);
|
|
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
while (await reader.ReadAsync(cancellationToken))
|
|
{
|
|
ids.Add(reader.GetInt32(0));
|
|
}
|
|
|
|
return ids;
|
|
}
|
|
|
|
private async Task<(int EnvelopeId, string? EnvelopeUuid, string? Title, byte[] DocumentBytes, List<string> AnnotationData, string ParentFolderUid)?> GetEnvelopeDataAsync(SqlConnection connection, int envelopeId, CancellationToken cancellationToken)
|
|
{
|
|
const string sql = @"SELECT T.GUID, T.ENVELOPE_UUID, T.TITLE, T2.FILEPATH, T2.BYTE_DATA FROM [dbo].[TBSIG_ENVELOPE] T
|
|
JOIN TBSIG_ENVELOPE_DOCUMENT T2 ON T.GUID = T2.ENVELOPE_ID
|
|
WHERE T.GUID = @EnvelopeId";
|
|
|
|
await using var command = new SqlCommand(sql, connection);
|
|
command.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SingleRow, cancellationToken);
|
|
if (!await reader.ReadAsync(cancellationToken))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var envelopeUuid = reader.IsDBNull(1) ? string.Empty : reader.GetString(1);
|
|
var title = reader.IsDBNull(2) ? string.Empty : reader.GetString(2);
|
|
var filePath = reader.IsDBNull(3) ? string.Empty : reader.GetString(3);
|
|
var bytes = reader.IsDBNull(4) ? Array.Empty<byte>() : (byte[])reader[4];
|
|
await reader.CloseAsync();
|
|
|
|
if (bytes.Length == 0 && !string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath))
|
|
{
|
|
bytes = await File.ReadAllBytesAsync(filePath, cancellationToken);
|
|
}
|
|
|
|
var annotations = await GetAnnotationDataAsync(connection, envelopeId, cancellationToken);
|
|
|
|
var parentFolderUid = !string.IsNullOrWhiteSpace(filePath)
|
|
? Path.GetFileName(Path.GetDirectoryName(filePath) ?? string.Empty)
|
|
: envelopeUuid;
|
|
|
|
return (envelopeId, envelopeUuid, title, bytes, annotations, parentFolderUid ?? envelopeUuid ?? envelopeId.ToString());
|
|
}
|
|
|
|
private async Task<List<string>> GetAnnotationDataAsync(SqlConnection connection, int envelopeId, CancellationToken cancellationToken)
|
|
{
|
|
const string sql = "SELECT VALUE FROM TBSIG_DOCUMENT_STATUS WHERE ENVELOPE_ID = @EnvelopeId";
|
|
var result = new List<string>();
|
|
await using var command = new SqlCommand(sql, connection);
|
|
command.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
while (await reader.ReadAsync(cancellationToken))
|
|
{
|
|
if (!reader.IsDBNull(0))
|
|
{
|
|
result.Add(reader.GetString(0));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static async Task UpdateDocumentResultAsync(SqlConnection connection, int envelopeId, byte[] bytes, CancellationToken cancellationToken)
|
|
{
|
|
const string sql = "UPDATE TBSIG_ENVELOPE SET DOC_RESULT = @ImageData WHERE GUID = @EnvelopeId";
|
|
await using var command = new SqlCommand(sql, connection);
|
|
command.Parameters.AddWithValue("@ImageData", bytes);
|
|
command.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
await command.ExecuteNonQueryAsync(cancellationToken);
|
|
}
|
|
|
|
private static async Task ArchiveEnvelopeAsync(SqlConnection connection, int envelopeId, CancellationToken cancellationToken)
|
|
{
|
|
const string sql = "UPDATE TBSIG_ENVELOPE SET STATUS = @Status, CHANGED_WHEN = GETDATE() WHERE GUID = @EnvelopeId";
|
|
await using var command = new SqlCommand(sql, connection);
|
|
command.Parameters.AddWithValue("@Status", (int)EnvelopeStatus.EnvelopeArchived);
|
|
command.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
await command.ExecuteNonQueryAsync(cancellationToken);
|
|
}
|
|
}
|