Files
EnvelopeGenerator/EnvelopeGenerator.ServiceHost/Jobs/FinalizeDocumentJob.cs
TekH b28084bf19 Replace Info logging with LogInformation
Renamed all usages of the Info logging method to LogInformation across the codebase, including in the Logger class. This aligns logging with standard conventions and improves consistency with common logging frameworks.
2026-02-25 13:37:00 +01:00

435 lines
16 KiB
C#

using System.Data;
using System.IO;
using DigitalData.Modules.Base;
using DigitalData.Modules.Database;
using DigitalData.Modules.Logging;
using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.ServiceHost.Exceptions;
using EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
using GdPicture14;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Options;
namespace EnvelopeGenerator.ServiceHost.Jobs;
public class FinalizeDocumentJob(IOptions<WorkerOptions> options, IConfiguration config)
{
private readonly WorkerOptions _options = options.Value;
private readonly LicenseManager _licenseManager = new();
private GdViewer? _gdViewer;
private LogConfig? _logConfig;
private Logger? _logger;
private MSSQLServer? _database;
private DbConfig? _config;
private string _databaseConnectionString = string.Empty;
private ConfigModel? _configModel;
private EnvelopeModel? _envelopeModel;
private ReportModel? _reportModel;
private ActionService? _actionService;
private PDFBurner? _pdfBurner;
private PDFMerger? _pdfMerger;
private ReportCreator? _reportCreator;
private const int CompleteWaitTime = 1;
private string _parentFolderUid = string.Empty;
private TempFiles? _tempFiles;
private sealed class EnvelopeData
{
public int EnvelopeId { get; set; }
public string EnvelopeUuid { get; set; } = string.Empty;
public string DocumentPath { get; set; } = string.Empty;
public List<string> AnnotationData { get; set; } = new();
public byte[]? DocAsByte { get; set; }
}
public Task ExecuteAsync(CancellationToken cancellationToken = default)
{
var gdPictureKey = _options.GdPictureLicenseKey;
_logConfig = new LogConfig { Debug = _options.Debug };
_logger = _logConfig.GetLogger();
_tempFiles = new TempFiles(_logConfig);
_tempFiles.Create();
var jobId = typeof(FinalizeDocumentJob).FullName;
_logger.LogDebug("Starting job {0}", jobId);
try
{
_logger.LogDebug("Loading GdViewer..");
_gdViewer = new GdViewer();
_licenseManager.RegisterKEY(gdPictureKey);
_logger.LogDebug("Loading Database..");
var connectionString = config.GetConnectionString("Default") ?? throw new InvalidOperationException("Connection string 'Default' not found.");
_databaseConnectionString = MSSQLServer.DecryptConnectionString(connectionString);
_database = new MSSQLServer(_logConfig, _databaseConnectionString);
_logger.LogDebug("Loading Models & Services");
var state = GetState();
InitializeModels(state);
_logger.LogDebug("Loading Configuration..");
_config = _configModel?.LoadConfiguration() ?? new DbConfig();
state.DbConfig = _config;
InitializeServices(state);
_logger.LogDebug("Loading PDFBurner..");
var pdfBurnerParams = _options.PdfBurner;
_pdfBurner = new PDFBurner(_logConfig, gdPictureKey, pdfBurnerParams, _databaseConnectionString);
_logger.LogDebug("Loading PDFMerger..");
_pdfMerger = new PDFMerger(_logConfig, gdPictureKey);
_logger.LogDebug("Loading ReportCreator..");
_reportCreator = new ReportCreator(_logConfig, state);
_config.DocumentPath = _config.DocumentPath;
_logger.LogDebug("DocumentPath: [{0}]", _config.DocumentPath);
_logger.LogDebug("ExportPath: [{0}]", _config.ExportPath);
var completeStatus = EnvelopeStatus.EnvelopeCompletelySigned;
var sql = $"SELECT * FROM TBSIG_ENVELOPE WHERE STATUS = {completeStatus} AND DATEDIFF(minute, CHANGED_WHEN, GETDATE()) >= {CompleteWaitTime} ORDER BY GUID";
var table = _database.GetDatatable(sql);
var envelopeIds = table.Rows.Cast<DataRow>()
.Select(r => r.Field<int>("GUID"))
.ToList();
if (envelopeIds.Count > 0)
{
_logger.LogInformation("Found [{0}] completed envelopes.", envelopeIds.Count);
}
var total = envelopeIds.Count;
var current = 1;
foreach (var id in envelopeIds)
{
_logger.LogInformation("Finalizing Envelope [{0}] ({1}/{2})", id, current, total);
try
{
var envelope = _envelopeModel?.GetById(id);
if (envelope is null)
{
_logger.Warn("Envelope could not be loaded for Id [{0}]!", id);
throw new ArgumentNullException(nameof(EnvelopeData));
}
_logger.LogDebug("Loading Envelope Data..");
var envelopeData = GetEnvelopeData(id);
if (envelopeData is null)
{
_logger.Warn("EnvelopeData could not be loaded for Id [{0}]!", id);
throw new ArgumentNullException(nameof(EnvelopeData));
}
_logger.LogDebug("Burning Annotations to pdf ...");
var burnedDocument = BurnAnnotationsToPdf(envelopeData);
if (burnedDocument is null)
{
_logger.Warn("Document could not be finalized!");
throw new ApplicationException("Document could not be finalized");
}
if (_actionService?.CreateReport(envelope) == false)
{
_logger.Warn("Document Report could not be created!");
throw new ApplicationException("Document Report could not be created");
}
_logger.LogDebug("Creating report..");
var report = _reportCreator!.CreateReport(envelope);
_logger.LogDebug("Report created!");
_logger.LogDebug("Merging documents ...");
var mergedDocument = _pdfMerger!.MergeDocuments(burnedDocument, report);
_logger.LogDebug("Documents merged!");
var outputDirectoryPath = Path.Combine(_config.ExportPath, _parentFolderUid);
_logger.LogDebug("oOutputDirectoryPath is {0}", outputDirectoryPath);
if (!Directory.Exists(outputDirectoryPath))
{
_logger.LogDebug("Directory not existing. Creating ... ");
Directory.CreateDirectory(outputDirectoryPath);
}
var outputFilePath = Path.Combine(outputDirectoryPath, $"{envelope.Uuid}.pdf");
_logger.LogDebug("Writing finalized Pdf to disk..");
_logger.LogInformation("Output path is [{0}]", outputFilePath);
try
{
File.WriteAllBytes(outputFilePath, mergedDocument);
}
catch (Exception ex)
{
_logger.Warn("Could not export final document to disk!");
throw new ExportDocumentException("Could not export final document to disk!", ex);
}
_logger.LogDebug("Writing EB-bytes to database...");
UpdateFileDb(outputFilePath, envelope.Id);
if (!SendFinalEmails(envelope))
{
throw new ApplicationException("Final emails could not be sent!");
}
_logger.LogInformation("Report-mails successfully sent!");
_logger.LogDebug("Setting envelope status..");
if (_actionService?.FinalizeEnvelope(envelope) == false)
{
_logger.Warn("Envelope could not be finalized!");
throw new ApplicationException("Envelope could not be finalized");
}
}
catch (Exception ex)
{
_logger.Error(ex);
_logger.Warn(ex, "Unhandled exception while working envelope [{0}]", id);
}
current += 1;
_logger.LogInformation("Envelope [{0}] finalized!", id);
}
_logger.LogDebug("Completed job {0} successfully!", jobId);
}
catch (MergeDocumentException ex)
{
_logger.Warn("Certificate Document job failed at step: Merging documents!");
_logger.Error(ex);
}
catch (ExportDocumentException ex)
{
_logger.Warn("Certificate Document job failed at step: Exporting document!");
_logger.Error(ex);
}
catch (Exception ex)
{
_logger.Warn("Certificate Document job failed!");
_logger.Error(ex);
}
finally
{
_logger.LogDebug("Job execution for [{0}] ended", jobId);
}
return Task.FromResult(true);
}
private void UpdateFileDb(string filePath, long envelopeId)
{
try
{
var imageData = ReadFile(filePath);
if (imageData is null)
{
return;
}
var query = $"UPDATE TBSIG_ENVELOPE SET DOC_RESULT = @ImageData WHERE GUID = {envelopeId}";
using var command = new SqlCommand(query, _database!.GetConnection);
command.Parameters.Add(new SqlParameter("@ImageData", imageData));
command.ExecuteNonQuery();
}
catch (Exception ex)
{
_logger?.Error(ex);
}
}
private static byte[]? ReadFile(string path)
{
var fileInfo = new FileInfo(path);
var numBytes = fileInfo.Length;
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
using var reader = new BinaryReader(stream);
return reader.ReadBytes((int)numBytes);
}
private bool SendFinalEmails(Envelope envelope)
{
var mailToCreator = (FinalEmailType)(envelope.FinalEmailToCreator ?? 0);
var mailToReceivers = (FinalEmailType)(envelope.FinalEmailToReceivers ?? 0);
if (mailToCreator != FinalEmailType.No)
{
_logger?.LogDebug("Sending email to creator ...");
SendFinalEmailToCreator(envelope, mailToCreator);
}
else
{
_logger?.Warn("No SendFinalEmailToCreator - oMailToCreator [{0}] <> [{1}] ", mailToCreator, FinalEmailType.No);
}
if (mailToReceivers != FinalEmailType.No)
{
_logger?.LogDebug("Sending emails to receivers..");
SendFinalEmailToReceivers(envelope, mailToReceivers);
}
else
{
_logger?.Warn("No SendFinalEmailToReceivers - oMailToCreator [{0}] <> [{1}] ", mailToReceivers, FinalEmailType.No);
}
return true;
}
private bool SendFinalEmailToCreator(Envelope envelope, FinalEmailType mailToCreator)
{
var includeAttachment = SendFinalEmailWithAttachment(mailToCreator);
_logger?.LogDebug("Attachment included: [{0}]", includeAttachment);
if (_actionService?.CompleteEnvelope(envelope) == false)
{
_logger?.Error(new Exception("CompleteEnvelope failed"), "Envelope could not be completed for receiver [{0}]", envelope.User?.Email);
return false;
}
return true;
}
private bool SendFinalEmailToReceivers(Envelope envelope, FinalEmailType mailToReceivers)
{
var includeAttachment = SendFinalEmailWithAttachment(mailToReceivers);
_logger?.LogDebug("Attachment included: [{0}]", includeAttachment);
foreach (var receiver in envelope.EnvelopeReceivers ?? Enumerable.Empty<EnvelopeReceiver>())
{
if (_actionService?.CompleteEnvelope(envelope, receiver.Receiver) == false)
{
_logger?.Error(new Exception("CompleteEnvelope failed"), "Envelope could not be completed for receiver [{0}]", receiver.Receiver?.EmailAddress);
return false;
}
}
return true;
}
private static bool SendFinalEmailWithAttachment(FinalEmailType type)
{
return type == FinalEmailType.YesWithAttachment;
}
private byte[] BurnAnnotationsToPdf(EnvelopeData envelopeData)
{
var envelopeId = envelopeData.EnvelopeId;
_logger?.LogInformation("Burning [{0}] signatures", envelopeData.AnnotationData.Count);
var annotations = envelopeData.AnnotationData;
var inputPath = string.Empty;
if (envelopeData.DocAsByte is null)
{
inputPath = envelopeData.DocumentPath;
_logger?.LogInformation("Input path: [{0}]", inputPath);
}
else
{
_logger?.LogDebug("we got bytes..");
inputPath = _config!.DocumentPathOrigin;
_logger?.LogDebug("oInputPath: {0}", _config.DocumentPathOrigin);
}
if (envelopeData.DocAsByte is null)
{
var directorySource = Path.GetDirectoryName(inputPath) ?? string.Empty;
var split = directorySource.Split('\\');
_parentFolderUid = split[^1];
}
else
{
_parentFolderUid = envelopeData.EnvelopeUuid;
}
_logger?.LogInformation("ParentFolderUID: [{0}]", _parentFolderUid);
byte[] inputDocumentBuffer;
if (envelopeData.DocAsByte is not null)
{
inputDocumentBuffer = envelopeData.DocAsByte;
}
else
{
try
{
inputDocumentBuffer = File.ReadAllBytes(inputPath);
}
catch (Exception ex)
{
throw new BurnAnnotationException("Source document could not be read from disk!", ex);
}
}
return _pdfBurner!.BurnAnnotsToPDF(inputDocumentBuffer, annotations, envelopeId);
}
private EnvelopeData? GetEnvelopeData(int envelopeId)
{
var sql = $"SELECT T.GUID, T.ENVELOPE_UUID, T.ENVELOPE_TYPE, T2.FILEPATH, T2.BYTE_DATA FROM [dbo].[TBSIG_ENVELOPE] T\n JOIN TBSIG_ENVELOPE_DOCUMENT T2 ON T.GUID = T2.ENVELOPE_ID\n WHERE T.GUID = {envelopeId}";
var table = _database!.GetDatatable(sql);
var row = table.Rows.Cast<DataRow>().SingleOrDefault();
if (row is null)
{
return null;
}
var annotationData = GetAnnotationData(envelopeId);
var data = new EnvelopeData
{
EnvelopeId = envelopeId,
DocumentPath = row.ItemEx("FILEPATH", string.Empty),
AnnotationData = annotationData,
DocAsByte = row.Field<byte[]?>("BYTE_DATA"),
EnvelopeUuid = row.ItemEx("ENVELOPE_UUID", string.Empty)
};
_logger?.LogDebug("Document path: [{0}]", data.DocumentPath);
return data;
}
private List<string> GetAnnotationData(int envelopeId)
{
var sql = $"SELECT VALUE FROM TBSIG_DOCUMENT_STATUS WHERE ENVELOPE_ID = {envelopeId}";
var table = _database!.GetDatatable(sql);
return table.Rows.Cast<DataRow>()
.Select(r => r.ItemEx("VALUE", string.Empty))
.ToList();
}
private void InitializeServices(State state)
{
_actionService = new ActionService(state, _database!);
}
private void InitializeModels(State state)
{
_configModel = new ConfigModel(state);
_envelopeModel = new EnvelopeModel(state);
_reportModel = new ReportModel(state);
}
private State GetState()
{
return new State
{
LogConfig = _logConfig!,
Database = _database!,
UserId = 0,
Config = null,
DbConfig = null
};
}
}