diff --git a/EnvelopeGenerator.Common/EnvelopeGenerator.Common.vbproj b/EnvelopeGenerator.Common/EnvelopeGenerator.Common.vbproj
index fb3a52fb..2efb05ef 100644
--- a/EnvelopeGenerator.Common/EnvelopeGenerator.Common.vbproj
+++ b/EnvelopeGenerator.Common/EnvelopeGenerator.Common.vbproj
@@ -137,6 +137,7 @@
+
@@ -267,5 +268,6 @@
PreserveNewest
+
\ No newline at end of file
diff --git a/EnvelopeGenerator.Common/Jobs/CertificateDocumentJob.vb b/EnvelopeGenerator.Common/Jobs/CertificateDocumentJob.vb
index 74730f0f..9290cf9d 100644
--- a/EnvelopeGenerator.Common/Jobs/CertificateDocumentJob.vb
+++ b/EnvelopeGenerator.Common/Jobs/CertificateDocumentJob.vb
@@ -1,72 +1,231 @@
Imports DigitalData.Modules.Database
Imports DigitalData.Modules.Logging
+Imports DigitalData.Modules.Base
Imports GdPicture14
Imports Quartz
Imports Quartz.Util
+Imports DevExpress.XtraRichEdit.Keyboard
+Imports DevExpress.Pdf
+Imports System.Security.Cryptography
+Imports DevExpress
+Imports System.IO
-Public Class CertificateDocumentJob
- Implements IJob
+Namespace Jobs
+ Public Class CertificateDocumentJob
+ Implements IJob
- Private LicenseManager As New LicenseManager()
- Private GdViewer As GdViewer
+ Private LicenseManager As New LicenseManager()
+ Private GdViewer As GdViewer
- Private LogConfig As LogConfig
- Private Logger As Logger
- Private Database As MSSQLServer
- Private Config As DbConfig
+ Private ConfigModel As ConfigModel
+ Private EnvelopeModel As EnvelopeModel
- Public Function Execute(pContext As IJobExecutionContext) As Task Implements IJob.Execute
- Dim oGdPictureKey As String = pContext.MergedJobDataMap.Item(Constants.GDPICTURE)
- LogConfig = pContext.MergedJobDataMap.Item(Constants.LOGCONFIG)
- Logger = LogConfig.GetLogger()
+ Private LogConfig As LogConfig
+ Private Logger As Logger
+ Private Database As MSSQLServer
+ Private Config As DbConfig
- Try
- Logger.Info("Loading GdViewer..")
- GdViewer = New GdViewer()
- LicenseManager.RegisterKEY(oGdPictureKey)
+ Private PDFBurner As PDFBurner
- Logger.Info("Loading Database..")
- Database = GetDatabase(pContext, LogConfig)
+ Private Class EnvelopeData
+ Public EnvelopeId As Integer
+ Public DocumentPath As String
+ Public AnnotationData As List(Of String)
- Logger.Info("Loading Configuration..")
- Config = GetDbConfig(Database)
+ End Class
- Dim JobId = pContext.JobDetail.Key
- Logger.Info("Starting job {0}", JobId)
+ Public Async Function Execute(pContext As IJobExecutionContext) As Task Implements IJob.Execute
+ Dim oGdPictureKey As String = pContext.MergedJobDataMap.Item(Constants.GDPICTURE)
+ LogConfig = pContext.MergedJobDataMap.Item(Constants.LOGCONFIG)
+ Logger = LogConfig.GetLogger()
- Dim oCompleteStatus As Integer = Constants.EnvelopeStatus.EnvelopeCompletelySigned
- Dim oSql = $"SELECT * FROM TBSIG_ENVELOPE WHERE STATUS = {oCompleteStatus}"
- Dim oTable = Database.GetDatatable(oSql)
+ Try
+ Logger.Debug("Loading GdViewer..")
+ GdViewer = New GdViewer()
+ LicenseManager.RegisterKEY(oGdPictureKey)
- Logger.Info("Found [{0}] completed envelopes.", oTable.Rows.Count)
+ Logger.Debug("Loading PDFBurner..")
+ PDFBurner = New PDFBurner(LogConfig, oGdPictureKey)
- ' Do important work...
+ Logger.Debug("Loading Database..")
+ Database = GetDatabase(pContext, LogConfig)
- Logger.Info("Completed job {0}", JobId)
- Return Task.FromResult(True)
+ Logger.Debug("Loading Configuration..")
+ Config = ConfigModel.LoadConfiguration()
- Catch ex As Exception
- Logger.Warn("Certificate Document job failed!")
- Logger.Error(ex)
+ Dim JobId = pContext.JobDetail.Key
+ Logger.Debug("Starting job {0}", JobId)
- Return Task.FromException(ex)
- End Try
- End Function
+ Dim oCompleteStatus As Integer = Constants.EnvelopeStatus.EnvelopeCompletelySigned
+ Dim oSql = $"SELECT * FROM TBSIG_ENVELOPE WHERE STATUS = {oCompleteStatus}"
+ Dim oTable = Database.GetDatatable(oSql)
+ Dim oEnvelopeIds As List(Of Integer) = oTable.Rows.Cast(Of DataRow).
+ Select(Function(r) r.Item("GUID")).
+ Cast(Of Integer).
+ ToList()
- Private Function GetDbConfig(pDatabase) As DbConfig
- Dim oState As New State With {.Database = pDatabase}
- Dim oConfigModel = New ConfigModel(oState)
+ Logger.Info("Found [{0}] completed envelopes.", oEnvelopeIds.Count)
- Return oConfigModel.LoadConfiguration()
- End Function
+ For Each oId In oEnvelopeIds
+
+ Logger.Info("Finalizing Envelope [{0}]", oId)
+ Dim oEnvelopeData = GetEnvelopeData(oId)
+
+ If oEnvelopeData Is Nothing Then
+ Logger.Warn("EnvelopeData could not be loaded for Envelope [{0}]!", oId)
+ End If
+
+ GenerateFinalPDF(oEnvelopeData)
+ Await GenerateReportPdf()
+
+ Next
+
+ Logger.Debug("Completed job {0}", JobId)
+
+ Catch ex As Exception
+ Logger.Warn("Certificate Document job failed!")
+ Logger.Error(ex)
+ End Try
+ End Function
+
+ Private Function MergeDocuments(pDocumentPath As String, pReport As Byte())
+
+ Using oGdPicturePDF As New GdPicturePDF()
+ Using oGdPicturePDFReport As New GdPicturePDF()
+ Using oStream As New MemoryStream(pReport)
+
+ ' Load the source file into memory
+ If oGdPicturePDF.LoadFromFile(pDocumentPath, True) <> GdPictureStatus.OK Then
+ Throw New ApplicationException("Document could not be loaded!")
+ End If
+
+ ' Load the report file into memory
+ If oGdPicturePDFReport.LoadFromStream(oStream, True) <> GdPictureStatus.OK Then
+ Throw New ApplicationException("Report could not be loaded!")
+ End If
+
+ If oGdPicturePDF.ClonePages(oGdPicturePDFReport, "*") = GdPictureStatus.OK Then
+ Throw New ApplicationException("Report could not be loaded!")
+ End If
+
+ End Using
+ End Using
+ End Using
+
+ End Function
+
+ Private Function GenerateFinalPDF(pData As EnvelopeData) As Boolean
+ Dim pEnvelopeId = pData.EnvelopeId
+
+ Logger.Info("Burning [{0}] signatures", pData.AnnotationData.Count)
+ Dim oAnnotations = pData.AnnotationData
+ Dim oInputPath = pData.DocumentPath
+ Dim oOutputPath = Config.ExportPath
+ Logger.Info("Input path: [{0}]", oInputPath)
+ Logger.Info("Output path: [{0}]", oOutputPath)
+
+ Dim oBurnResult = PDFBurner.BurnInstantJSONAnnotationsToPDF(oInputPath, oAnnotations, oOutputPath)
+
+ If oBurnResult = False Then
+ Logger.Warn("PDF Could not be burned for Envelope [{0}]!", pEnvelopeId)
+ Return False
+ End If
+
+ Return True
+ End Function
+
+ Private Async Function GenerateReportPdf(pEnvelopeId As Integer) As Task(Of Boolean)
+ Dim oSql As String = $"SELECT * FROM VWSIG_ENVELOPE_REPORT WHERE ENVELOPE_ID = {pEnvelopeId}"
+ Dim oTable As DataTable = Database.GetDatatable(oSql)
+ Dim oItems = GetReportSource(oTable)
+
+ If oItems.Count = 0 Then
+ Return False
+ End If
+
+ Dim oState As New State() With {
+ .Database = Database,
+ .LogConfig = LogConfig
+ }
+ EnvelopeModel = New EnvelopeModel(oState)
+ Dim oEnvelope = EnvelopeModel.GetById(pEnvelopeId)
+
+ Dim oCreator As New ReportCreator(oEnvelope)
+ Dim oBuffer = Await oCreator.CreateReport(oItems)
+
+ Return
+ End Function
+
+ Private Function GetReportSource(pDataTable As DataTable) As List(Of ReportItem)
+ Return pDataTable.Rows.
+ Cast(Of DataRow).
+ Select(AddressOf ToReportItem).
+ OrderByDescending(Function(r) r.ItemDate).
+ ToList()
+ End Function
+
+ Private Function GetEnvelopeData(pEnvelopeId As Integer) As EnvelopeData
+ Dim oSql = $"SELECT T.GUID, T2.FILEPATH FROM [dbo].[TBSIG_ENVELOPE] T
+ JOIN TBSIG_ENVELOPE_DOCUMENT T2 ON T.GUID = T2.ENVELOPE_ID
+ WHERE T.GUID = {pEnvelopeId}"
+ Dim oTable As DataTable = Database.GetDatatable(oSql)
+ Dim oRow As DataRow = oTable.Rows.Cast(Of DataRow).SingleOrDefault()
+ If oRow Is Nothing Then
+ Return Nothing
+ End If
+
+ Dim oAnnotationData = GetAnnotationData(pEnvelopeId)
+ Dim oData As New EnvelopeData With {
+ .EnvelopeId = pEnvelopeId,
+ .DocumentPath = oRow.ItemEx("FILEPATH", ""),
+ .AnnotationData = oAnnotationData
+ }
+
+ Return oData
+ End Function
+ Private Function GetAnnotationData(pEnvelopeId As Integer) As List(Of String)
+ Dim oSql = $"SELECT VALUE FROM TBSIG_DOCUMENT_STATUS WHERE ENVELOPE_ID = {pEnvelopeId}"
+ Dim oTable As DataTable = Database.GetDatatable(oSql)
+
+ Return oTable.Rows.Cast(Of DataRow).
+ Select(Function(r) r.ItemEx("VALUE", String.Empty)).
+ Cast(Of String).
+ ToList()
+
+ End Function
+
+ Private Function ToReportItem(pRow As DataRow) As ReportItem
+ Return New ReportItem() With {
+ .EnvelopeId = pRow.Item("ENVELOPE_ID"),
+ .EnvelopeTitle = pRow.ItemEx("HEAD_TITLE", String.Empty),
+ .EnvelopeSubject = pRow.ItemEx("HEAD_SUBJECT", String.Empty),
+ .ItemDate = pRow.ItemEx(Of Date)("POS_WHEN", Nothing),
+ .ItemStatus = pRow.ItemEx("POS_STATUS", 0),
+ .ItemUserReference = pRow.ItemEx("POS_WHO", "")
+ }
+ End Function
- Private Function GetDatabase(pContext As IJobExecutionContext, pLogConfig As LogConfig) As MSSQLServer
- Dim oConnectionString As String = pContext.MergedJobDataMap.Item(Constants.DATABASE)
- Dim Database = New MSSQLServer(pLogConfig, MSSQLServer.DecryptConnectionString(oConnectionString))
+ Private Sub InitializeModels()
+ Dim oState = GetState()
+ ConfigModel = New ConfigModel(oState)
+ EnvelopeModel = New EnvelopeModel(oState)
+ End Sub
- Return Database
- End Function
-End Class
+ Private Function GetDatabase(pContext As IJobExecutionContext, pLogConfig As LogConfig) As MSSQLServer
+ Dim oConnectionString As String = pContext.MergedJobDataMap.Item(Constants.DATABASE)
+ Dim Database = New MSSQLServer(pLogConfig, MSSQLServer.DecryptConnectionString(oConnectionString))
+
+ Return Database
+ End Function
+
+ Private Function GetState() As State
+ Return New State() With {
+ .LogConfig = LogConfig,
+ .Database = Database
+ }
+ End Function
+ End Class
+End Namespace
diff --git a/EnvelopeGenerator.Common/Jobs/PDFBurner.vb b/EnvelopeGenerator.Common/Jobs/PDFBurner.vb
new file mode 100644
index 00000000..9a06bb1b
--- /dev/null
+++ b/EnvelopeGenerator.Common/Jobs/PDFBurner.vb
@@ -0,0 +1,163 @@
+Imports DevExpress.Pdf
+Imports DigitalData.Modules.Base
+Imports DigitalData.Modules.Logging
+Imports GdPicture14
+Imports Newtonsoft.Json
+
+Namespace Jobs
+ Public Class PDFBurner
+ Inherits BaseClass
+
+ Private ReadOnly LicenseKey As String
+ Private ReadOnly Manager As AnnotationManager
+ Private ReadOnly LicenseManager As LicenseManager
+
+ Private Const ANNOTATION_TYPE_IMAGE = "pspdfkit/image"
+ Private Const ANNOTATION_TYPE_INK = "pspdfkit/ink"
+
+ Public Sub New(pLogConfig As LogConfig, pGDPictureLicenseKey As String)
+ MyBase.New(pLogConfig)
+
+ LicenseKey = pGDPictureLicenseKey
+ LicenseManager = New LicenseManager()
+ LicenseManager.RegisterKEY(pGDPictureLicenseKey)
+
+ Manager = New AnnotationManager()
+ End Sub
+
+ Public Function BurnInstantJSONAnnotationsToPDF(pSourcePath As String, pInstantJSONList As List(Of String), pDestinationPath As String) As Boolean
+ If Manager.InitFromFile(pSourcePath) <> GdPictureStatus.OK Then
+ Logger.Warn("Could not open file [{0}] for burning.", pSourcePath)
+ Return False
+ End If
+
+ For Each oJSON In pInstantJSONList
+ If AddInstantJSONAnnotationToPDF(oJSON) = False Then
+ Logger.Warn("Adding Annotation failed. Exiting")
+ Return False
+ End If
+ Next
+
+ Try
+ Manager.BurnAnnotationsToPage(RemoveInitialAnnots:=True, VectorMode:=True)
+ Manager.SaveDocumentToPDF(pDestinationPath)
+ Manager.Close()
+
+ Return True
+ Catch ex As Exception
+ Logger.Warn("Could not burn and save annotations to file [{0}]!", pDestinationPath)
+ Logger.Error(ex)
+
+ Return False
+ End Try
+ End Function
+
+ Private Function AddInstantJSONAnnotationToPDF(pInstantJSON As String) As Boolean
+ Try
+ Dim oAnnotationData = JsonConvert.DeserializeObject(Of AnnotationData)(pInstantJSON)
+
+ For Each oAnnotation In oAnnotationData.annotations
+ Select Case oAnnotation.type
+ Case ANNOTATION_TYPE_IMAGE
+ AddImageAnnotation(oAnnotation, oAnnotationData.attachments)
+ Case ANNOTATION_TYPE_INK
+ AddInkAnnotation(oAnnotation)
+ End Select
+ Next
+
+ Return True
+ Catch ex As Exception
+ Logger.Warn("Could not create annotation from InstantJSON")
+ Logger.Error(ex)
+ Return False
+ End Try
+ End Function
+
+ Private Function AddImageAnnotation(pAnnotation As Annotation, pAttachments As Dictionary(Of String, Attachment)) As Boolean
+ Try
+ Dim oAttachment = pAttachments.Where(Function(a) a.Key = pAnnotation.imageAttachmentId).
+ SingleOrDefault()
+
+ ' Convert pixels to Inches
+ Dim oBounds = pAnnotation.bbox.Select(AddressOf ToInches).ToList()
+
+ Dim oX = oBounds.Item(0)
+ Dim oY = oBounds.Item(1)
+ Dim oWidth = oBounds.Item(2)
+ Dim oHeight = oBounds.Item(3)
+
+ Manager.AddEmbeddedImageAnnotFromBase64(oAttachment.Value.binary, oX, oY, oWidth, oHeight)
+
+ Return True
+ Catch ex As Exception
+ Logger.Warn("Could not add image annotation!")
+ Logger.Error(ex)
+
+ Return False
+ End Try
+ End Function
+
+ Private Function AddInkAnnotation(pAnnotation As Annotation) As Boolean
+ Try
+ Dim oSegments = pAnnotation.lines.points
+ Dim oColor = ColorTranslator.FromHtml(pAnnotation.strokeColor)
+ Manager.SelectPage(pAnnotation.pageIndex)
+
+ For Each oSegment As List(Of List(Of Single)) In oSegments
+ Dim oPoints = oSegment.
+ Select(AddressOf ToPointF).
+ ToArray()
+
+
+ Manager.AddFreeHandAnnot(oColor, oPoints)
+ Next
+
+ Return True
+ Catch ex As Exception
+ Logger.Warn("Could not add image annotation!")
+ Logger.Error(ex)
+
+ Return False
+ End Try
+
+ End Function
+
+ Private Function ToPointF(pPoints As List(Of Single)) As PointF
+ Dim oPoints = pPoints.Select(AddressOf ToInches).ToList()
+ Return New PointF(oPoints.Item(0), oPoints.Item(1))
+ End Function
+
+ Private Function ToInches(pValue As Double) As Double
+ Return pValue / 72
+ End Function
+
+ Private Function ToInches(pValue As Single) As Single
+ Return pValue / 72
+ End Function
+
+ Friend Class AnnotationData
+ Public Property annotations As List(Of Annotation)
+ Public Property attachments As Dictionary(Of String, Attachment)
+ End Class
+
+ Friend Class Annotation
+ Public Property id As String
+ Public Property bbox As List(Of Double)
+ Public Property type As String
+ Public Property isSignature As Boolean
+ Public Property imageAttachmentId As String
+ Public Property lines As Lines
+ Public Property pageIndex As Integer
+ Public Property strokeColor As String
+ End Class
+
+ Friend Class Lines
+ Public Property points As List(Of List(Of List(Of Single)))
+ End Class
+
+ Friend Class Attachment
+ Public Property binary As String
+ Public Property contentType As String
+ End Class
+ End Class
+End Namespace