using System.Data; using DigitalData.Modules.Database; 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; using EnvelopeGenerator.ServiceHost.Extensions; using MediatR; using EnvelopeGenerator.Application.Configuration.Queries; using EnvelopeGenerator.Application.Common.Dto; using EnvelopeGenerator.Application.Envelopes.Queries; using DigitalData.Core.Abstraction.Application.Repository; using Microsoft.EntityFrameworkCore; namespace EnvelopeGenerator.ServiceHost.Jobs; [Obsolete("ActionService is a placeholder service added by copilot. Migrate the actual logic from CommonServices.Jobs")] public class FinalizeDocumentJob(IOptions options, IConfiguration config, ILogger logger, TempFiles tempFiles, ActionService actionService, PDFBurner pdfBurner, PDFMerger pdfMerger, ReportCreator reportCreator, ReportModel _reportModel, MSSQLServer _database, GdViewer? _gdViewer, LicenseManager licenseManager, IMediator mediator, IRepository envRepo) { private readonly WorkerOptions _options = options.Value; private ConfigDto? _config; private const int CompleteWaitTime = 1; private string _parentFolderUid = string.Empty; 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 AnnotationData { get; set; } = new(); public byte[]? DocAsByte { get; set; } } public bool RethrowOnError { get; set; } = true; public async Task ExecuteAsync(CancellationToken cancel = default) { var gdPictureKey = _options.GdPictureLicenseKey; tempFiles.Create(); var jobId = typeof(FinalizeDocumentJob).FullName; _config = await mediator.Send(new ReadDefaultConfigQuery(), cancel); var envelopes = await envRepo .Where(e => e.Status == EnvelopeStatus.EnvelopeCompletelySigned && e.ChangedWhen.HasValue && EF.Functions.DateDiffMinute(e.ChangedWhen.Value, DateTime.Now) >= CompleteWaitTime) .OrderBy(e => e.Id) .ToListAsync(cancel); if (envelopes.Count > 0) logger.LogInformation("Found [{count}] completed envelopes.", envelopes.Count); foreach (var envelope in envelopes) { try { Finalize(envelope); } catch (Exception ex) { logger.LogError(ex, "Unhandled exception while working envelope [{id}]", envelope.Id); if(RethrowOnError) throw; } logger.LogInformation("Envelope [{id}] finalized!", envelope.Id); } } private void Finalize(Envelope envelope) { var envelopeData = GetEnvelopeData(envelope.Id); if (envelopeData is null) { logger.LogWarning("EnvelopeData could not be loaded for Id [{id}]!", envelope.Id); throw new ArgumentNullException(nameof(EnvelopeData)); } var burnedDocument = BurnAnnotationsToPdf(envelopeData); if (burnedDocument is null) { logger.LogWarning("Document could not be finalized!"); throw new ApplicationException("Document could not be finalized"); } if (actionService?.CreateReport(envelope) == false) { logger.LogWarning("Document Report could not be created!"); throw new ApplicationException("Document Report could not be created"); } var report = reportCreator!.CreateReport(envelope); var mergedDocument = pdfMerger!.MergeDocuments(burnedDocument, report); var outputDirectoryPath = Path.Combine(_config.ExportPath, _parentFolderUid); if (!Directory.Exists(outputDirectoryPath)) Directory.CreateDirectory(outputDirectoryPath); var outputFilePath = Path.Combine(outputDirectoryPath, $"{envelope.Uuid}.pdf"); logger.LogInformation("Output path is [{outputFilePath}]", outputFilePath); try { File.WriteAllBytes(outputFilePath, mergedDocument); } catch (Exception ex) { logger.LogWarning("Could not export final document to disk!"); throw new ExportDocumentException("Could not export final document to disk!", ex); } UpdateFileDb(outputFilePath, envelope.Id); if (!SendFinalEmails(envelope)) throw new ApplicationException("Final emails could not be sent!"); logger.LogInformation("Report-mails successfully sent!"); if (actionService?.FinalizeEnvelope(envelope) == false) { logger.LogWarning("Envelope could not be finalized!"); throw new ApplicationException("Envelope could not be finalized"); } } 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?.LogError(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) SendFinalEmailToCreator(envelope, mailToCreator); else logger?.LogWarning("No SendFinalEmailToCreator - oMailToCreator [{mailToCreator}] <> [{noFinalEmailType}] ", mailToCreator, FinalEmailType.No); if (mailToReceivers != FinalEmailType.No) { SendFinalEmailToReceivers(envelope, mailToReceivers); } else { logger?.LogWarning("No SendFinalEmailToReceivers - oMailToCreator [{mailToReceivers}] <> [{noFinalEmailType}] ", mailToReceivers, FinalEmailType.No); } return true; } private bool SendFinalEmailToCreator(Envelope envelope, FinalEmailType mailToCreator) { var includeAttachment = SendFinalEmailWithAttachment(mailToCreator); if (actionService?.CompleteEnvelope(envelope) == false) { logger?.LogError(new Exception("CompleteEnvelope failed"), "Envelope could not be completed for receiver [{email}]", envelope.User?.Email); return false; } return true; } private bool SendFinalEmailToReceivers(Envelope envelope, FinalEmailType mailToReceivers) { var includeAttachment = SendFinalEmailWithAttachment(mailToReceivers); foreach (var receiver in envelope.EnvelopeReceivers ?? Enumerable.Empty()) { if (actionService?.CompleteEnvelope(envelope, receiver.Receiver) == false) { logger?.LogError(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 { inputPath = _config!.DocumentPath; } 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().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_DATA"), EnvelopeUuid = row.ItemEx("ENVELOPE_UUID", string.Empty) }; return data; } private List GetAnnotationData(int envelopeId) { var sql = $"SELECT VALUE FROM TBSIG_DOCUMENT_STATUS WHERE ENVELOPE_ID = {envelopeId}"; var table = _database!.GetDatatable(sql); return [.. table.Rows.Cast().Select(r => r.ItemEx("VALUE", string.Empty))]; } }