feat(FinalizeDocument): aus CommonJobs kopiert, mit einfachen Fehlerbehebungen unter Verwendung von Copilot
- Programmiersprache von VSC zu C# geändert - Framework von .NET Framework zu .NET geändert
This commit is contained in:
@@ -8,6 +8,21 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="GdPicture" Version="14.3.3" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.17" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Quartz" Version="3.8.0" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.16" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
31
EnvelopeGenerator.ServiceHost/Jobs/ActionService.cs
Normal file
31
EnvelopeGenerator.ServiceHost/Jobs/ActionService.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using DigitalData.Modules.Database;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class ActionService : BaseService
|
||||
{
|
||||
public ActionService(State state, MSSQLServer database) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public bool CreateReport(Envelope envelope)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool FinalizeEnvelope(Envelope envelope)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CompleteEnvelope(Envelope envelope)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CompleteEnvelope(Envelope envelope, Receiver receiver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
18
EnvelopeGenerator.ServiceHost/Jobs/BaseModel.cs
Normal file
18
EnvelopeGenerator.ServiceHost/Jobs/BaseModel.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using DigitalData.Modules.Database;
|
||||
using DigitalData.Modules.Logging;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public abstract class BaseModel
|
||||
{
|
||||
protected MSSQLServer Database { get; }
|
||||
protected Logger Logger { get; }
|
||||
protected State State { get; }
|
||||
|
||||
protected BaseModel(State state)
|
||||
{
|
||||
Logger = state.LogConfig!.GetLogger();
|
||||
Database = state.Database!;
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
13
EnvelopeGenerator.ServiceHost/Jobs/BaseService.cs
Normal file
13
EnvelopeGenerator.ServiceHost/Jobs/BaseService.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using DigitalData.Modules.Base;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class BaseService : BaseClass
|
||||
{
|
||||
protected State State { get; }
|
||||
|
||||
public BaseService(State state) : base(state.LogConfig!)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
37
EnvelopeGenerator.ServiceHost/Jobs/ConfigModel.cs
Normal file
37
EnvelopeGenerator.ServiceHost/Jobs/ConfigModel.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using DigitalData.Modules.Base;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class ConfigModel : BaseModel
|
||||
{
|
||||
public ConfigModel(State state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public DbConfig LoadConfiguration()
|
||||
{
|
||||
try
|
||||
{
|
||||
const string sql = "SELECT TOP 1 * FROM TBSIG_CONFIG";
|
||||
var table = Database.GetDatatable(sql);
|
||||
var row = table.Rows[0];
|
||||
|
||||
return new DbConfig
|
||||
{
|
||||
DocumentPath = row.ItemEx("DOCUMENT_PATH", string.Empty),
|
||||
DocumentPathOrigin = row.ItemEx("DOCUMENT_PATH", string.Empty),
|
||||
ExportPath = row.ItemEx("EXPORT_PATH", string.Empty),
|
||||
SendingProfile = row.ItemEx("SENDING_PROFILE", 0),
|
||||
SignatureHost = row.ItemEx("SIGNATURE_HOST", string.Empty),
|
||||
ExternalProgramName = row.ItemEx("EXTERNAL_PROGRAM_NAME", string.Empty),
|
||||
Default_Tfa_Enabled = row.ItemEx("DEF_TFA_ENABLED", false),
|
||||
Default_Tfa_WithPhone = row.ItemEx("DEF_TFA_WITH_PHONE", false)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
return new DbConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
13
EnvelopeGenerator.ServiceHost/Jobs/DbConfig.cs
Normal file
13
EnvelopeGenerator.ServiceHost/Jobs/DbConfig.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class DbConfig
|
||||
{
|
||||
public string ExternalProgramName { get; set; } = "signFLOW";
|
||||
public string DocumentPathOrigin { get; set; } = string.Empty;
|
||||
public string DocumentPath { get; set; } = string.Empty;
|
||||
public string ExportPath { get; set; } = string.Empty;
|
||||
public int SendingProfile { get; set; }
|
||||
public string SignatureHost { get; set; } = string.Empty;
|
||||
public bool Default_Tfa_Enabled { get; set; }
|
||||
public bool Default_Tfa_WithPhone { get; set; }
|
||||
}
|
||||
42
EnvelopeGenerator.ServiceHost/Jobs/EnvelopeModel.cs
Normal file
42
EnvelopeGenerator.ServiceHost/Jobs/EnvelopeModel.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using DigitalData.Modules.Base;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class EnvelopeModel : BaseModel
|
||||
{
|
||||
public EnvelopeModel(State state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public Envelope? GetById(int envelopeId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql = $"SELECT * FROM [dbo].[TBSIG_ENVELOPE] WHERE GUID = {envelopeId}";
|
||||
var table = Database.GetDatatable(sql);
|
||||
var row = table.Rows.Cast<System.Data.DataRow>().SingleOrDefault();
|
||||
if (row is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Envelope
|
||||
{
|
||||
Id = row.ItemEx("GUID", 0),
|
||||
Uuid = row.ItemEx("ENVELOPE_UUID", string.Empty),
|
||||
FinalEmailToCreator = row.ItemEx("FINAL_EMAIL_TO_CREATOR", 0),
|
||||
FinalEmailToReceivers = row.ItemEx("FINAL_EMAIL_TO_RECEIVERS", 0),
|
||||
UserId = row.ItemEx("USER_ID", 0),
|
||||
User = null!,
|
||||
EnvelopeReceivers = new List<EnvelopeReceiver>()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
445
EnvelopeGenerator.ServiceHost/Jobs/FinalizeDocument/PDFBurner.cs
Normal file
445
EnvelopeGenerator.ServiceHost/Jobs/FinalizeDocument/PDFBurner.cs
Normal file
@@ -0,0 +1,445 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using DigitalData.Modules.Base;
|
||||
using DigitalData.Modules.Logging;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.Infrastructure;
|
||||
using EnvelopeGenerator.PdfEditor;
|
||||
using EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
using GdPicture14;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class PDFBurner : BaseClass
|
||||
{
|
||||
private readonly AnnotationManager _manager;
|
||||
private readonly LicenseManager _licenseManager;
|
||||
private readonly DbContextOptions<EGDbContext> _dbContextOptions;
|
||||
|
||||
private readonly PDFBurnerParams _pdfBurnerParams;
|
||||
|
||||
public PDFBurner(LogConfig logConfig, string gdPictureLicenseKey, PDFBurnerParams pdfBurnerParams, string connectionString) : base(logConfig)
|
||||
{
|
||||
_licenseManager = new LicenseManager();
|
||||
_licenseManager.RegisterKEY(gdPictureLicenseKey);
|
||||
|
||||
_manager = new AnnotationManager();
|
||||
|
||||
_pdfBurnerParams = pdfBurnerParams;
|
||||
_dbContextOptions = new DbContextOptionsBuilder<EGDbContext>()
|
||||
.UseSqlServer(connectionString)
|
||||
.EnableDetailedErrors()
|
||||
.EnableSensitiveDataLogging()
|
||||
.Options;
|
||||
}
|
||||
|
||||
public byte[] BurnAnnotsToPDF(byte[] sourceBuffer, List<string> instantJsonList, int envelopeId)
|
||||
{
|
||||
using var context = new EGDbContext(_dbContextOptions, Options.Create(new DbTriggerParams()));
|
||||
|
||||
var envelope = context.Envelopes.FirstOrDefault(env => env.Id == envelopeId);
|
||||
if (envelope is null)
|
||||
{
|
||||
throw new BurnAnnotationException($"Envelope with Id {envelopeId} not found.");
|
||||
}
|
||||
|
||||
if (envelope.ReadOnly)
|
||||
{
|
||||
return sourceBuffer;
|
||||
}
|
||||
|
||||
var elements = context.DocumentReceiverElements
|
||||
.Where(sig => sig.Document.EnvelopeId == envelopeId)
|
||||
.Include(sig => sig.Annotations)
|
||||
.ToList();
|
||||
|
||||
return elements.Any()
|
||||
? BurnElementAnnotsToPDF(sourceBuffer, elements)
|
||||
: BurnInstantJSONAnnotsToPDF(sourceBuffer, instantJsonList);
|
||||
}
|
||||
|
||||
public byte[] BurnElementAnnotsToPDF(byte[] sourceBuffer, List<Signature> elements)
|
||||
{
|
||||
using (var doc = Pdf.FromMemory(sourceBuffer))
|
||||
{
|
||||
sourceBuffer = doc.Background(elements, 1.9500000000000002 * 0.93, 2.52 * 0.67).ExportStream().ToArray();
|
||||
|
||||
using var sourceStream = new MemoryStream(sourceBuffer);
|
||||
var result = _manager.InitFromStream(sourceStream);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not open document for burning: [{result}]");
|
||||
}
|
||||
|
||||
var margin = 0.2;
|
||||
var inchFactor = 72d;
|
||||
|
||||
var keys = new[] { "position", "city", "date" };
|
||||
var unitYOffsets = 0.2;
|
||||
var yOffsetsOfFF = keys
|
||||
.Select((k, i) => new { Key = k, Value = unitYOffsets * i + 1 })
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
foreach (var element in elements)
|
||||
{
|
||||
var frameX = element.Left - 0.7 - margin;
|
||||
|
||||
var frame = element.Annotations.FirstOrDefault(a => a.Name == "frame");
|
||||
var frameY = element.Top - 0.5 - margin;
|
||||
var frameYShift = (frame?.Y ?? 0) - frameY * inchFactor;
|
||||
var frameXShift = (frame?.X ?? 0) - frameX * inchFactor;
|
||||
|
||||
foreach (var annot in element.Annotations)
|
||||
{
|
||||
var yOffsetOfFF = yOffsetsOfFF.TryGetValue(annot.Name, out var offset) ? offset : 0;
|
||||
var y = frameY + yOffsetOfFF;
|
||||
|
||||
if (annot.Type == AnnotationType.FormField)
|
||||
{
|
||||
AddFormFieldValue(annot.X / inchFactor, y, annot.Width / inchFactor, annot.Height / inchFactor, element.Page, annot.Value);
|
||||
}
|
||||
else if (annot.Type == AnnotationType.Image)
|
||||
{
|
||||
AddImageAnnotation(
|
||||
annot.X / inchFactor,
|
||||
annot.Name == "signature" ? (annot.Y - frameYShift) / inchFactor : y,
|
||||
annot.Width / inchFactor,
|
||||
annot.Height / inchFactor,
|
||||
element.Page,
|
||||
annot.Value);
|
||||
}
|
||||
else if (annot.Type == AnnotationType.Ink)
|
||||
{
|
||||
AddInkAnnotation(element.Page, annot.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using var newStream = new MemoryStream();
|
||||
result = _manager.SaveDocumentToPDF(newStream);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not save document to stream: [{result}]");
|
||||
}
|
||||
|
||||
_manager.Close();
|
||||
|
||||
return newStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] BurnInstantJSONAnnotsToPDF(byte[] sourceBuffer, List<string> instantJsonList)
|
||||
{
|
||||
using var sourceStream = new MemoryStream(sourceBuffer);
|
||||
var result = _manager.InitFromStream(sourceStream);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not open document for burning: [{result}]");
|
||||
}
|
||||
|
||||
foreach (var json in instantJsonList)
|
||||
{
|
||||
try
|
||||
{
|
||||
AddInstantJSONAnnotationToPDF(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Error in AddInstantJSONAnnotationToPDF - oJson: ");
|
||||
Logger.Warn(json);
|
||||
throw new BurnAnnotationException("Adding Annotation failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
result = _manager.BurnAnnotationsToPage(RemoveInitialAnnots: true, VectorMode: true);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not burn annotations to file: [{result}]");
|
||||
}
|
||||
|
||||
using var newStream = new MemoryStream();
|
||||
result = _manager.SaveDocumentToPDF(newStream);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not save document to stream: [{result}]");
|
||||
}
|
||||
|
||||
_manager.Close();
|
||||
|
||||
return newStream.ToArray();
|
||||
}
|
||||
|
||||
private void AddInstantJSONAnnotationToPDF(string instantJson)
|
||||
{
|
||||
var annotationData = JsonConvert.DeserializeObject<AnnotationData>(instantJson);
|
||||
if (annotationData is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
annotationData.annotations.Reverse();
|
||||
|
||||
foreach (var annotation in annotationData.annotations)
|
||||
{
|
||||
Logger.Debug("Adding AnnotationID: " + annotation.id);
|
||||
|
||||
switch (annotation.type)
|
||||
{
|
||||
case AnnotationType.Image:
|
||||
AddImageAnnotation(annotation, annotationData.attachments);
|
||||
break;
|
||||
case AnnotationType.Ink:
|
||||
AddInkAnnotation(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(annotation, formFieldValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddImageAnnotation(double x, double y, double width, double height, int page, string base64)
|
||||
{
|
||||
_manager.SelectPage(page);
|
||||
_manager.AddEmbeddedImageAnnotFromBase64(base64, (float) x, (float) y, (float) width, (float) height);
|
||||
}
|
||||
|
||||
private void AddImageAnnotation(Annotation annotation, Dictionary<string, Attachment> attachments)
|
||||
{
|
||||
var attachment = attachments.Where(a => a.Key == annotation.imageAttachmentId).SingleOrDefault();
|
||||
|
||||
var bounds = annotation.bbox.Select(ToInches).ToList();
|
||||
|
||||
var x = bounds[0];
|
||||
var y = bounds[1];
|
||||
var width = bounds[2];
|
||||
var height = bounds[3];
|
||||
|
||||
_manager.SelectPage(annotation.pageIndex + 1);
|
||||
_manager.AddEmbeddedImageAnnotFromBase64(attachment.Value.binary, (float) x, (float) y, (float) width, (float) height);
|
||||
}
|
||||
|
||||
private void AddInkAnnotation(int page, string value)
|
||||
{
|
||||
var ink = JsonConvert.DeserializeObject<Ink>(value);
|
||||
if (ink is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var segments = ink.lines.points;
|
||||
var color = ColorTranslator.FromHtml(ink.strokeColor);
|
||||
_manager.SelectPage(page);
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
var points = segment.Select(ToPointF).ToArray();
|
||||
_manager.AddFreeHandAnnot(color, points);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInkAnnotation(Annotation annotation)
|
||||
{
|
||||
var segments = annotation.lines.points;
|
||||
var color = ColorTranslator.FromHtml(annotation.strokeColor);
|
||||
_manager.SelectPage(annotation.pageIndex + 1);
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
var points = segment.Select(ToPointF).ToArray();
|
||||
_manager.AddFreeHandAnnot(color, points);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFormFieldValue(double x, double y, double width, double height, int page, string value)
|
||||
{
|
||||
_manager.SelectPage(page);
|
||||
|
||||
var annot = _manager.AddTextAnnot((float) x, (float) y, (float) width, (float) height, value);
|
||||
annot.FontName = _pdfBurnerParams.FontName;
|
||||
annot.FontSize = _pdfBurnerParams.FontSize;
|
||||
annot.FontStyle = _pdfBurnerParams.FontStyle;
|
||||
_manager.SaveAnnotationsToPage();
|
||||
}
|
||||
|
||||
private void AddFormFieldValue(Annotation annotation, FormFieldValue formFieldValue)
|
||||
{
|
||||
var ffIndex = EGName.Index[annotation.egName];
|
||||
|
||||
var bounds = annotation.bbox.Select(ToInches).ToList();
|
||||
|
||||
var x = bounds[0];
|
||||
var y = bounds[1] + _pdfBurnerParams.YOffset * ffIndex + _pdfBurnerParams.TopMargin;
|
||||
var width = bounds[2];
|
||||
var height = bounds[3];
|
||||
|
||||
_manager.SelectPage(annotation.pageIndex + 1);
|
||||
var annot = _manager.AddTextAnnot((float) x, (float) y, (float) width, (float) height, formFieldValue.value);
|
||||
annot.FontName = _pdfBurnerParams.FontName;
|
||||
annot.FontSize = _pdfBurnerParams.FontSize;
|
||||
annot.FontStyle = _pdfBurnerParams.FontStyle;
|
||||
_manager.SaveAnnotationsToPage();
|
||||
}
|
||||
|
||||
private static PointF ToPointF(List<float> points)
|
||||
{
|
||||
var convertedPoints = points.Select(ToInches).ToList();
|
||||
return new PointF(convertedPoints[0], convertedPoints[1]);
|
||||
}
|
||||
|
||||
private static double ToInches(double value) => value / 72;
|
||||
|
||||
private static float ToInches(float value) => value / 72;
|
||||
|
||||
internal static class AnnotationType
|
||||
{
|
||||
public const string Image = "pspdfkit/image";
|
||||
public const string Ink = "pspdfkit/ink";
|
||||
public const string Widget = "pspdfkit/widget";
|
||||
public const string FormField = "pspdfkit/form-field-value";
|
||||
}
|
||||
|
||||
internal class AnnotationData
|
||||
{
|
||||
public List<Annotation> annotations { get; set; } = new();
|
||||
|
||||
public IEnumerable<List<Annotation>> AnnotationsByReceiver => annotations
|
||||
.Where(annot => annot.hasStructuredID)
|
||||
.GroupBy(a => a.receiverId)
|
||||
.Select(g => g.ToList());
|
||||
|
||||
public IEnumerable<List<Annotation>> UnstructuredAnnotations => annotations
|
||||
.Where(annot => !annot.hasStructuredID)
|
||||
.GroupBy(a => a.receiverId)
|
||||
.Select(g => g.ToList());
|
||||
|
||||
public Dictionary<string, Attachment> attachments { get; set; } = new();
|
||||
public List<FormFieldValue> formFieldValues { get; set; } = new();
|
||||
}
|
||||
|
||||
internal class Annotation
|
||||
{
|
||||
private string? _id;
|
||||
|
||||
public int envelopeId;
|
||||
public int receiverId;
|
||||
public int index;
|
||||
public string egName = EGName.NoName;
|
||||
public bool hasStructuredID;
|
||||
|
||||
public bool isLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(egName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var parts = egName.Split('_');
|
||||
return parts.Length > 1 && parts[1] == "label";
|
||||
}
|
||||
}
|
||||
|
||||
public string id
|
||||
{
|
||||
get => _id ?? string.Empty;
|
||||
set
|
||||
{
|
||||
_id = value;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_id))
|
||||
{
|
||||
throw new BurnAnnotationException("The identifier of annotation is null or empty.");
|
||||
}
|
||||
|
||||
var parts = value.Split('#');
|
||||
|
||||
if (parts.Length != 4)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(parts[0], out envelopeId))
|
||||
{
|
||||
throw new BurnAnnotationException($"The envelope ID of annotation is not integer. Id: {_id}");
|
||||
}
|
||||
|
||||
if (!int.TryParse(parts[1], out receiverId))
|
||||
{
|
||||
throw new BurnAnnotationException($"The receiver ID of annotation is not integer. Id: {_id}");
|
||||
}
|
||||
|
||||
if (!int.TryParse(parts[2], out index))
|
||||
{
|
||||
throw new BurnAnnotationException($"The index of annotation is not integer. Id: {_id}");
|
||||
}
|
||||
|
||||
egName = parts[3];
|
||||
|
||||
hasStructuredID = true;
|
||||
}
|
||||
}
|
||||
|
||||
public List<double> bbox { get; set; } = new();
|
||||
|
||||
public string type { get; set; } = string.Empty;
|
||||
|
||||
public bool isSignature { get; set; }
|
||||
|
||||
public string imageAttachmentId { get; set; } = string.Empty;
|
||||
|
||||
public Lines lines { get; set; } = new();
|
||||
|
||||
public int pageIndex { get; set; }
|
||||
|
||||
public string strokeColor { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
internal class Ink
|
||||
{
|
||||
public Lines lines { get; set; } = new();
|
||||
|
||||
public string strokeColor { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class EGName
|
||||
{
|
||||
public static readonly string NoName = Guid.NewGuid().ToString();
|
||||
public static readonly string Seal = "signature";
|
||||
|
||||
public static readonly ImmutableDictionary<string, int> Index = new Dictionary<string, int>
|
||||
{
|
||||
{ NoName, 0 },
|
||||
{ Seal, 0 },
|
||||
{ "position", 1 },
|
||||
{ "city", 2 },
|
||||
{ "date", 3 }
|
||||
}.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
internal class Lines
|
||||
{
|
||||
public List<List<List<float>>> points { get; set; } = new();
|
||||
}
|
||||
|
||||
internal class Attachment
|
||||
{
|
||||
public string binary { get; set; } = string.Empty;
|
||||
public string contentType { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
internal class FormFieldValue
|
||||
{
|
||||
public string name { get; set; } = string.Empty;
|
||||
public string value { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Drawing;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class PDFBurnerParams
|
||||
{
|
||||
public List<string> IgnoredLabels { get; set; } = 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;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.IO;
|
||||
using DigitalData.Modules.Base;
|
||||
using DigitalData.Modules.Logging;
|
||||
using EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
using GdPicture14;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class PDFMerger : BaseClass
|
||||
{
|
||||
private readonly AnnotationManager _manager;
|
||||
private readonly LicenseManager _licenseManager;
|
||||
|
||||
private const bool AllowRasterization = true;
|
||||
private const bool AllowVectorization = true;
|
||||
|
||||
private readonly PdfConversionConformance _pdfaConformanceLevel = PdfConversionConformance.PDF_A_1b;
|
||||
|
||||
public PDFMerger(LogConfig logConfig, string gdPictureLicenseKey) : base(logConfig)
|
||||
{
|
||||
_licenseManager = new LicenseManager();
|
||||
_licenseManager.RegisterKEY(gdPictureLicenseKey);
|
||||
|
||||
_manager = new AnnotationManager();
|
||||
}
|
||||
|
||||
public byte[] MergeDocuments(byte[] document, byte[] report)
|
||||
{
|
||||
using var documentStream = new MemoryStream(document);
|
||||
using var reportStream = new MemoryStream(report);
|
||||
using var finalStream = new MemoryStream();
|
||||
using var documentPdf = new GdPicturePDF();
|
||||
using var reportPdf = new GdPicturePDF();
|
||||
|
||||
documentPdf.LoadFromStream(documentStream, true);
|
||||
var status = documentPdf.GetStat();
|
||||
if (status != GdPictureStatus.OK)
|
||||
{
|
||||
throw new MergeDocumentException($"Document could not be loaded: {status}");
|
||||
}
|
||||
|
||||
reportPdf.LoadFromStream(reportStream, true);
|
||||
status = reportPdf.GetStat();
|
||||
if (status != GdPictureStatus.OK)
|
||||
{
|
||||
throw new MergeDocumentException($"Report could not be loaded: {status}");
|
||||
}
|
||||
|
||||
var mergedPdf = documentPdf.Merge2Documents(documentPdf, reportPdf);
|
||||
status = mergedPdf.GetStat();
|
||||
if (status != GdPictureStatus.OK)
|
||||
{
|
||||
throw new MergeDocumentException($"Documents could not be merged: {status}");
|
||||
}
|
||||
|
||||
mergedPdf.ConvertToPDFA(finalStream, _pdfaConformanceLevel, AllowVectorization, AllowRasterization);
|
||||
status = documentPdf.GetStat();
|
||||
if (status != GdPictureStatus.OK)
|
||||
{
|
||||
throw new MergeDocumentException($"Document could not be converted to PDF/A: {status}");
|
||||
}
|
||||
|
||||
return finalStream.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using DigitalData.Modules.Base;
|
||||
using DigitalData.Modules.Logging;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
using EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class ReportCreator : BaseClass
|
||||
{
|
||||
private Envelope? _envelope;
|
||||
private readonly ReportModel _reportModel;
|
||||
|
||||
public ReportCreator(LogConfig logConfig, State state) : base(logConfig)
|
||||
{
|
||||
_reportModel = new ReportModel(state);
|
||||
}
|
||||
|
||||
public byte[] CreateReport(Envelope envelope)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Debug("Loading report data..");
|
||||
var table = _reportModel.List(envelope.Id);
|
||||
var items = GetReportSource(table);
|
||||
|
||||
_envelope = envelope;
|
||||
|
||||
if (items.Count == 0)
|
||||
{
|
||||
throw new CreateReportException("No report data found!");
|
||||
}
|
||||
|
||||
Logger.Debug("Creating report with [{0}] items..", items.Count);
|
||||
var buffer = DoCreateReport(items);
|
||||
Logger.Debug("Report created!");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
throw new CreateReportException("Could not prepare report data!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ReportItem> GetReportSource(DataTable dataTable)
|
||||
{
|
||||
Logger.Debug("Preparing report data");
|
||||
return dataTable.Rows
|
||||
.Cast<DataRow>()
|
||||
.Select(ToReportItem)
|
||||
.OrderByDescending(r => r.ItemDate)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private byte[] DoCreateReport(List<ReportItem> reportItems)
|
||||
{
|
||||
var items = reportItems.Select(MergeEnvelope).ToList();
|
||||
var source = new ReportSource { Items = items };
|
||||
var report = new rptEnvelopeHistory { DataSource = source, DataMember = "Items" };
|
||||
|
||||
Logger.Debug("Creating report in memory..");
|
||||
report.CreateDocument();
|
||||
|
||||
Logger.Debug("Exporting report to stream..");
|
||||
using var stream = new MemoryStream();
|
||||
report.ExportToPdf(stream);
|
||||
|
||||
Logger.Debug("Writing report to buffer..");
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private ReportItem MergeEnvelope(ReportItem item)
|
||||
{
|
||||
if (item.Envelope is null)
|
||||
{
|
||||
item.Envelope = _envelope;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private ReportItem ToReportItem(DataRow row)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ReportItem
|
||||
{
|
||||
EnvelopeId = row.ItemEx("ENVELOPE_ID", 0),
|
||||
EnvelopeTitle = row.ItemEx("HEAD_TITLE", string.Empty),
|
||||
EnvelopeSubject = row.ItemEx("HEAD_SUBJECT", string.Empty),
|
||||
ItemDate = row.ItemEx("POS_WHEN", DateTime.MinValue),
|
||||
ItemStatus = (EnvelopeStatus)row.ItemEx("POS_STATUS", 0),
|
||||
ItemUserReference = row.ItemEx("POS_WHO", string.Empty)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
throw new CreateReportException("Could not read data from database!", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.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; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class ReportSource
|
||||
{
|
||||
public List<ReportItem> Items { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.IO;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class rptEnvelopeHistory
|
||||
{
|
||||
public object? DataSource { get; set; }
|
||||
public string? DataMember { get; set; }
|
||||
|
||||
public void CreateDocument()
|
||||
{
|
||||
}
|
||||
|
||||
public void ExportToPdf(Stream stream)
|
||||
{
|
||||
stream.Write(Array.Empty<byte>(), 0, 0);
|
||||
}
|
||||
}
|
||||
432
EnvelopeGenerator.ServiceHost/Jobs/FinalizeDocumentJob.cs
Normal file
432
EnvelopeGenerator.ServiceHost/Jobs/FinalizeDocumentJob.cs
Normal file
@@ -0,0 +1,432 @@
|
||||
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<string> 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<DataRow>()
|
||||
.Select(r => r.Field<int>("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<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?.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<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?.Debug("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
|
||||
};
|
||||
}
|
||||
}
|
||||
63
EnvelopeGenerator.ServiceHost/Jobs/Infrastructure/Base.cs
Normal file
63
EnvelopeGenerator.ServiceHost/Jobs/Infrastructure/Base.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Data;
|
||||
using DigitalData.Modules.Logging;
|
||||
|
||||
namespace DigitalData.Modules.Base;
|
||||
|
||||
public abstract class BaseClass
|
||||
{
|
||||
protected BaseClass(LogConfig logConfig)
|
||||
{
|
||||
LogConfig = logConfig;
|
||||
Logger = logConfig.GetLogger();
|
||||
}
|
||||
|
||||
protected LogConfig LogConfig { get; }
|
||||
protected Logger Logger { get; }
|
||||
}
|
||||
|
||||
public static class ObjectEx
|
||||
{
|
||||
public static T ToEnum<T>(object value) where T : struct, Enum
|
||||
{
|
||||
if (value is T enumValue)
|
||||
{
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
if (value is string stringValue && Enum.TryParse<T>(stringValue, true, out var parsed))
|
||||
{
|
||||
return parsed;
|
||||
}
|
||||
|
||||
if (int.TryParse(Convert.ToString(value), out var intValue))
|
||||
{
|
||||
return (T)Enum.ToObject(typeof(T), intValue);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DataRowExtensions
|
||||
{
|
||||
public static T ItemEx<T>(this DataRow row, string columnName, T defaultValue)
|
||||
{
|
||||
if (!row.Table.Columns.Contains(columnName))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var value = row[columnName];
|
||||
if (value is DBNull or null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
public static string ItemEx(this DataRow row, string columnName, string defaultValue)
|
||||
{
|
||||
return ItemEx<string>(row, columnName, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Data;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using DigitalData.Modules.Logging;
|
||||
|
||||
namespace DigitalData.Modules.Database;
|
||||
|
||||
public class MSSQLServer
|
||||
{
|
||||
private readonly LogConfig _logConfig;
|
||||
private readonly string _connectionString;
|
||||
|
||||
public MSSQLServer(LogConfig logConfig, string connectionString)
|
||||
{
|
||||
_logConfig = logConfig;
|
||||
_connectionString = connectionString;
|
||||
}
|
||||
|
||||
public static string DecryptConnectionString(string connectionString) => connectionString;
|
||||
|
||||
public SqlConnection GetConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
var connection = new SqlConnection(_connectionString);
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
public DataTable GetDatatable(string sql)
|
||||
{
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
using var command = new SqlCommand(sql, connection);
|
||||
using var adapter = new SqlDataAdapter(command);
|
||||
var table = new DataTable();
|
||||
adapter.Fill(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
public object? GetScalarValue(string sql)
|
||||
{
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
using var command = new SqlCommand(sql, connection);
|
||||
connection.Open();
|
||||
return command.ExecuteScalar();
|
||||
}
|
||||
|
||||
public bool ExecuteNonQuery(string sql)
|
||||
{
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
using var command = new SqlCommand(sql, connection);
|
||||
connection.Open();
|
||||
return command.ExecuteNonQuery() > 0;
|
||||
}
|
||||
|
||||
public bool ExecuteNonQuery(SqlCommand command)
|
||||
{
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
command.Connection = connection;
|
||||
connection.Open();
|
||||
return command.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
26
EnvelopeGenerator.ServiceHost/Jobs/Infrastructure/Logging.cs
Normal file
26
EnvelopeGenerator.ServiceHost/Jobs/Infrastructure/Logging.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace DigitalData.Modules.Logging;
|
||||
|
||||
public class LogConfig
|
||||
{
|
||||
public bool Debug { get; set; }
|
||||
|
||||
public Logger GetLogger() => new();
|
||||
}
|
||||
|
||||
public class Logger
|
||||
{
|
||||
public void Debug(string message, params object?[] args) => Write("DEBUG", message, args);
|
||||
public void Info(string message, params object?[] args) => Write("INFO", message, args);
|
||||
public void Warn(string message, params object?[] args) => Write("WARN", message, args);
|
||||
public void Warn(Exception exception, string message, params object?[] args) => Write("WARN", message + " " + exception.Message, args);
|
||||
public void Error(Exception exception) => Write("ERROR", exception.Message, Array.Empty<object?>());
|
||||
public void Error(Exception exception, string message, params object?[] args) => Write("ERROR", message + " " + exception.Message, args);
|
||||
|
||||
private static void Write(string level, string message, params object?[] args)
|
||||
{
|
||||
var formatted = args.Length > 0 ? string.Format(CultureInfo.InvariantCulture, message, args) : message;
|
||||
Console.WriteLine($"[{level}] {formatted}");
|
||||
}
|
||||
}
|
||||
16
EnvelopeGenerator.ServiceHost/Jobs/ReportModel.cs
Normal file
16
EnvelopeGenerator.ServiceHost/Jobs/ReportModel.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Data;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class ReportModel : BaseModel
|
||||
{
|
||||
public ReportModel(State state) : base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public DataTable List(int envelopeId)
|
||||
{
|
||||
var sql = $"SELECT * FROM VWSIG_ENVELOPE_REPORT WHERE ENVELOPE_ID = {envelopeId}";
|
||||
return Database.GetDatatable(sql);
|
||||
}
|
||||
}
|
||||
15
EnvelopeGenerator.ServiceHost/Jobs/State.cs
Normal file
15
EnvelopeGenerator.ServiceHost/Jobs/State.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using DigitalData.Modules.Database;
|
||||
using DigitalData.Modules.Logging;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class State
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public FormUser? User { get; set; }
|
||||
public Config? Config { get; set; }
|
||||
public DbConfig? DbConfig { get; set; }
|
||||
public LogConfig? LogConfig { get; set; }
|
||||
public MSSQLServer? Database { get; set; }
|
||||
}
|
||||
72
EnvelopeGenerator.ServiceHost/Jobs/TempFiles.cs
Normal file
72
EnvelopeGenerator.ServiceHost/Jobs/TempFiles.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.IO;
|
||||
using DigitalData.Modules.Base;
|
||||
using DigitalData.Modules.Logging;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class TempFiles : BaseClass
|
||||
{
|
||||
public string TempPath { get; }
|
||||
|
||||
public TempFiles(LogConfig logConfig) : base(logConfig)
|
||||
{
|
||||
var tempDirectoryPath = Path.GetTempPath();
|
||||
TempPath = Path.Combine(tempDirectoryPath, "EnvelopeGenerator");
|
||||
}
|
||||
|
||||
public bool Create()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(TempPath))
|
||||
{
|
||||
Directory.CreateDirectory(TempPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
CleanUpFiles();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CleanUpFiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var fileItem in Directory.GetFiles(TempPath))
|
||||
{
|
||||
Logger.Debug("Deleting tempPath-file: {0} ...", fileItem);
|
||||
File.Delete(fileItem);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CleanUp()
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Debug("Deleting tempPath-Data: {0} ...", TempPath);
|
||||
Directory.Delete(TempPath, true);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user