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