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 Quartz; namespace EnvelopeGenerator.ServiceHost.Jobs; public class FinalizeDocumentJob : IJob { 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 AnnotationData { get; set; } = new(); public byte[]? DocAsByte { get; set; } } public Task Execute(IJobExecutionContext context) { var gdPictureKey = (string)context.MergedJobDataMap[Value.GDPICTURE]; _logConfig = (LogConfig)context.MergedJobDataMap[Value.LOGCONFIG]; _logger = _logConfig.GetLogger(); _tempFiles = new TempFiles(_logConfig); _tempFiles.Create(); var jobId = context.JobDetail.Key; _logger.Debug("Starting job {0}", jobId); try { _logger.Debug("Loading GdViewer.."); _gdViewer = new GdViewer(); _licenseManager.RegisterKEY(gdPictureKey); _logger.Debug("Loading Database.."); var connectionString = (string)context.MergedJobDataMap[Value.DATABASE]; _databaseConnectionString = MSSQLServer.DecryptConnectionString(connectionString); _database = new MSSQLServer(_logConfig, _databaseConnectionString); _logger.Debug("Loading Models & Services"); var state = GetState(); InitializeModels(state); _logger.Debug("Loading Configuration.."); _config = _configModel?.LoadConfiguration() ?? new DbConfig(); state.DbConfig = _config; InitializeServices(state); _logger.Debug("Loading PDFBurner.."); var pdfBurnerParams = (PDFBurnerParams)context.MergedJobDataMap[Value.PDF_BURNER_PARAMS]; _pdfBurner = new PDFBurner(_logConfig, gdPictureKey, pdfBurnerParams, _databaseConnectionString); _logger.Debug("Loading PDFMerger.."); _pdfMerger = new PDFMerger(_logConfig, gdPictureKey); _logger.Debug("Loading ReportCreator.."); _reportCreator = new ReportCreator(_logConfig, state); _config.DocumentPath = _config.DocumentPath; _logger.Debug("DocumentPath: [{0}]", _config.DocumentPath); _logger.Debug("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() .Select(r => r.Field("GUID")) .ToList(); if (envelopeIds.Count > 0) { _logger.Info("Found [{0}] completed envelopes.", envelopeIds.Count); } var total = envelopeIds.Count; var current = 1; foreach (var id in envelopeIds) { _logger.Info("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.Debug("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.Debug("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.Debug("Creating report.."); var report = _reportCreator!.CreateReport(envelope); _logger.Debug("Report created!"); _logger.Debug("Merging documents ..."); var mergedDocument = _pdfMerger!.MergeDocuments(burnedDocument, report); _logger.Debug("Documents merged!"); var outputDirectoryPath = Path.Combine(_config.ExportPath, _parentFolderUid); _logger.Debug("oOutputDirectoryPath is {0}", outputDirectoryPath); if (!Directory.Exists(outputDirectoryPath)) { _logger.Debug("Directory not existing. Creating ... "); Directory.CreateDirectory(outputDirectoryPath); } var outputFilePath = Path.Combine(outputDirectoryPath, $"{envelope.Uuid}.pdf"); _logger.Debug("Writing finalized Pdf to disk.."); _logger.Info("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.Debug("Writing EB-bytes to database..."); UpdateFileDb(outputFilePath, envelope.Id); if (!SendFinalEmails(envelope)) { throw new ApplicationException("Final emails could not be sent!"); } _logger.Info("Report-mails successfully sent!"); _logger.Debug("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.Info("Envelope [{0}] finalized!", id); } _logger.Debug("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.Debug("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?.Debug("Sending email to creator ..."); SendFinalEmailToCreator(envelope, mailToCreator); } else { _logger?.Warn("No SendFinalEmailToCreator - oMailToCreator [{0}] <> [{1}] ", mailToCreator, FinalEmailType.No); } if (mailToReceivers != FinalEmailType.No) { _logger?.Debug("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?.Debug("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?.Debug("Attachment included: [{0}]", includeAttachment); foreach (var receiver in envelope.EnvelopeReceivers ?? Enumerable.Empty()) { 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?.Info("Burning [{0}] signatures", envelopeData.AnnotationData.Count); var annotations = envelopeData.AnnotationData; var inputPath = string.Empty; if (envelopeData.DocAsByte is null) { inputPath = envelopeData.DocumentPath; _logger?.Info("Input path: [{0}]", inputPath); } else { _logger?.Debug("we got bytes.."); inputPath = _config!.DocumentPathOrigin; _logger?.Debug("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?.Info("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) }; _logger?.Debug("Document path: [{0}]", data.DocumentPath); 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)) .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 }; } }