diff --git a/EnvelopeGenerator.Jobs/EnvelopeGenerator.Jobs.csproj b/EnvelopeGenerator.Jobs/EnvelopeGenerator.Jobs.csproj deleted file mode 100644 index 1779441d..00000000 --- a/EnvelopeGenerator.Jobs/EnvelopeGenerator.Jobs.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net8.0 - enable - enable - - - - - - - - - - - - - - - - - - - - diff --git a/EnvelopeGenerator.Jobs/Jobs/APIBackendJobs/APIEnvelopeJob.cs b/EnvelopeGenerator.Jobs/Jobs/APIBackendJobs/APIEnvelopeJob.cs deleted file mode 100644 index 7f8fac2b..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/APIBackendJobs/APIEnvelopeJob.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Threading.Tasks; -using EnvelopeGenerator.Domain.Constants; -using Microsoft.Data.SqlClient; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Quartz; - -namespace EnvelopeGenerator.Jobs.APIBackendJobs; - -public class APIEnvelopeJob(ILogger? logger = null) : IJob -{ - private readonly ILogger _logger = logger ?? NullLogger.Instance; - - public async Task Execute(IJobExecutionContext context) - { - var jobId = context.JobDetail.Key.ToString(); - _logger.LogDebug("API Envelopes - Starting job {JobId}", jobId); - - try - { - var connectionString = context.MergedJobDataMap.GetString(Value.DATABASE); - if (string.IsNullOrWhiteSpace(connectionString)) - { - _logger.LogWarning("API Envelopes - Connection string missing"); - return; - } - - await using var connection = new SqlConnection(connectionString); - await connection.OpenAsync(context.CancellationToken); - - await ProcessInvitationsAsync(connection, context.CancellationToken); - await ProcessWithdrawnAsync(connection, context.CancellationToken); - - _logger.LogDebug("API Envelopes - Completed job {JobId} successfully", jobId); - } - catch (System.Exception ex) - { - _logger.LogError(ex, "API Envelopes job failed"); - } - finally - { - _logger.LogDebug("API Envelopes execution for {JobId} ended", jobId); - } - } - - private async Task ProcessInvitationsAsync(SqlConnection connection, System.Threading.CancellationToken cancellationToken) - { - const string sql = "SELECT GUID FROM TBSIG_ENVELOPE WHERE SOURCE = 'API' AND STATUS = 1003 ORDER BY GUID"; - var envelopeIds = new List(); - - await using (var command = new SqlCommand(sql, connection)) - await using (var reader = await command.ExecuteReaderAsync(cancellationToken)) - { - while (await reader.ReadAsync(cancellationToken)) - { - if (reader[0] is int id) - { - envelopeIds.Add(id); - } - } - } - - if (envelopeIds.Count == 0) - { - _logger.LogDebug("SendInvMail - No envelopes found"); - return; - } - - _logger.LogInformation("SendInvMail - Found {Count} envelopes", envelopeIds.Count); - var total = envelopeIds.Count; - var current = 1; - - foreach (var id in envelopeIds) - { - _logger.LogInformation("SendInvMail - Processing Envelope {EnvelopeId} ({Current}/{Total})", id, current, total); - try - { - // Placeholder for invitation email sending logic. - _logger.LogDebug("SendInvMail - Marking envelope {EnvelopeId} as queued", id); - const string updateSql = "UPDATE TBSIG_ENVELOPE SET CURRENT_WORK_APP = @App WHERE GUID = @Id"; - await using var updateCommand = new SqlCommand(updateSql, connection); - updateCommand.Parameters.AddWithValue("@App", "signFLOW_API_EnvJob_InvMail"); - updateCommand.Parameters.AddWithValue("@Id", id); - await updateCommand.ExecuteNonQueryAsync(cancellationToken); - } - catch (System.Exception ex) - { - _logger.LogWarning(ex, "SendInvMail - Unhandled exception while working envelope {EnvelopeId}", id); - } - - current++; - _logger.LogInformation("SendInvMail - Envelope finalized"); - } - } - - private async Task ProcessWithdrawnAsync(SqlConnection connection, System.Threading.CancellationToken cancellationToken) - { - const string sql = @"SELECT ENV.GUID, REJ.COMMENT AS REJECTION_REASON FROM - (SELECT * FROM TBSIG_ENVELOPE WHERE STATUS = 1009 AND SOURCE = 'API') ENV INNER JOIN - (SELECT MAX(GUID) GUID, ENVELOPE_ID, MAX(ADDED_WHEN) ADDED_WHEN, MAX(ACTION_DATE) ACTION_DATE, COMMENT FROM TBSIG_ENVELOPE_HISTORY WHERE STATUS = 1009 GROUP BY ENVELOPE_ID, COMMENT ) REJ ON ENV.GUID = REJ.ENVELOPE_ID LEFT JOIN - (SELECT * FROM TBSIG_ENVELOPE_HISTORY WHERE STATUS = 3004 ) M_Send ON ENV.GUID = M_Send.ENVELOPE_ID - WHERE M_Send.GUID IS NULL"; - - var withdrawn = new List<(int EnvelopeId, string Reason)>(); - await using (var command = new SqlCommand(sql, connection)) - await using (var reader = await command.ExecuteReaderAsync(cancellationToken)) - { - while (await reader.ReadAsync(cancellationToken)) - { - var id = reader.GetInt32(0); - var reason = reader.IsDBNull(1) ? string.Empty : reader.GetString(1); - withdrawn.Add((id, reason)); - } - } - - if (withdrawn.Count == 0) - { - _logger.LogDebug("WithdrawnEnv - No envelopes found"); - return; - } - - _logger.LogInformation("WithdrawnEnv - Found {Count} envelopes", withdrawn.Count); - var total = withdrawn.Count; - var current = 1; - - foreach (var (envelopeId, reason) in withdrawn) - { - _logger.LogInformation("WithdrawnEnv - Processing Envelope {EnvelopeId} ({Current}/{Total})", envelopeId, current, total); - try - { - // Log withdrawn mail trigger placeholder - const string insertHistory = "INSERT INTO TBSIG_ENVELOPE_HISTORY (ENVELOPE_ID, STATUS, USER_REFERENCE, ADDED_WHEN, ACTION_DATE, COMMENT) VALUES (@EnvelopeId, @Status, @UserReference, GETDATE(), GETDATE(), @Comment)"; - await using var insertCommand = new SqlCommand(insertHistory, connection); - insertCommand.Parameters.AddWithValue("@EnvelopeId", envelopeId); - insertCommand.Parameters.AddWithValue("@Status", 3004); - insertCommand.Parameters.AddWithValue("@UserReference", "API"); - insertCommand.Parameters.AddWithValue("@Comment", reason ?? string.Empty); - await insertCommand.ExecuteNonQueryAsync(cancellationToken); - } - catch (System.Exception ex) - { - _logger.LogWarning(ex, "WithdrawnEnv - Unhandled exception while working envelope {EnvelopeId}", envelopeId); - } - - current++; - _logger.LogInformation("WithdrawnEnv - Envelope finalized"); - } - } -} diff --git a/EnvelopeGenerator.Jobs/Jobs/DataRowExtensions.cs b/EnvelopeGenerator.Jobs/Jobs/DataRowExtensions.cs deleted file mode 100644 index 805f6f1f..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/DataRowExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Data; - -namespace EnvelopeGenerator.Jobs; - -public static class DataRowExtensions -{ - public static T? GetValueOrDefault(this DataRow row, string columnName, T? defaultValue = default) - { - if (!row.Table.Columns.Contains(columnName)) - { - return defaultValue; - } - - var value = row[columnName]; - if (value == DBNull.Value) - { - return defaultValue; - } - - try - { - return (T)Convert.ChangeType(value, typeof(T)); - } - catch - { - return defaultValue; - } - } -} diff --git a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/FinalizeDocumentExceptions.cs b/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/FinalizeDocumentExceptions.cs deleted file mode 100644 index 847ab06b..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/FinalizeDocumentExceptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace EnvelopeGenerator.Jobs.FinalizeDocument; - -public static class FinalizeDocumentExceptions -{ - public class MergeDocumentException : ApplicationException - { - public MergeDocumentException(string message) : base(message) { } - public MergeDocumentException(string message, Exception innerException) : base(message, innerException) { } - } - - public class BurnAnnotationException : ApplicationException - { - public BurnAnnotationException(string message) : base(message) { } - public BurnAnnotationException(string message, Exception innerException) : base(message, innerException) { } - } - - public class CreateReportException : ApplicationException - { - public CreateReportException(string message) : base(message) { } - public CreateReportException(string message, Exception innerException) : base(message, innerException) { } - } - - public class ExportDocumentException : ApplicationException - { - public ExportDocumentException(string message) : base(message) { } - public ExportDocumentException(string message, Exception innerException) : base(message, innerException) { } - } -} diff --git a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/FinalizeDocumentJob.cs b/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/FinalizeDocumentJob.cs deleted file mode 100644 index 2ddb4e1d..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/FinalizeDocumentJob.cs +++ /dev/null @@ -1,229 +0,0 @@ -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 _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 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 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> 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(); - 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 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[])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> GetAnnotationDataAsync(SqlConnection connection, int envelopeId, CancellationToken cancellationToken) - { - const string sql = "SELECT VALUE FROM TBSIG_DOCUMENT_STATUS WHERE ENVELOPE_ID = @EnvelopeId"; - var result = new List(); - 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); - } -} diff --git a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/PDFBurner.cs b/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/PDFBurner.cs deleted file mode 100644 index 0d04846c..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/PDFBurner.cs +++ /dev/null @@ -1,277 +0,0 @@ -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using iText.IO.Image; -using iText.Kernel.Colors; -using iText.Kernel.Pdf; -using iText.Kernel.Pdf.Canvas; -using iText.Layout; -using iText.Layout.Element; -using iText.Layout.Font; -using iText.Layout.Properties; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Newtonsoft.Json; -using static EnvelopeGenerator.Jobs.FinalizeDocument.FinalizeDocumentExceptions; -using LayoutImage = iText.Layout.Element.Image; - -namespace EnvelopeGenerator.Jobs.FinalizeDocument; - -public class PDFBurner -{ - private static readonly FontProvider FontProvider = CreateFontProvider(); - private readonly ILogger _logger; - private readonly PDFBurnerParams _pdfBurnerParams; - - public PDFBurner() : this(NullLogger.Instance, new PDFBurnerParams()) - { - } - - public PDFBurner(ILogger logger, PDFBurnerParams? pdfBurnerParams = null) - { - _logger = logger; - _pdfBurnerParams = pdfBurnerParams ?? new PDFBurnerParams(); - } - - public byte[] BurnAnnotsToPDF(byte[] sourceBuffer, IList instantJsonList, int envelopeId) - { - if (sourceBuffer is null || sourceBuffer.Length == 0) - { - throw new BurnAnnotationException("Source document is empty"); - } - - try - { - using var inputStream = new MemoryStream(sourceBuffer); - using var outputStream = new MemoryStream(); - using var reader = new PdfReader(inputStream); - using var writer = new PdfWriter(outputStream); - using var pdf = new PdfDocument(reader, writer); - - foreach (var json in instantJsonList ?? Enumerable.Empty()) - { - if (string.IsNullOrWhiteSpace(json)) - { - continue; - } - - var annotationData = JsonConvert.DeserializeObject(json); - if (annotationData?.annotations is null) - { - continue; - } - - annotationData.annotations.Reverse(); - - foreach (var annotation in annotationData.annotations) - { - try - { - switch (annotation.type) - { - case AnnotationType.Image: - AddImageAnnotation(pdf, annotation, annotationData.attachments); - break; - case AnnotationType.Ink: - AddInkAnnotation(pdf, annotation); - break; - case AnnotationType.Widget: - var formFieldValue = annotationData.formFieldValues?.FirstOrDefault(fv => fv.name == annotation.id); - if (formFieldValue is not null && !_pdfBurnerParams.IgnoredLabels.Contains(formFieldValue.value)) - { - AddFormFieldValue(pdf, annotation, formFieldValue.value); - } - break; - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Error applying annotation {AnnotationId} on envelope {EnvelopeId}", annotation.id, envelopeId); - throw new BurnAnnotationException("Adding annotation failed", ex); - } - } - } - - pdf.Close(); - return outputStream.ToArray(); - } - catch (BurnAnnotationException) - { - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to burn annotations for envelope {EnvelopeId}", envelopeId); - throw new BurnAnnotationException("Annotations could not be burned", ex); - } - } - - private void AddImageAnnotation(PdfDocument pdf, Annotation annotation, Dictionary? attachments) - { - if (attachments is null || string.IsNullOrWhiteSpace(annotation.imageAttachmentId) || !attachments.TryGetValue(annotation.imageAttachmentId, out var attachment)) - { - return; - } - - var page = pdf.GetPage(annotation.pageIndex + 1); - var bounds = annotation.bbox.Select(ToInches).ToList(); - var x = (float)bounds[0]; - var y = (float)bounds[1]; - var width = (float)bounds[2]; - var height = (float)bounds[3]; - - var imageBytes = Convert.FromBase64String(attachment.binary); - var imageData = ImageDataFactory.Create(imageBytes); - var image = new LayoutImage(imageData) - .ScaleAbsolute(width, height) - .SetFixedPosition(annotation.pageIndex + 1, x, y); - - using var canvas = new Canvas(new PdfCanvas(page), page.GetPageSize()); - canvas.Add(image); - } - - private void AddInkAnnotation(PdfDocument pdf, Annotation annotation) - { - if (annotation.lines?.points is null) - { - return; - } - - var page = pdf.GetPage(annotation.pageIndex + 1); - var canvas = new PdfCanvas(page); - var color = ParseColor(annotation.strokeColor); - canvas.SetStrokeColor(color); - canvas.SetLineWidth(1); - - foreach (var segment in annotation.lines.points) - { - var first = true; - foreach (var point in segment) - { - var (px, py) = (ToInches(point[0]), ToInches(point[1])); - if (first) - { - canvas.MoveTo(px, py); - first = false; - } - else - { - canvas.LineTo(px, py); - } - } - canvas.Stroke(); - } - } - - private static FontProvider CreateFontProvider() - { - var provider = new FontProvider(); - provider.AddStandardPdfFonts(); - provider.AddSystemFonts(); - return provider; - } - - private void AddFormFieldValue(PdfDocument pdf, Annotation annotation, string value) - { - var bounds = annotation.bbox.Select(ToInches).ToList(); - var x = (float)bounds[0]; - var y = (float)bounds[1]; - var width = (float)bounds[2]; - var height = (float)bounds[3]; - - var page = pdf.GetPage(annotation.pageIndex + 1); - var canvas = new Canvas(new PdfCanvas(page), page.GetPageSize()); - canvas.SetProperty(Property.FONT_PROVIDER, FontProvider); - canvas.SetProperty(Property.FONT, FontProvider.GetFontSet()); - - var paragraph = new Paragraph(value) - .SetFontSize(_pdfBurnerParams.FontSize) - .SetFontColor(ColorConstants.BLACK) - .SetFontFamily(_pdfBurnerParams.FontName); - - if (_pdfBurnerParams.FontStyle.HasFlag(FontStyle.Italic)) - { - paragraph.SetItalic(); - } - - if (_pdfBurnerParams.FontStyle.HasFlag(FontStyle.Bold)) - { - paragraph.SetBold(); - } - - canvas.ShowTextAligned( - paragraph, - x + (float)_pdfBurnerParams.TopMargin, - y + (float)_pdfBurnerParams.YOffset, - annotation.pageIndex + 1, - iText.Layout.Properties.TextAlignment.LEFT, - iText.Layout.Properties.VerticalAlignment.TOP, - 0); - } - - private static DeviceRgb ParseColor(string? color) - { - if (string.IsNullOrWhiteSpace(color)) - { - return new DeviceRgb(0, 0, 0); - } - - try - { - var drawingColor = ColorTranslator.FromHtml(color); - return new DeviceRgb(drawingColor.R, drawingColor.G, drawingColor.B); - } - catch - { - return new DeviceRgb(0, 0, 0); - } - } - - private static double ToInches(double value) => value / 72d; - private static double ToInches(float value) => value / 72d; - - #region Model - private static class AnnotationType - { - public const string Image = "pspdfkit/image"; - public const string Ink = "pspdfkit/ink"; - public const string Widget = "pspdfkit/widget"; - } - - private sealed class AnnotationData - { - public List? annotations { get; set; } - public Dictionary? attachments { get; set; } - public List? formFieldValues { get; set; } - } - - private sealed class Annotation - { - public string id { get; set; } = string.Empty; - public List bbox { get; set; } = new(); - public string type { get; set; } = string.Empty; - public string imageAttachmentId { get; set; } = string.Empty; - public Lines? lines { get; set; } - public int pageIndex { get; set; } - public string strokeColor { get; set; } = string.Empty; - public string egName { get; set; } = string.Empty; - } - - private sealed class Lines - { - public List>> points { get; set; } = new(); - } - - private sealed class Attachment - { - public string binary { get; set; } = string.Empty; - public string contentType { get; set; } = string.Empty; - } - - private sealed class FormFieldValue - { - public string name { get; set; } = string.Empty; - public string value { get; set; } = string.Empty; - } - #endregion -} diff --git a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/PDFBurnerParams.cs b/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/PDFBurnerParams.cs deleted file mode 100644 index 0b9536fa..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/PDFBurnerParams.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; -using System.Drawing; - -namespace EnvelopeGenerator.Jobs.FinalizeDocument; - -public class PDFBurnerParams -{ - public List IgnoredLabels { get; } = new() { "Date", "Datum", "ZIP", "PLZ", "Place", "Ort", "Position", "Stellung" }; - - public double TopMargin { get; set; } = 0.1; - - public double YOffset { get; set; } = -0.3; - - public string FontName { get; set; } = "Arial"; - - public int FontSize { get; set; } = 8; - - public FontStyle FontStyle { get; set; } = FontStyle.Italic; -} diff --git a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/PDFMerger.cs b/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/PDFMerger.cs deleted file mode 100644 index 2306fac7..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/PDFMerger.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.IO; -using iText.Kernel.Pdf; -using iText.Kernel.Utils; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using static EnvelopeGenerator.Jobs.FinalizeDocument.FinalizeDocumentExceptions; - -namespace EnvelopeGenerator.Jobs.FinalizeDocument; - -public class PDFMerger -{ - private readonly ILogger _logger; - - public PDFMerger() : this(NullLogger.Instance) - { - } - - public PDFMerger(ILogger logger) - { - _logger = logger; - } - - public byte[] MergeDocuments(byte[] document, byte[] report) - { - try - { - using var finalStream = new MemoryStream(); - using var documentReader = new PdfReader(new MemoryStream(document)); - using var reportReader = new PdfReader(new MemoryStream(report)); - using var writer = new PdfWriter(finalStream); - using var targetDoc = new PdfDocument(documentReader, writer); - using var reportDoc = new PdfDocument(reportReader); - - var merger = new PdfMerger(targetDoc); - merger.Merge(reportDoc, 1, reportDoc.GetNumberOfPages()); - - targetDoc.Close(); - return finalStream.ToArray(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to merge PDF documents"); - throw new MergeDocumentException("Documents could not be merged", ex); - } - } -} diff --git a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/ReportCreator.cs b/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/ReportCreator.cs deleted file mode 100644 index b0e4aa08..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/ReportCreator.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Data; -using System.IO; -using EnvelopeGenerator.Domain.Entities; -using iText.Kernel.Pdf; -using iText.Layout.Element; -using Microsoft.Data.SqlClient; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using static EnvelopeGenerator.Jobs.FinalizeDocument.FinalizeDocumentExceptions; -using LayoutDocument = iText.Layout.Document; - -namespace EnvelopeGenerator.Jobs.FinalizeDocument; - -public class ReportCreator -{ - private readonly ILogger _logger; - - public ReportCreator() : this(NullLogger.Instance) - { - } - - public ReportCreator(ILogger logger) - { - _logger = logger; - } - - public byte[] CreateReport(SqlConnection connection, Envelope envelope) - { - try - { - var reportItems = LoadReportItems(connection, envelope.Id); - using var stream = new MemoryStream(); - using var writer = new PdfWriter(stream); - using var pdf = new PdfDocument(writer); - using var document = new LayoutDocument(pdf); - - document.Add(new Paragraph("Envelope Finalization Report").SetFontSize(16)); - document.Add(new Paragraph($"Envelope Id: {envelope.Id}")); - document.Add(new Paragraph($"UUID: {envelope.Uuid}")); - document.Add(new Paragraph($"Title: {envelope.Title}")); - document.Add(new Paragraph($"Subject: {envelope.Comment}")); - document.Add(new Paragraph($"Generated: {DateTime.UtcNow:O}")); - document.Add(new Paragraph(" ")); - - var table = new Table(4).UseAllAvailableWidth(); - table.AddHeaderCell("Date"); - table.AddHeaderCell("Status"); - table.AddHeaderCell("User"); - table.AddHeaderCell("EnvelopeId"); - - foreach (var item in reportItems.OrderByDescending(r => r.ItemDate)) - { - table.AddCell(item.ItemDate.ToString("u")); - table.AddCell(item.ItemStatus.ToString()); - table.AddCell(item.ItemUserReference); - table.AddCell(item.EnvelopeId.ToString()); - } - - document.Add(table); - document.Close(); - return stream.ToArray(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Could not create report for envelope {EnvelopeId}", envelope.Id); - throw new CreateReportException("Could not prepare report data", ex); - } - } - - private List LoadReportItems(SqlConnection connection, int envelopeId) - { - const string sql = "SELECT ENVELOPE_ID, POS_WHEN, POS_STATUS, POS_WHO FROM VWSIG_ENVELOPE_REPORT WHERE ENVELOPE_ID = @EnvelopeId"; - var result = new List(); - - using var command = new SqlCommand(sql, connection); - command.Parameters.AddWithValue("@EnvelopeId", envelopeId); - using var reader = command.ExecuteReader(); - while (reader.Read()) - { - result.Add(new ReportItem - { - EnvelopeId = reader.GetInt32(0), - ItemDate = reader.IsDBNull(1) ? DateTime.MinValue : reader.GetDateTime(1), - ItemStatus = reader.IsDBNull(2) ? default : (EnvelopeGenerator.Domain.Constants.EnvelopeStatus)reader.GetInt32(2), - ItemUserReference = reader.IsDBNull(3) ? string.Empty : reader.GetString(3) - }); - } - - return result; - } -} diff --git a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/ReportItem.cs b/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/ReportItem.cs deleted file mode 100644 index c8e0d52e..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/ReportItem.cs +++ /dev/null @@ -1,19 +0,0 @@ -using EnvelopeGenerator.Domain.Constants; -using EnvelopeGenerator.Domain.Entities; - -namespace EnvelopeGenerator.Jobs.FinalizeDocument; - -public class ReportItem -{ - public Envelope? Envelope { get; set; } - public int EnvelopeId { get; set; } - public string EnvelopeTitle { get; set; } = string.Empty; - public string EnvelopeSubject { get; set; } = string.Empty; - - public EnvelopeStatus ItemStatus { get; set; } - - public string ItemStatusTranslated => ItemStatus.ToString(); - - public string ItemUserReference { get; set; } = string.Empty; - public DateTime ItemDate { get; set; } -} diff --git a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/ReportSource.cs b/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/ReportSource.cs deleted file mode 100644 index 47afd920..00000000 --- a/EnvelopeGenerator.Jobs/Jobs/FinalizeDocument/ReportSource.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections.Generic; - -namespace EnvelopeGenerator.Jobs.FinalizeDocument; - -public class ReportSource -{ - public List Items { get; set; } = new(); -} diff --git a/EnvelopeGenerator.sln b/EnvelopeGenerator.sln index fe942d58..f55cdd56 100644 --- a/EnvelopeGenerator.sln +++ b/EnvelopeGenerator.sln @@ -33,8 +33,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.PdfEditor EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Tests", "EnvelopeGenerator.Tests\EnvelopeGenerator.Tests.csproj", "{224C4845-1CDE-22B7-F3A9-1FF9297F70E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Jobs", "EnvelopeGenerator.Jobs\EnvelopeGenerator.Jobs.csproj", "{3D0514EA-2681-4B13-AD71-35CC6363DBD7}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.API", "EnvelopeGenerator.API\EnvelopeGenerator.API.csproj", "{EC768913-6270-14F4-1DD3-69C87A659462}" EndProject Global @@ -83,10 +81,6 @@ Global {224C4845-1CDE-22B7-F3A9-1FF9297F70E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {224C4845-1CDE-22B7-F3A9-1FF9297F70E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {224C4845-1CDE-22B7-F3A9-1FF9297F70E8}.Release|Any CPU.Build.0 = Release|Any CPU - {3D0514EA-2681-4B13-AD71-35CC6363DBD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3D0514EA-2681-4B13-AD71-35CC6363DBD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3D0514EA-2681-4B13-AD71-35CC6363DBD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D0514EA-2681-4B13-AD71-35CC6363DBD7}.Release|Any CPU.Build.0 = Release|Any CPU {EC768913-6270-14F4-1DD3-69C87A659462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EC768913-6270-14F4-1DD3-69C87A659462}.Debug|Any CPU.Build.0 = Debug|Any CPU {EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -109,7 +103,6 @@ Global {6D56C01F-D6CB-4D8A-BD3D-4FD34326998C} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} {211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB} {224C4845-1CDE-22B7-F3A9-1FF9297F70E8} = {0CBC2432-A561-4440-89BC-671B66A24146} - {3D0514EA-2681-4B13-AD71-35CC6363DBD7} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB} {EC768913-6270-14F4-1DD3-69C87A659462} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution