Imports System.Collections.Generic Imports System.Data Imports System.Data.SqlClient Imports System.IO Imports System.Linq Imports DigitalData.Modules.Base Imports DigitalData.Modules.Config Imports DigitalData.Modules.Database Imports DigitalData.Modules.Interfaces Imports DigitalData.Modules.Interfaces.Exceptions Imports DigitalData.Modules.Jobs.Exceptions Imports DigitalData.Modules.Logging Public Class ImportZUGFeRDFiles Implements IJob Public Const ZUGFERD_IN = "ZUGFeRD in" Public Const ZUGFERD_ERROR = "ZUGFeRD Error" Public Const ZUGFERD_SUCCESS = "ZUGFeRD Success" Public Const ZUGFERD_EML = "ZUGFeRD Eml" Public Const ZUGFERD_REJECTED_EML = "ZUGFeRD Eml Rejected" Public Const ZUGFERD_ATTACHMENTS = "ZUGFeRD Attachments" Public Const ZUGFERD_NO_ZUGFERD = "Non-ZUGFeRD Files" Public HISTORY_ID As Integer Private Const DIRECTORY_DONT_MOVE = "DIRECTORY_DONT_MOVE" ' List of allowed extensions for PDF/A Attachments ' This list should not contain xml so the zugferd xml file will be filtered out Private ReadOnly AllowedExtensions As New List(Of String) From {"docx", "doc", "pdf", "xls", "xlsx", "ppt", "pptx", "txt"} Private ReadOnly _logger As Logger Private ReadOnly _logConfig As LogConfig Private ReadOnly _filesystem As FilesystemEx Private ReadOnly _mssql As MSSQLServer Private ReadOnly _email As ZUGFeRD.EmailFunctions Private ReadOnly _file As ZUGFeRD.FileFunctions Private ReadOnly _history As ZUGFeRD.HistoryFunctions Private ReadOnly _hash As HashFunctions Private ReadOnly _embeds As PDFEmbeds Private SQL_xRechnung_ItemTemplate As String = "" Private ReadOnly _gdpictureLicenseKey As String Private ReadOnly _xRechnungCreator As XRechnungViewDocument Private _zugferd As ZUGFeRDInterface Private _EmailOutAccountId As Integer Private MyTemplateValues_xInvDT As DataTable Private Class ProcessFileResult Public ZugferdFileFound As Boolean = False Public ZugferdFileCount As Integer = 0 Public MD5Checksum As String = Nothing Public EmailAttachmentFiles As New List(Of FileInfo) Public EmbeddedAttachmentFiles As New List(Of PDFEmbeds.EmbeddedFile) End Class Private Class DatabaseConnections Public Property SQLServerConnection As SqlConnection Public Property SQLServerTransaction As SqlTransaction End Class Public Sub New(LogConfig As LogConfig, Optional MSSQL As MSSQLServer = Nothing) _logConfig = LogConfig _logger = LogConfig.GetLogger() _filesystem = New FilesystemEx(_logConfig) _mssql = MSSQL _email = New ZUGFeRD.EmailFunctions(LogConfig, _mssql) _file = New ZUGFeRD.FileFunctions(LogConfig, _mssql) _history = New ZUGFeRD.HistoryFunctions(LogConfig, _mssql) _embeds = New PDFEmbeds(LogConfig) _hash = New HashFunctions(_logConfig, _mssql) _xRechnungCreator = New XRechnungViewDocument(_logConfig, _mssql, _gdpictureLicenseKey) _logger.Debug("Registering GDPicture License") If _mssql IsNot Nothing Then _gdpictureLicenseKey = ConfigDbFunct.GetProductLicense("GDPICTURE", "11.2024", _logConfig, _mssql.CurrentConnectionString) Else _logger.Warn("GDPicture License could not be registered! MSSQL is not enabled!") Throw New ArgumentNullException("MSSQL") End If End Sub Public Sub Start(Arguments As Object) Implements IJob.Start Dim oArgs As WorkerArgs = Arguments Dim oAttachmentExtractor = New PDFEmbeds(_logConfig) _EmailOutAccountId = oArgs.EmailOutProfileId _zugferd = New ZUGFeRDInterface(_logConfig, _gdpictureLicenseKey, New ZUGFeRDInterface.ZugferdOptions() With { .AllowFacturX_Filename = oArgs.AllowFacturX, .AllowXRechnung_Filename = oArgs.AllowXRechnung, .AllowPeppol_3017_Schema = oArgs.AllowPeppolBISBill3x }) _logger.Debug("Starting Job {0}", [GetType].Name) If oArgs.AllowXRechnung Then ' TODO - Config-Schalter hat NIX mit XRechnung-Dateien u. Sichtbelegen zu tun, sondern nur mit Dateinamen zu tun. Dim oSQL = "SELECT SQL_COMMAND FROM TBDD_SQL_COMMANDS WHERE TITLE = 'VWDD_ZUGFERD_VIEW_RECEIPT_TEMPLATE_ITEMS'" SQL_xRechnung_ItemTemplate = _mssql.GetScalarValue(oSQL) End If Try 'For Each oPath As String In oArgs.WatchDirectory Dim oPath As String = oArgs.WatchDirectory Dim oDirInfo As New DirectoryInfo(oPath) _logger.Debug($"Start processing directory {oDirInfo.FullName}") If oDirInfo.Exists = False Then _logger.Warn("Watch directory exists. Exiting.") Exit Sub End If ' Filter out *.lock files Dim oFiles As List(Of FileInfo) = oDirInfo. GetFiles(). Where(Function(f) Not f.Name.EndsWith(".lock")). ToList() If oFiles.Count = 0 Then _logger.Debug("No files to process. Exiting.") Exit Sub End If _logger.Info("Found {0} files", oFiles.Count) ' Group files by messageId Dim oGrouped As Dictionary(Of String, List(Of FileInfo)) = _zugferd.FileGroup.GroupFiles(oFiles) _logger.Info("Found [{0}] file groups", oGrouped.Count) 'Process each file group together 'oGrouped equals one e-invoice For Each oFileGroup In oGrouped ' Start a new transaction for each file group. ' This way we can rollback database changes for the whole filegroup in case something goes wrong. Dim oSQLConnection As SqlConnection = _mssql.GetConnection() Dim oSQLTransaction As SqlTransaction = oSQLConnection?.BeginTransaction() Dim oConnections As New DatabaseConnections() With { .SQLServerConnection = oSQLConnection, .SQLServerTransaction = oSQLTransaction } ' Count the amount of ZUGFeRD files Dim oZUGFeRDCount As Integer = 0 ' Set the default Move Directory Dim oMoveDirectory As String = oArgs.ErrorDirectory ' Flag to save if the whole process was a success. ' Will be set only at the end of the function if no error occurred. Dim oIsSuccess As Boolean = False ' Flag to save if the occurred error (if any) was expected ' Used to determine if transactions should be committed or not Dim oExpectedError As Boolean = True ' Create file lists Dim oEInvoiceFileGroup As List(Of FileInfo) = oFileGroup.Value Dim oEmailAttachmentFiles As New List(Of FileInfo) Dim oEmbeddedAttachmentFiles As New List(Of PDFEmbeds.EmbeddedFile) Dim oMessageId As String = oFileGroup.Key Dim oMD5CheckSum As String = String.Empty _logger.Info("START processing file group {0}", oMessageId) If _file.CheckFileAge(oEInvoiceFileGroup, oArgs.MinFileAgeInMinutes) Then _logger.Info("At least one file was created less than [{0}] minutes ago. Skipping file group.", oArgs.MinFileAgeInMinutes) Continue For End If Dim oEmailDataBase = _email.GetEmailDataForMessageId(oMessageId) Dim oFileCounter As Integer = 0 Try For Each oFile In oEInvoiceFileGroup oFileCounter += 1 Dim oResult As ProcessFileResult If oFileCounter = 1 AndAlso oFile.Name.ToUpper.EndsWith(".XML") Then oResult = ProcessXMLFile(oMessageId, oZUGFeRDCount, oFile, oConnections, oArgs) Else oResult = ProcessFile(oMessageId, oZUGFeRDCount, oFile, oConnections, oArgs) End If If oResult.ZugferdFileFound = True Then _logger.Debug("Zugferd File found") oMD5CheckSum = oResult.MD5Checksum oZUGFeRDCount = oResult.ZugferdFileCount oEmailAttachmentFiles.AddRange(oResult.EmailAttachmentFiles) oEmbeddedAttachmentFiles.AddRange(oResult.EmbeddedAttachmentFiles) Else ' No zugferd found! oEmailAttachmentFiles.AddRange(oResult.EmailAttachmentFiles) oEmbeddedAttachmentFiles.AddRange(oResult.EmbeddedAttachmentFiles) End If Next 'Check if there are no ZUGFeRD files If oZUGFeRDCount = 0 Then ' If NonZugferdDirectory is not set, a NoFerdsException will be thrown and a rejection will be generated ' This is the default/initial behaviour. If String.IsNullOrEmpty(oArgs.NonZugferdDirectory) Then Throw New NoFerdsException() End If ' Also, if the directory is set but does not exist, still a rejection will be generated. If Not Directory.Exists(oArgs.NonZugferdDirectory) Then Throw New NoFerdsException() End If ' Only if the directory is set and does exist, it will be used and any file groups which ' do NOT CONTAIN ANY ZUGFERD DOCUMENTS, are moved to that directory. Throw New NoFerdsAlternateException() End If 'If no errors occurred, update the history table for this MessageId If String.IsNullOrEmpty(oMD5CheckSum) Then _history.Update_HistoryEntry(oMessageId, String.Empty, "SUCCESS (with empty MD5Hash)") Else _history.Update_HistoryEntry(oMessageId, oMD5CheckSum, "SUCCESS") End If oIsSuccess = True oMoveDirectory = oArgs.SuccessDirectory Catch ex As ValidationException _logger.Error(ex) Dim oRejectionCodeString = GetRejectionCodeString(oMessageId, ErrorCode.ValidationException) 'Dim oMessage = "REJECTED - ZUGFeRD yes but formal validation failed!" _history.Update_HistoryEntry(oMessageId, oMD5CheckSum, oRejectionCodeString) Dim oErrors = ex.ValidationErrors Dim oErrorList As String = "" Dim oErrorListDE As String = "" For Each oError In oErrors oErrorList += $"
  • Element '{oError.ElementName}' with Value '{oError.ElementValue}': {oError.ErrorMessage}
  • " oErrorListDE += $"
  • Element '{oError.ElementName}' mit Wert '{oError.ElementValue}': {oError.ErrorMessageDE}
  • " Next Dim oBody = String.Format(EmailStrings.EMAIL_VALIDATION_ERROR, oErrorList) Dim oEmailData = _file.MoveAndRenameEmailToRejected(oArgs, oMessageId) _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "ValidationException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.ValidationException, oErrorListDE, oErrorList) AddRejectedState(oMessageId, oRejectionCodeString, "Die Rechnungsvalidierung ist fehlgeschlagen!", "", oSQLTransaction) Catch ex As MD5HashException _logger.Error(ex) Dim oRejectionCodeString = GetRejectionCodeString(oMessageId, ErrorCode.MD5HashException) ' When MD5HashException is thrown, we don't have a MD5Hash yet. ' Thats why we set it to String.Empty here. 'Dim oMessage = "REJECTED - Already processed (MD5Hash)" _history.Update_HistoryEntry(oMessageId, String.Empty, oRejectionCodeString) Dim oBody = String.Format(EmailStrings.EMAIL_MD5_ERROR, ex.FileName) Dim oEmailData = _file.MoveAndRenameEmailToRejected(oArgs, oMessageId) _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "MD5HashException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.MD5HashException, ex.FileName, "") AddRejectedState(oMessageId, oRejectionCodeString, "Die gesendete Rechnung wurde bereits verarbeitet!", "", oSQLTransaction) Catch ex As UnsupportedFerdException _logger.Error(ex) Dim oRejectionCodeString = GetRejectionCodeString(oMessageId, ErrorCode.UnsupportedFerdException) ' When UnsupportedFerdException is thrown, we don't have a MD5Hash yet. ' Thats why we set it to String.Empty here. _history.Update_HistoryEntry(oMessageId, String.Empty, oRejectionCodeString) Dim oEmailData = _file.MoveAndRenameEmailToRejected(oArgs, oMessageId) Dim oBody As String = String.Format(EmailStrings.EMAIL_UNSUPPORTED_DOCUMENT, oEmailData.Subject, ex.XmlFile) _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "UnsupportedFerdException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.UnsupportedFerdException, ex.XmlFile, "") AddRejectedState(oMessageId, oRejectionCodeString, "Nicht unterstütztes Datenformat", "", oSQLTransaction) Catch ex As InvalidFerdException _logger.Error(ex) Dim oRejectionCodeString = GetRejectionCodeString(oMessageId, ErrorCode.InvalidFerdException) ' When InvalidFerdException is thrown, we don't have a MD5Hash yet. ' Thats why we set it to String.Empty here. _history.Update_HistoryEntry(oMessageId, String.Empty, oRejectionCodeString) Dim oEmailData = _file.MoveAndRenameEmailToRejected(oArgs, oMessageId) Dim oBody = String.Format(EmailStrings.EMAIL_INVALID_DOCUMENT, oEmailData.Subject) _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "InvalidFerdException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.InvalidFerdException, "", "") AddRejectedState(oMessageId, oRejectionCodeString, "Inkorrektes Format", "", oSQLTransaction) Catch ex As TooMuchFerdsException _logger.Error(ex) Dim oRejectionCodeString = GetRejectionCodeString(oMessageId, ErrorCode.TooMuchFerdsException) _history.Update_HistoryEntry(oMessageId, oMD5CheckSum, oRejectionCodeString) Dim oEmailData = _file.MoveAndRenameEmailToRejected(oArgs, oMessageId) Dim oBody = String.Format(EmailStrings.EMAIL_TOO_MUCH_FERDS, oEmailData.Subject) _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "TooMuchFerdsException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.TooMuchFerdsException, "", "") AddRejectedState(oMessageId, oRejectionCodeString, "Email enthielt mehr als ein ZUGFeRD-Dokument", "", oSQLTransaction) Catch ex As NoFerdsException _logger.Error(ex) Dim oRejectionCodeString = GetRejectionCodeString(oMessageId, ErrorCode.NoFerdsException) _history.Update_HistoryEntry(oMessageId, oMD5CheckSum, oRejectionCodeString) Dim oEmailData = _file.MoveAndRenameEmailToRejected(oArgs, oMessageId) Dim oBody = String.Format(EmailStrings.EMAIL_NO_FERDS, oEmailData.Subject) _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "NoFerdsException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.NoFerdsException, "", "") AddRejectedState(oMessageId, oRejectionCodeString, "Email enthielt keine ZUGFeRD-Dokumente", "", oSQLTransaction) Catch ex As MissingValueException _logger.Error(ex) Dim oRejectionCodeString = GetRejectionCodeString(oMessageId, ErrorCode.MissingValueException) _history.Update_HistoryEntry(oMessageId, oMD5CheckSum, oRejectionCodeString) Dim oMissingFieldList As String = "" For Each oMissingFieldDescription In ex.MissingProperties oMissingFieldList += $"
  • {oMissingFieldDescription.Description}
    {oMissingFieldDescription.XMLPath}
  • " Next Dim oOrgFilename = _hash.GetOriginalFilename(ex.File.Name) Dim oBody = _email.CreateBodyForMissingProperties(ex.File.Name, ex.MissingProperties) Dim oEmailData = _file.MoveAndRenameEmailToRejected(oArgs, oMessageId) _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "MissingValueException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.MissingValueException, oOrgFilename, oMissingFieldList) AddRejectedState(oMessageId, oRejectionCodeString, "Es fehlten ZugferdSpezifikationen", "", oSQLTransaction) Catch ex As FileSizeLimitReachedException _logger.Error(ex) Dim oRejectionCodeString = GetRejectionCodeString(oMessageId, ErrorCode.FileSizeLimitReachedException) _history.Update_HistoryEntry(oMessageId, oMD5CheckSum, oRejectionCodeString) Dim oEmailData = _file.MoveAndRenameEmailToRejected(oArgs, oMessageId) Dim oKey = FileSizeLimitReachedException.KEY_FILENAME Dim oFileExceedingThreshold As String = IIf(ex.Data.Contains(oKey), ex.Data.Item(oKey), "") Dim oOrgFilename = _hash.GetOriginalFilename(oFileExceedingThreshold) Dim oBody = String.Format(EmailStrings.EMAIL_FILE_SIZE_REACHED, oArgs.MaxAttachmentSizeInMegaBytes, oOrgFilename) _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "FileSizeLimitReachedException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.FileSizeLimitReachedException, oArgs.MaxAttachmentSizeInMegaBytes, oOrgFilename) AddRejectedState(oMessageId, oRejectionCodeString, "Erlaubte Dateigröße überschritten", "", oSQLTransaction) Catch ex As NoFerdsAlternateException ' TODO: Maybe dont even log this 'error', since it's not really an error and it might happen *A LOT* _logger.Error(ex) oMoveDirectory = oArgs.NonZugferdDirectory Catch ex As OutOfMemoryException _logger.Warn("OutOfMemory Error occurred: {0}", ex.Message) _logger.Error(ex) ' Send Email to Digital Data Dim oBody = _email.CreateBodyForUnhandledException(oMessageId, ex) Dim oEmailData As New EmailData With { .From = oArgs.ExceptionEmailAddress, .Subject = $"OutOfMemoryException im ZUGFeRD-Parser @ {oMessageId}" } _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "OutOfMemoryException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.UnhandledException, ex.Message, ex.StackTrace) ' Rollback Transaction oSQLTransaction.Rollback() oMoveDirectory = DIRECTORY_DONT_MOVE oExpectedError = False Catch ex As Exception _logger.Warn("Unknown Error occurred: {0}", ex.Message) _logger.Error(ex) oMoveDirectory = DIRECTORY_DONT_MOVE oExpectedError = False If oSQLConnection IsNot Nothing And oSQLTransaction IsNot Nothing Then ' Send Email to Digital Data Dim oBody = _email.CreateBodyForUnhandledException(oMessageId, ex) Dim oEmailData As New EmailData With { .From = oArgs.ExceptionEmailAddress, .Subject = $"UnhandledException im ZUGFeRD-Parser @ {oMessageId}" } _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "UnhandledException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.UnhandledException, ex.Message, ex.StackTrace) ' Rollback Transaction oSQLTransaction.Rollback() End If Finally Dim oxRechnungHandle As Boolean = False Try Dim oRegularMove As Boolean = False ' If an application error occurred, dont move files so they will be processed again later If oMoveDirectory = DIRECTORY_DONT_MOVE Then _logger.Info("Application Error occurred. Files for message Id {0} will not be moved.", oMessageId) ElseIf oArgs.AllowXRechnung And oIsSuccess And oEInvoiceFileGroup.Item(0).Extension = ".xml" Then _logger.Debug("Before Creating the PDF-File from XML data / Before Commit") oxRechnungHandle = True ' Hier das neue PDF erzeugen 'but before we need to get all Data we need MyTemplateValues_xInvDT = Nothing Dim oSQL_MsgIDReplace = SQL_xRechnung_ItemTemplate oSQL_MsgIDReplace = oSQL_MsgIDReplace.Replace("@MSG_ID", oFileGroup.Key) If oSQLTransaction IsNot Nothing Then ' Commit Transaction oSQLTransaction.Commit() _logger.Debug("XML commit triggered") End If MyTemplateValues_xInvDT = _mssql.GetDatatable(oSQL_MsgIDReplace) If Not IsNothing(MyTemplateValues_xInvDT) Then If MyTemplateValues_xInvDT.Rows.Count > 0 Then Dim oViewReceiptFileInfo As FileInfo = _xRechnungCreator.Create_PDFfromXML(oEInvoiceFileGroup.Item(0), MyTemplateValues_xInvDT) If Not IsNothing(oViewReceiptFileInfo) Then oEInvoiceFileGroup.Item(0) = oViewReceiptFileInfo oRegularMove = True End If End If End If Else oRegularMove = True End If If oRegularMove Then ' Move all files of the current group _file.MoveFiles(oArgs, oMessageId, oEInvoiceFileGroup, oEmailAttachmentFiles, oEmbeddedAttachmentFiles, oMoveDirectory, oIsSuccess) End If _logger.Info("END processing file group {0}", oMessageId) Catch ex As Exception ' Send Email to Digital Data Dim oBody = _email.CreateBodyForUnhandledException(oMessageId, ex) Dim oEmailData As New EmailData With { .From = oArgs.ExceptionEmailAddress, .Subject = $"FileMoveException im ZUGFeRD-Parser @ {oMessageId}" } _email.AddToEmailQueueMSSQL(oMessageId, oSQLTransaction, oBody, oEmailData, "FileMoveException", _EmailOutAccountId, oArgs.NamePortal, oArgs.RejectionTemplateId, ErrorCode.UnhandledException, ex.Message, ex.StackTrace) _logger.Warn("Could not move files!") _logger.Error(ex) Throw ex End Try Try ' If everything went OK or an expected error occurred, ' finally commit all changes To the Database ' ================================================================== If oIsSuccess Or oExpectedError Then _logger.Debug("Before default sql commit: oxRechnungHandle [{0}]", oxRechnungHandle) If oxRechnungHandle = False AndAlso oSQLTransaction IsNot Nothing Then ' Commit Transaction oSQLTransaction.Commit() _logger.Debug("default commit triggered") End If End If Catch ex As Exception _logger.Error(ex) _logger.Warn("Database Transactions were not committed successfully.") End Try Try If oSQLConnection IsNot Nothing Then _logger.Debug("Before default sql close") oSQLConnection.Close() End If Catch ex As Exception _logger.Error(ex) _logger.Warn("Database Connections were not closed successfully.") End Try End Try Next _logger.Debug("Finishing Job {0}", Me.GetType.Name) Catch ex As Exception _logger.Error(ex) _logger.Info("Job Failed! See error log for details") End Try End Sub Private Function GetRejectionCodeString(pMessageId As String, pRejectionCode As ErrorCode) As String Dim intCode As Integer = DirectCast(pRejectionCode, Integer) Dim oRejectionCodeString = $"{EmailStrings.ErrorCodePraefix}{intCode}" ' Wir wollen im error-Log den Code und die MessageID haben, um die es geht Dim oInfoMessage = $"Rejection {oRejectionCodeString} triggered for '{pMessageId}'" _logger.Error(oInfoMessage) Return oRejectionCodeString End Function Private Function ProcessXMLFile(pMessageId As String, pZugferdFileCounter As Integer, pFile As FileInfo, pConnections As DatabaseConnections, pArgs As WorkerArgs) As ProcessFileResult Dim oDocument As ZUGFeRDInterface.ZugferdResult Dim oResult As New ProcessFileResult() If pFile.Extension.Equals(".xml", StringComparison.OrdinalIgnoreCase) = False Then ' Diese Methode ist nur für den xml-Beleg gedacht Return oResult End If _logger.Info("Start xml processing file {0}", pFile.Name) ' Checking filesize If _filesystem.TestFileSizeIsLessThanMaxFileSize(pFile.FullName, pArgs.MaxAttachmentSizeInMegaBytes) = False Then _logger.Warn("Filesize for File [{0}] exceeded limit of {1} MB", pFile.Name, pArgs.MaxAttachmentSizeInMegaBytes) Throw New FileSizeLimitReachedException(pFile.Name, pArgs.MaxAttachmentSizeInMegaBytes) End If Try oDocument = _zugferd.GetSerializedXMLContentFromFile(pFile) Catch ex As ValidationException Throw ex Catch ex As ZUGFeRDExecption Select Case ex.ErrorType Case ZUGFeRDInterface.ErrorType.NoZugferd _logger.Info("File [{0}] is not a valid ZUGFeRD document. Skipping.", pFile.Name) oResult.EmailAttachmentFiles.Add(pFile) Return oResult Case ZUGFeRDInterface.ErrorType.UnsupportedFormat _logger.Info("File [{0}/{1}] is an unsupported ZUFeRD document format!", pFile.Name, ex.XmlFile) Throw New UnsupportedFerdException(ex.XmlFile) Case ZUGFeRDInterface.ErrorType.NoValidZugferd _logger.Info("File [{0}] is an Incorrectly formatted ZUGFeRD document!", pFile.Name) Throw New InvalidFerdException() Case Else _logger.Warn("Unexpected Error occurred while extracting ZUGFeRD Information from file {0}", pFile.Name) Throw ex End Select End Try Try oDocument.ReceiptFileType = ZUGFeRDInterface.RECEIPT_TYPE_XML Dim sqlResult As Boolean = StoreXMLItemsInDatabase(pMessageId, oDocument, pFile, pConnections, pArgs) Catch ex As Exception Throw ex End Try _logger.Debug("File processed.") Dim oMD5Checksum = _hash.GenerateAndCheck_MD5Sum(pFile, pMessageId, pArgs.IgnoreRejectionStatus) oResult.ZugferdFileFound = True oResult.MD5Checksum = oMD5Checksum oResult.ZugferdFileCount = 1 ' Es kann hier nur genau einen Treffer geben! Return oResult End Function Private Function ProcessFile(pMessageId As String, pZugferdFileCounter As Integer, pFile As FileInfo, pConnections As DatabaseConnections, pArgs As WorkerArgs) As ProcessFileResult Dim oDocument As ZUGFeRDInterface.ZugferdResult Dim oResult As New ProcessFileResult() ' Only pdf files are allowed from here on If Not pFile.Name.ToUpper.EndsWith(".PDF") Then _logger.Debug("Skipping non-pdf file {0}", pFile.Name) oResult.EmailAttachmentFiles.Add(pFile) ' Checking filesize for attachment files If _filesystem.TestFileSizeIsLessThanMaxFileSize(pFile.FullName, pArgs.MaxAttachmentSizeInMegaBytes) = False Then _logger.Warn("Filesize for File [{0}] exceeded limit of {1} MB", pFile.Name, pArgs.MaxAttachmentSizeInMegaBytes) Throw New FileSizeLimitReachedException(pFile.Name, pArgs.MaxAttachmentSizeInMegaBytes) End If Return oResult End If _logger.Info("Start processing file {0}", pFile.Name) ' Checking filesize for pdf files If _filesystem.TestFileSizeIsLessThanMaxFileSize(pFile.FullName, pArgs.MaxAttachmentSizeInMegaBytes) = False Then _logger.Warn("Filesize for File [{0}] exceeded limit of {1} MB", pFile.Name, pArgs.MaxAttachmentSizeInMegaBytes) Throw New FileSizeLimitReachedException(pFile.Name, pArgs.MaxAttachmentSizeInMegaBytes) End If Try oDocument = _zugferd.ExtractZUGFeRDFileWithGDPicture(pFile.FullName) Catch ex As ValidationException Throw ex Catch ex As ZUGFeRDExecption Select Case ex.ErrorType Case ZUGFeRDInterface.ErrorType.NoZugferd _logger.Info("File [{0}] is not a valid ZUGFeRD document. Skipping.", pFile.Name) oResult.EmailAttachmentFiles.Add(pFile) Return oResult Case ZUGFeRDInterface.ErrorType.UnsupportedFormat _logger.Info("File [{0}/{1}] is an unsupported ZUFeRD document format!", pFile.Name, ex.XmlFile) Throw New UnsupportedFerdException(ex.XmlFile) Case ZUGFeRDInterface.ErrorType.NoValidZugferd _logger.Info("File [{0}] is an Incorrectly formatted ZUGFeRD document!", pFile.Name) Throw New InvalidFerdException() Case Else _logger.Warn("Unexpected Error occurred while extracting ZUGFeRD Information from file {0}", pFile.Name) Throw ex End Select End Try ' Check if there are more than one ZUGFeRD files If pZugferdFileCounter = 1 Then Throw New TooMuchFerdsException() End If ' Since extraction went well, increase the amount of ZUGFeRD files pZugferdFileCounter += 1 _logger.Info("Zugferd file found. Increasing counter.") ' Extract all attachments with the extensions specified in `AllowedExtensions`. ' If you need to extract and use embedded xml files, you need to filter out the zugferd-invoice.xml yourself. ' Right now the zugferd-invoice.xml is filtered out because `AllowedExtensions` does not contain `xml`. Dim oAttachments = _embeds.Extract(pFile.FullName, AllowedExtensions) If oAttachments Is Nothing Then _logger.Warn("Attachments for file [{0}] could not be extracted", pFile.FullName) Else oResult.EmbeddedAttachmentFiles.AddRange(oAttachments) End If Try oDocument.ReceiptFileType = ZUGFeRDInterface.RECEIPT_TYPE_PDF Dim sqlResult As Boolean = StoreXMLItemsInDatabase(pMessageId, oDocument, pFile, pConnections, pArgs) Catch ex As Exception Throw ex End Try _logger.Debug("File processed.") ' Check the Checksum and rejection status Dim oMD5Checksum = _hash.GenerateAndCheck_MD5Sum(pFile, pMessageId, pArgs.IgnoreRejectionStatus) oResult.ZugferdFileFound = True oResult.MD5Checksum = oMD5Checksum oResult.ZugferdFileCount = pZugferdFileCounter Return oResult End Function Private Function StoreXMLItemsInDatabase(pMessageId As String, pDocument As ZUGFeRDInterface.ZugferdResult, pFile As FileInfo, pConnections As DatabaseConnections, pArgs As WorkerArgs) As Boolean ' Check the document against the configured property map and return: ' - a List of valid properties ' - a List of missing properties Dim oPropertyMap = _zugferd.FilterPropertyMap(pArgs.PropertyMap, pDocument.Specification) Dim oCheckResult = _zugferd.PropertyValues.CheckPropertyValues(pDocument.SchemaObject, oPropertyMap, pMessageId) _logger.Info("Properties checked: [{0}] missing properties / [{1}] valid properties found.", oCheckResult.MissingProperties.Count, oCheckResult.ValidProperties.Count) If oCheckResult.MissingProperties.Count > 0 Then _logger.Warn("[{0}] missing properties found. Exiting.", oCheckResult.MissingProperties.Count) Throw New MissingValueException(pFile, oCheckResult.MissingProperties) Else _logger.Debug("No missing properties found. Continuing.") End If If DeleteExistingPropertyValues(pMessageId, pConnections) = False Then Throw New Exception("Could not cleanup data. Exiting.") End If ' DataTable vorbereiten Dim oDataTable As DataTable = FillDataTable(pMessageId, oCheckResult, pDocument) ' ColumnList initialisieren Dim oColumnNames As List(Of String) = New List(Of String) From { "REFERENCE_GUID", "ITEM_DESCRIPTION", "ITEM_VALUE", "GROUP_COUNTER", "SPEC_NAME", "IS_REQUIRED" } Dim oBulkResult = BulkInsert(pConnections, oDataTable, "TBEDMI_ITEM_VALUE", oColumnNames) If oBulkResult = False Then _logger.Error("Bulk Insert for MessageId [{0}] failed!", pMessageId) Throw New Exception("Bulk Insert failed! Exiting.") End If _logger.Info("Bulk Insert finished. [{0}] rows inserted for MessageId [{1}].", oDataTable.Rows.Count, pMessageId) Return True End Function Private Function FillDataTable(pMessageId As String, pCheckResult As PropertyValues.CheckPropertyValuesResult, pDocument As ZUGFeRDInterface.ZugferdResult) As DataTable Dim oDataTable As DataTable = New DataTable() oDataTable.Columns.Add(New DataColumn("REFERENCE_GUID", GetType(String))) oDataTable.Columns.Add(New DataColumn("ITEM_DESCRIPTION", GetType(String))) oDataTable.Columns.Add(New DataColumn("ITEM_VALUE", GetType(String))) oDataTable.Columns.Add(New DataColumn("GROUP_COUNTER", GetType(Int32))) oDataTable.Columns.Add(New DataColumn("SPEC_NAME", GetType(String))) oDataTable.Columns.Add(New DataColumn("IS_REQUIRED", GetType(Boolean))) ' Erste Zeile enthält die Spezifikation Dim oFirstRow As DataRow = oDataTable.NewRow() oFirstRow("REFERENCE_GUID") = pMessageId oFirstRow("ITEM_DESCRIPTION") = "ZUGFeRDSpezifikation" oFirstRow("ITEM_VALUE") = pDocument.Specification oFirstRow("GROUP_COUNTER") = 0 oFirstRow("SPEC_NAME") = "ZUGFERD_SPECIFICATION" oFirstRow("IS_REQUIRED") = False _logger.Debug("Mapping Property [ZUGFERD_SPECIFICATION] with value [{0}]", pDocument.Specification) oDataTable.Rows.Add(oFirstRow) ' Zweite Zeile enthält das verwendete XML Schema Dim oSecondRow As DataRow = oDataTable.NewRow() oSecondRow("REFERENCE_GUID") = pMessageId oSecondRow("ITEM_DESCRIPTION") = "ZUGFeRDXMLSchema" oSecondRow("ITEM_VALUE") = pDocument.UsedXMLSchema oSecondRow("GROUP_COUNTER") = 0 oSecondRow("SPEC_NAME") = "ZUGFERD_XML_SCHEMA" oSecondRow("IS_REQUIRED") = False _logger.Debug("Mapping Property [ZUGFERD_XML_SCHEMA] with value [{0}]", pDocument.UsedXMLSchema) oDataTable.Rows.Add(oSecondRow) ' Dritte Zeile enthält das verwendete Datei-Format des Belegs (PDF/XML) Dim oThirdRow As DataRow = oDataTable.NewRow() oThirdRow("REFERENCE_GUID") = pMessageId oThirdRow("ITEM_DESCRIPTION") = "ReceiptFileType" oThirdRow("ITEM_VALUE") = pDocument.ReceiptFileType oThirdRow("GROUP_COUNTER") = 0 oThirdRow("SPEC_NAME") = "RECEIPT_FILE_TYPE" oThirdRow("IS_REQUIRED") = False _logger.Debug("Mapping Property [RECEIPT_FILE_TYPE] with value [{0}]", pDocument.ReceiptFileType) oDataTable.Rows.Add(oThirdRow) For Each oProperty In pCheckResult.ValidProperties ' If GroupCounter is -1, it means this is a default property that can only occur once. ' Set the actual inserted value to 0 Dim oGroupCounterValue As Integer = oProperty.GroupCounter If oGroupCounterValue = -1 Then oGroupCounterValue = 0 End If If oProperty.Value.Length > 900 Then _logger.Warn("Value for field [{0}] is longer than 900 characters, will be truncated!", oProperty.TableColumn) End If Dim oNewRow As DataRow = oDataTable.NewRow() oNewRow("REFERENCE_GUID") = pMessageId oNewRow("ITEM_DESCRIPTION") = oProperty.Description oNewRow("ITEM_VALUE") = oProperty.Value.Truncate(900).Replace("'", "''") oNewRow("GROUP_COUNTER") = oGroupCounterValue oNewRow("SPEC_NAME") = oProperty.TableColumn oNewRow("IS_REQUIRED") = oProperty.IsRequired _logger.Debug("Mapping Property [{0}] with value [{1}]", oProperty.TableColumn, oProperty.Value.Replace("'", "''")) oDataTable.Rows.Add(oNewRow) Next Return oDataTable End Function Private Function DeleteExistingPropertyValues(pMessageId As String, pConnections As DatabaseConnections) As Boolean Dim oDelSQL = $"DELETE FROM TBEDMI_ITEM_VALUE where REFERENCE_GUID = '{pMessageId}'" Dim oStep As String = "TBEDMI_ITEM_VALUE Delete MessageID Items" Try Dim retValue As Boolean = _mssql.ExecuteNonQueryWithConnectionObject(oDelSQL, pConnections.SQLServerConnection, MSSQLServer.TransactionMode.ExternalTransaction, pConnections.SQLServerTransaction) Return retValue Catch ex As Exception _logger.Warn("Step [{0}] with SQL [{1}] was not successful.", oStep, oDelSQL) End Try Return False End Function Private Function BulkInsert(pConnections As DatabaseConnections, pTable As DataTable, pDestinationTable As String, pColumns As List(Of String)) As Boolean Using oBulkCopy = New SqlBulkCopy(pConnections.SQLServerConnection, SqlBulkCopyOptions.Default, pConnections.SQLServerTransaction) oBulkCopy.DestinationTableName = pDestinationTable For Each oColumn In pColumns oBulkCopy.ColumnMappings.Add(New SqlBulkCopyColumnMapping(oColumn, oColumn)) Next Try oBulkCopy.WriteToServer(pTable) Catch ex As Exception _logger.Error(ex) Return False End Try End Using Return True End Function Private Sub AddRejectedState(pMessageID As String, pTitle As String, pTitle1 As String, pComment As String, pTransaction As SqlTransaction) Try 'PRCUST_ADD_HISTORY_STATE: @MessageID VARCHAR(250), @TITLE1 VARCHAR(250), @TITLE2 VARCHAR(250) Dim oSQL = $"EXEC PRCUST_ADD_HISTORY_STATE '{pMessageID}','{pTitle}','{pTitle1}','{pComment.Replace("'", "''")}'" _mssql.ExecuteNonQuery(oSQL, pTransaction) Catch ex As Exception _logger.Error(ex) End Try End Sub End Class