diff --git a/Database/Adapters/MSSQLServer.vb b/Database/Adapters/MSSQLServer.vb index c6695542..d3e4d02f 100644 --- a/Database/Adapters/MSSQLServer.vb +++ b/Database/Adapters/MSSQLServer.vb @@ -340,7 +340,7 @@ Public Class MSSQLServer pSqlCommandObject.CommandTimeout = pTimeout Using oAdapter As New SqlDataAdapter(pSqlCommandObject) - Logger.Debug("GetDatatableWithConnectionObject: Running Query [{0}]", pSqlCommandObject.CommandText) + Logger.Debug("GetDatatableWithConnectionObject: Running Query [{0}] and Parameters [{1}]", pSqlCommandObject.CommandText, GetParameterListAsString(pSqlCommandObject)) oAdapter.Fill(oTable) End Using @@ -427,7 +427,7 @@ Public Class MSSQLServer Dim oTransaction As SqlTransaction = MaybeGetTransaction(pSqlConnection, pTransactionMode, pTransaction) Try - Logger.Debug("ExecuteNonQueryWithConnectionObject: Running Command [{0}]", pSqlCommandObject.CommandText) + Logger.Debug("ExecuteNonQueryWithConnectionObject: Running Command [{0}] and Parameters [{1}]", pSqlCommandObject.CommandText, GetParameterListAsString(pSqlCommandObject)) pSqlCommandObject.Connection = pSqlConnection pSqlCommandObject.Transaction = oTransaction @@ -517,6 +517,9 @@ Public Class MSSQLServer Dim oResult As Object = Nothing Try + + Logger.Debug("GetScalarValueWithConnectionObject: Running Query [{0}] with Parameters [{1}]", pSqlCommandObject, GetParameterListAsString(pSqlCommandObject)) + pSqlCommandObject.Connection = pSqlConnection pSqlCommandObject.CommandTimeout = pTimeout pSqlCommandObject.Transaction = oTransaction @@ -601,4 +604,13 @@ Public Class MSSQLServer Dim res = command.EndExecuteNonQuery(result) Logger.Info("Finished executing Async database operation: {0}", command.CommandText) End Sub + + Private Function GetParameterListAsString(pSQLCommand As SqlCommand) As String + Dim oList = pSQLCommand.Parameters. + Cast(Of SqlParameter). + Select(Function(p) $"({p.ParameterName}={p.Value})"). + ToList() + + Return String.Join(",", oList) + End Function End Class diff --git a/Database/Database.vbproj b/Database/Database.vbproj index 1137ac87..b0ef3829 100644 --- a/Database/Database.vbproj +++ b/Database/Database.vbproj @@ -100,7 +100,6 @@ - diff --git a/Database/Helpers.vb b/Database/Helpers.vb deleted file mode 100644 index 4d09d04b..00000000 --- a/Database/Helpers.vb +++ /dev/null @@ -1,10 +0,0 @@ -Public Class Helpers - - Public Shared Function MaybeEscapeSQLCommand(pSQLCommand As String) As String - - - - End Function - - -End Class diff --git a/Database/My Project/AssemblyInfo.vb b/Database/My Project/AssemblyInfo.vb index 9e1ccdd2..2c7ff65c 100644 --- a/Database/My Project/AssemblyInfo.vb +++ b/Database/My Project/AssemblyInfo.vb @@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices - + @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - - + + diff --git a/Filesystem/File.vb b/Filesystem/File.vb index 60379f2b..3a20856d 100644 --- a/Filesystem/File.vb +++ b/Filesystem/File.vb @@ -109,11 +109,21 @@ Public Class File ''' ''' Adds file version string to given filename `Destination` if that file already exists. ''' - ''' - ''' - Public Function GetVersionedFilename(Destination As String) As String + ''' Filepath to check + ''' Versioned string + Public Function GetVersionedFilename(pFilePath As String) As String + Return GetVersionedFilenameWithFilecheck(pFilePath, Function(pPath As String) IO.File.Exists(pFilePath)) + End Function + + ''' + ''' Adds file version string to given filename `Destination` if that file already exists. + ''' + ''' Filepath to check + ''' Custom action to check for file existence + ''' Versioned string + Public Function GetVersionedFilenameWithFilecheck(pFilePath As String, pFileExistsAction As Func(Of String, Boolean)) As String Try - Dim oFileName As String = Destination + Dim oFileName As String = pFilePath Dim oFinalFileName = oFileName Dim oDestinationDir = Path.GetDirectoryName(oFileName) @@ -131,7 +141,7 @@ Public Class File ' Shorten the filename (only filename, without extension or version) ' by cutting the length in half. This should work no matter how long the path and/or filename are. ' The initial check operates on the full path to catch all scenarios. - If Destination.Length > MAX_FILE_PATH_LENGTH Then + If pFilePath.Length > MAX_FILE_PATH_LENGTH Then _Logger.Info("Filename is too long. Filename will be cut to prevent further errors.") _Logger.Info("Original Filename is: {0}", oFileNameWithoutExtension) Dim oNewLength As Integer = Math.Round(oFileNameWithoutExtension.Length / 2) @@ -147,15 +157,15 @@ Public Class File _Logger.Debug("Intermediate Filename is {0}", oFinalFileName) _Logger.Debug("File version: {0}", oFileVersion) oFileVersion += 1 - Loop While (IO.File.Exists(oFinalFileName)) + Loop While pFileExistsAction(oFinalFileName) = True _Logger.Debug("Final Filename is {0}", oFinalFileName) Return oFinalFileName Catch ex As Exception - _Logger.Warn("Filename {0} could not be versioned. Original filename will be returned!", Destination) + _Logger.Warn("Filename {0} could not be versioned. Original filename will be returned!", pFilePath) _Logger.Error(ex) - Return Destination + Return pFilePath End Try End Function @@ -195,6 +205,8 @@ Public Class File oStringVersion = 1 End If + _Logger.Debug("Versioned: String [{0}], Version [{1}]", pString, oStringVersion) + Return New Tuple(Of String, Integer)(pString, oStringVersion) End Function diff --git a/Filesystem/My Project/AssemblyInfo.vb b/Filesystem/My Project/AssemblyInfo.vb index f1519343..dffefb47 100644 --- a/Filesystem/My Project/AssemblyInfo.vb +++ b/Filesystem/My Project/AssemblyInfo.vb @@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices - + @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - - + + diff --git a/Interfaces/ActiveDirectoryInterface.vb b/Interfaces/ActiveDirectoryInterface.vb index d3617f4e..5da2f55a 100644 --- a/Interfaces/ActiveDirectoryInterface.vb +++ b/Interfaces/ActiveDirectoryInterface.vb @@ -193,6 +193,8 @@ Public Class ActiveDirectoryInterface .FirebirdSyskey = oMap.FirebirdSyskey, .MSSQLColumn = oMap.MSSQLColumn }) + Else + _logger.Debug("Attribute [{0}] is empty.", oMap.AttributeName) End If Next Else diff --git a/Interfaces/ActiveDirectoryInterface/ActiveDirectoryUser.vb b/Interfaces/ActiveDirectoryInterface/ActiveDirectoryUser.vb index ee7504f6..0a0558b3 100644 --- a/Interfaces/ActiveDirectoryInterface/ActiveDirectoryUser.vb +++ b/Interfaces/ActiveDirectoryInterface/ActiveDirectoryUser.vb @@ -8,6 +8,7 @@ Public Class ADUser Public Property GivenName As String Public Property Middlename As String Public Property Email As String + Public Property Language As String Public CustomAttributes As List(Of CustomAttribute) diff --git a/Interfaces/My Project/AssemblyInfo.vb b/Interfaces/My Project/AssemblyInfo.vb index d792ad58..955d1b45 100644 --- a/Interfaces/My Project/AssemblyInfo.vb +++ b/Interfaces/My Project/AssemblyInfo.vb @@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices - + @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - - + + diff --git a/Interfaces/ZUGFeRDInterface.vb b/Interfaces/ZUGFeRDInterface.vb index 344d4f01..4c10aa50 100644 --- a/Interfaces/ZUGFeRDInterface.vb +++ b/Interfaces/ZUGFeRDInterface.vb @@ -12,6 +12,13 @@ Public Class ZUGFeRDInterface Private ReadOnly _logger As Logger Private ReadOnly _Options As ZugferdOptions + ' These constants define the specification markers for the different + ' zugferd document schema versions. These markers need to be used to + ' define the property map in the database (column SPECIFICATION). + Public Const ZUGFERD_SPEC_DEFAULT = "DEFAULT" + Public Const ZUGFERD_SPEC_10 = "ZUGFERD_10" + Public Const ZUGFERD_SPEC_2x = "ZUGFERD_2x" + Private ReadOnly ValidFilenames As New List(Of String) From { PDFEmbeds.ZUGFERD_XML_FILENAME.ToUpper, PDFEmbeds.FACTUR_X_XML_FILENAME_DE.ToUpper, @@ -35,6 +42,15 @@ Public Class ZUGFeRDInterface Public Class ZugferdOptions Public Property AllowFacturX_Filename As Boolean = True Public Property AllowXRechnung_Filename As Boolean = True + Public Property AllowZugferd_1_0_Schema As Boolean = True + Public Property AllowZugferd_2_x_Schema As Boolean = True + End Class + + Public Class ZugferdResult + Public Property DataFileName As String + Public Property XPathObject As XPathDocument + Public Property SchemaObject As Object + Public Property Specification As String End Class ''' @@ -89,14 +105,14 @@ Public Class ZUGFeRDInterface ''' ''' ''' - Public Function ExtractZUGFeRDFileWithGDPicture(Path As String) As Tuple(Of String, Object) - Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Path) + Public Function ExtractZUGFeRDFileWithGDPicture(Path As String) As ZugferdResult + Dim oResult = ValidateZUGFeRDFileWithGDPicture(Path) - If IsNothing(oXmlDocument.Item2) Then + If IsNothing(oResult.SchemaObject) Then Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.") End If - Return SerializeZUGFeRDDocument(oXmlDocument) + Return SerializeZUGFeRDDocument(oResult) End Function ''' @@ -104,14 +120,14 @@ Public Class ZUGFeRDInterface ''' ''' ''' - Public Function ExtractZUGFeRDFileWithGDPicture(Stream As Stream) As Tuple(Of String, Object) - Dim oXmlDocument = ValidateZUGFeRDFileWithGDPicture(Stream) + Public Function ExtractZUGFeRDFileWithGDPicture(Stream As Stream) As ZugferdResult + Dim oResult = ValidateZUGFeRDFileWithGDPicture(Stream) - If IsNothing(oXmlDocument.Item2) Then + If IsNothing(oResult.SchemaObject) Then Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.") End If - Return SerializeZUGFeRDDocument(oXmlDocument) + Return SerializeZUGFeRDDocument(oResult) End Function ''' @@ -120,7 +136,7 @@ Public Class ZUGFeRDInterface ''' ''' ''' The embedded xml data as an XPath document - Public Function ValidateZUGFeRDFileWithGDPicture(pStream As Stream) As Tuple(Of String, XPathDocument) + Public Function ValidateZUGFeRDFileWithGDPicture(pStream As Stream) As ZugferdResult Dim oEmbedExtractor = New PDFEmbeds(_logConfig) Try @@ -148,7 +164,7 @@ Public Class ZUGFeRDInterface ''' ''' ''' The embedded xml data as an XPath document - Public Function ValidateZUGFeRDFileWithGDPicture(pPath As String) As Tuple(Of String, XPathDocument) + Public Function ValidateZUGFeRDFileWithGDPicture(pPath As String) As ZugferdResult Dim oEmbedExtractor = New PDFEmbeds(_logConfig) Try @@ -170,7 +186,7 @@ Public Class ZUGFeRDInterface End Try End Function - Private Function HandleEmbeddedFiles(pResults As List(Of PDFEmbeds.EmbeddedFile)) As Tuple(Of String, XPathDocument) + Private Function HandleEmbeddedFiles(pResults As List(Of PDFEmbeds.EmbeddedFile)) As ZugferdResult If pResults Is Nothing Then Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei, weil die Attachments nicht gelesen werden konnten.") End If @@ -200,7 +216,10 @@ Public Class ZUGFeRDInterface Try Using oStream As New MemoryStream(oAllowedResult.FileContents) - Return New Tuple(Of String, XPathDocument)(oAllowedResult.FileName, New XPathDocument(oStream)) + Return New ZugferdResult With { + .DataFileName = oAllowedResult.FileName, + .XPathObject = New XPathDocument(oStream) + } End Using Catch ex As ZUGFeRDExecption @@ -214,44 +233,59 @@ Public Class ZUGFeRDInterface End Try End Function - Public Function SerializeZUGFeRDDocument(pDocument As Tuple(Of String, XPathDocument)) As Tuple(Of String, Object) + Public Function SerializeZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult Try - Dim oNavigator As XPathNavigator = pDocument.Item2.CreateNavigator() + Dim oNavigator As XPathNavigator = pResult.XPathObject.CreateNavigator() Dim oReader As XmlReader - Dim oResult = Nothing - Dim oTypes As New List(Of Type) From { - GetType(ZUGFeRD.Version1_0.CrossIndustryDocumentType), - GetType(ZUGFeRD.Version2_0.CrossIndustryInvoiceType), - GetType(ZUGFeRD.Version2_1_1.CrossIndustryInvoiceType), - GetType(ZUGFeRD.Version2_2_FacturX.CrossIndustryInvoiceType) - } + Dim oObject As Object = Nothing + Dim oSpecification As String = Nothing - For Each oType In oTypes - _logger.Debug("Trying Type [{0}]", oType.FullName) - Dim oSerializer As New XmlSerializer(oType) + Dim oAllowedTypes As New Dictionary(Of String, Type) + + If _Options.AllowZugferd_1_0_Schema Then + oAllowedTypes.Add(ZUGFERD_SPEC_10, GetType(ZUGFeRD.Version1_0.CrossIndustryDocumentType)) + End If + + If _Options.AllowZugferd_2_x_Schema Then + oAllowedTypes.Add(ZUGFERD_SPEC_2x, GetType(ZUGFeRD.Version2_0.CrossIndustryInvoiceType)) + oAllowedTypes.Add(ZUGFERD_SPEC_2x, GetType(ZUGFeRD.Version2_1_1.CrossIndustryInvoiceType)) + oAllowedTypes.Add(ZUGFERD_SPEC_2x, GetType(ZUGFeRD.Version2_2_FacturX.CrossIndustryInvoiceType)) + End If + + For Each oType In oAllowedTypes + Dim oTypeName As String = oType.Value.FullName + Dim oSerializer As New XmlSerializer(oType.Value) + _logger.Debug("Trying Type [{0}]", oTypeName) Try oReader = oNavigator.ReadSubtree() - oResult = oSerializer.Deserialize(oReader) - _logger.Debug("Serializing with type [{0}] succeeded", oType.FullName) + + oObject = oSerializer.Deserialize(oReader) + oSpecification = oType.Key + + _logger.Debug("Serializing with type [{0}] succeeded", oTypeName) Exit For Catch ex As Exception - _logger.Debug("Serializing with type [{0}] failed", oType.FullName) + _logger.Debug("Serializing with type [{0}] failed", oTypeName) _logger.Debug(ex.Message) _logger.Error(ex.InnerException?.Message) End Try Next - If oResult Is Nothing Then + If oObject Is Nothing Then Throw New ApplicationException("No Types matched the given document. Document could not be serialized.") End If - Return oResult + pResult.Specification = oSpecification + pResult.SchemaObject = oObject + + Return pResult Catch ex As Exception _logger.Error(ex) - Throw New ZUGFeRDExecption(ErrorType.NoValidZugferd, "Datei ist eine ungültige ZUGFeRD Datei.") + Dim oMessage = "Datei ist eine ungültige ZUGFeRD Datei oder das Format wird nicht unterstüzt, oder das Format ist deaktiviert." + Throw New ZUGFeRDExecption(ErrorType.NoValidZugferd, oMessage) End Try End Function diff --git a/Interfaces/ZUGFeRDInterface/XmlItemProperty.vb b/Interfaces/ZUGFeRDInterface/XmlItemProperty.vb index a14c456e..672dcce0 100644 --- a/Interfaces/ZUGFeRDInterface/XmlItemProperty.vb +++ b/Interfaces/ZUGFeRDInterface/XmlItemProperty.vb @@ -1,8 +1,14 @@ Public Class XmlItemProperty + Public IsRequired As Boolean + Public IsGrouped As Boolean + Public TableName As String Public TableColumn As String Public Description As String - Public IsRequired As Boolean - Public IsGrouped As Boolean Public GroupScope As String + + ''' + ''' Document version, eg. ZUGFeRD Schema version + ''' + Public Specification As String End Class \ No newline at end of file diff --git a/Jobs/Jobs.vbproj b/Jobs/Jobs.vbproj index 7aa0c6db..68e81994 100644 --- a/Jobs/Jobs.vbproj +++ b/Jobs/Jobs.vbproj @@ -116,9 +116,9 @@ - + False - ..\Interfaces\bin\Debug\DigitalData.Modules.Language.dll + ..\Language\bin\Debug\DigitalData.Modules.Language.dll ..\packages\FirebirdSql.Data.FirebirdClient.7.5.0\lib\net452\FirebirdSql.Data.FirebirdClient.dll diff --git a/Jobs/My Project/AssemblyInfo.vb b/Jobs/My Project/AssemblyInfo.vb index 9dac9b52..4625e99b 100644 --- a/Jobs/My Project/AssemblyInfo.vb +++ b/Jobs/My Project/AssemblyInfo.vb @@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices - + @@ -30,5 +30,5 @@ Imports System.Runtime.InteropServices ' Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern ' übernehmen, indem Sie "*" eingeben: - - + + diff --git a/Jobs/ZUGFeRD/EmailFunctions.vb b/Jobs/ZUGFeRD/EmailFunctions.vb index 9dd4c1ae..010de5b1 100644 --- a/Jobs/ZUGFeRD/EmailFunctions.vb +++ b/Jobs/ZUGFeRD/EmailFunctions.vb @@ -1,5 +1,6 @@ Imports DigitalData.Modules.Logging Imports DigitalData.Modules.Database +Imports DigitalData.Modules.Language Imports System.Data Imports System.IO Imports System.Data.SqlClient @@ -164,10 +165,19 @@ Public Class EmailFunctions _logger.Debug("Got Email Data for FileId {0}", MessageId) oRow = oDatatable.Rows.Item(oDatatable.Rows.Count - 1) + Dim oFromDefault = String.Format("No Sender found for ({0})", MessageId) + Dim oFrom = oRow.ItemEx("EMAIL_FROM", oFromDefault) + + Dim oAttachmentDefault = String.Format("No Attachment found for ({0})", MessageId) + Dim oAttachment = oRow.ItemEx("EMAIL_ATTMT1", oAttachmentDefault) + + Dim oSubjectDefault = String.Format("No Subject found for ({0})", MessageId) + Dim oSubject = oRow.ItemEx("EMAIL_SUBJECT", oSubjectDefault) + Return New EmailData() With { - .From = oRow.Item("EMAIL_FROM"), - .Attachment = oRow.Item("EMAIL_ATTMT1"), - .Subject = oRow.Item("EMAIL_SUBJECT") + .From = oFrom, + .Attachment = oAttachment, + .Subject = oSubject } Catch ex As Exception _logger.Warn("Could not fetch Email Data for FileId {0}", MessageId) diff --git a/Jobs/ZUGFeRD/EmailStrings.vb b/Jobs/ZUGFeRD/EmailStrings.vb index ed3b1639..fbecd5bb 100644 --- a/Jobs/ZUGFeRD/EmailStrings.vb +++ b/Jobs/ZUGFeRD/EmailStrings.vb @@ -20,9 +20,9 @@ Public Const EMAIL_MD5_ERROR = "

Die von Ihnen gesendete Rechnung wurde bereits von unserem System verarbeitet.

" - Public Const EMAIL_TOO_MUCH_FERDS = "

Ihre Email enthielt mehr als ein ZUGFeRD-Dokument.

" + Public Const EMAIL_TOO_MUCH_FERDS = "

In Ihrer Email ({0}) sind mehr als ein ZUGFeRD Dokument enthalten. Bitte prüfen Sie Rechnung an Anhänge. Nur eine Rechnung darf das ZUGFeRD-Format enthalten

" - Public Const EMAIL_NO_FERDS = "

Ihre Email enthielt keine ZUGFeRD-Dokumente.

" + Public Const EMAIL_NO_FERDS = "

Ihre Email ({0}) enthielt keine ZUGFeRD-Dokumente.

" Public Const EMAIL_FILE_SIZE_REACHED = "

Die von Ihnen gesendete Rechnung oder einer der Rechnungs-Anhänge überschreitet die erlaubte Größe von {0} MB.

@@ -32,13 +32,13 @@ " Public Const EMAIL_INVALID_DOCUMENT = " -

Ihre Email enthielt ein ZUGFeRD Dokument, welches aber inkorrekt formatiert wurde.

+

Ihre Email ({0}) enthielt ein ZUGFeRD Dokument, welches aber inkorrekt formatiert wurde.

Mögliche Gründe für ein inkorrektes Format:

  • Betrags-Werte weisen ungültiges Format auf (25,01 anstatt 25.01)

" Public Const EMAIL_UNSUPPORTED_DOCUMENT = " -

Ihre Email enthielt ein ZUGFeRD Dokument ({0}), welches zur Zeit noch nicht unsterstützt wird.

+

Ihre Email ({0}) enthielt ein ZUGFeRD Format ({1}), welches zur Zeit noch nicht freigeschaltet ist.

" End Class diff --git a/Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb b/Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb index 226ada45..88b98b26 100644 --- a/Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb +++ b/Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb @@ -206,7 +206,9 @@ Public Class ImportZUGFeRDFiles ' different versions of ZUGFeRD and the type is unknown at compile-time. ' 17.11.2022: oDocument is now a Tuple of (String, Object), to be able to return the filename ' of the extracted xml file. - Dim oDocument As Tuple(Of String, Object) + ' 21.12.2022: oDocument is now an object of type ZugferdResult to be able to save + ' the new meta data, ie. the type of schema (zugferd version) + Dim oDocument As ZUGFeRDInterface.ZugferdResult ' Start a global group counter for each file Dim oGlobalGroupCounter = 0 @@ -283,7 +285,8 @@ Public Class ImportZUGFeRDFiles ' Check the document against the configured property map and return: ' - a List of valid properties ' - a List of missing properties - Dim oCheckResult = _zugferd.PropertyValues.CheckPropertyValues(oDocument, oArgs.PropertyMap, oMessageId) + Dim oPropertyMap = GetPropertyMapFor(oArgs, oDocument.Specification) + Dim oCheckResult = _zugferd.PropertyValues.CheckPropertyValues(oDocument.SchemaObject, oPropertyMap, oMessageId) _logger.Info("Properties checked: [{0}] missing properties / [{1}] valid properties found.", oCheckResult.MissingProperties.Count, oCheckResult.ValidProperties.Count) @@ -398,8 +401,9 @@ Public Class ImportZUGFeRDFiles ' That 's why we set it to String.Empty here. Create_HistoryEntry(oMessageId, String.Empty, "REJECTED - ZUGFeRD yes but unsupported format", oFBTransaction) - Dim oBody As String = String.Format(EmailStrings.EMAIL_UNSUPPORTED_DOCUMENT, ex.XmlFile) Dim oEmailData = MoveAndRenameEmailToRejected(oArgs, oMessageId) + Dim oBody As String = String.Format(EmailStrings.EMAIL_UNSUPPORTED_DOCUMENT, oEmailData.Subject, ex.XmlFile) + _email.AddToEmailQueueMSSQL(oMessageId, oBody, oEmailData, "UnsupportedFerdException", _EmailOutAccountId, oArgs.NamePortal) AddRejectedState(oMessageId, "UnsupportedFerdException", "Nicht unterstütztes Datenformat", "", oSQLTransaction) @@ -410,8 +414,9 @@ Public Class ImportZUGFeRDFiles ' That 's why we set it to String.Empty here. Create_HistoryEntry(oMessageId, String.Empty, "REJECTED - ZUGFeRD yes but incorrect format", oFBTransaction) - Dim oBody = EmailStrings.EMAIL_INVALID_DOCUMENT Dim oEmailData = MoveAndRenameEmailToRejected(oArgs, oMessageId) + Dim oBody = String.Format(EmailStrings.EMAIL_INVALID_DOCUMENT, oEmailData.Subject) + _email.AddToEmailQueueMSSQL(oMessageId, oBody, oEmailData, "InvalidFerdException", _EmailOutAccountId, oArgs.NamePortal) AddRejectedState(oMessageId, "InvalidFerdException", "Inkorrekte Formate", "", oSQLTransaction) @@ -420,8 +425,9 @@ Public Class ImportZUGFeRDFiles Create_HistoryEntry(oMessageId, oMD5CheckSum, "REJECTED - More than one ZUGFeRD-document in email", oFBTransaction) - Dim oBody = EmailStrings.EMAIL_TOO_MUCH_FERDS Dim oEmailData = MoveAndRenameEmailToRejected(oArgs, oMessageId) + Dim oBody = String.Format(EmailStrings.EMAIL_TOO_MUCH_FERDS, oEmailData.Subject) + _email.AddToEmailQueueMSSQL(oMessageId, oBody, oEmailData, "TooMuchFerdsException", _EmailOutAccountId, oArgs.NamePortal) AddRejectedState(oMessageId, "TooMuchFerdsException", "Email enthielt mehr als ein ZUGFeRD-Dokument", "", oSQLTransaction) @@ -430,8 +436,9 @@ Public Class ImportZUGFeRDFiles Create_HistoryEntry(oMessageId, oMD5CheckSum, "REJECTED - no ZUGFeRD-Document in email", oFBTransaction) - Dim oBody = EmailStrings.EMAIL_NO_FERDS Dim oEmailData = MoveAndRenameEmailToRejected(oArgs, oMessageId) + Dim oBody = String.Format(EmailStrings.EMAIL_NO_FERDS, oEmailData.Subject) + _email.AddToEmailQueueMSSQL(oMessageId, oBody, oEmailData, "NoFerdsException", _EmailOutAccountId, oArgs.NamePortal) AddRejectedState(oMessageId, "NoFerdsException", " Email enthielt keine ZUGFeRD-Dokumente", "", oSQLTransaction) @@ -576,6 +583,26 @@ Public Class ImportZUGFeRDFiles End Try End Sub + Private Function GetPropertyMapFor(pWorkerArgs As WorkerArgs, pSpecification As String) As Dictionary(Of String, XmlItemProperty) + Dim oPropertyMap = DoGetPropertyMapFor(pWorkerArgs, pSpecification) + + _logger.Debug("Found [{0}] Properties for Specification [{1}].", oPropertyMap.Count, pSpecification) + + ' If no properties were found, fall back to the default specification before giving up + If oPropertyMap.Count = 0 Then + _logger.Warn("No Properties found for Specification [{0}]. Loading default property map!", pSpecification) + Return DoGetPropertyMapFor(pWorkerArgs, ZUGFeRDInterface.ZUGFERD_SPEC_DEFAULT) + End If + + Return oPropertyMap + End Function + + Private Function DoGetPropertyMapFor(pWorkerArgs As WorkerArgs, pSpecification As String) As Dictionary(Of String, XmlItemProperty) + Return pWorkerArgs.PropertyMap. + Where(Function(kv) kv.Value.Specification = pSpecification). + ToDictionary(Function(kv) kv.Key, Function(kv) kv.Value) + End Function + Private Sub MoveFiles( Args As WorkerArgs, MessageId As String, diff --git a/Jobs/ZUGFeRD/WorkerArgs.vb b/Jobs/ZUGFeRD/WorkerArgs.vb index a34a90b0..ba9ac70d 100644 --- a/Jobs/ZUGFeRD/WorkerArgs.vb +++ b/Jobs/ZUGFeRD/WorkerArgs.vb @@ -26,4 +26,6 @@ Public Class WorkerArgs Public AllowFacturX As Boolean = True Public AllowXRechnung As Boolean = True + Public AllowZugferd10 As Boolean = True + Public AllowZugferd2x As Boolean = True End Class \ No newline at end of file diff --git a/Logging/LogConfig.vb b/Logging/LogConfig.vb index 8a9d1458..77450f51 100644 --- a/Logging/LogConfig.vb +++ b/Logging/LogConfig.vb @@ -75,12 +75,14 @@ Public Class LogConfig Private Const FILE_NAME_FORMAT_DEBUG As String = "${shortdate}-${var:product}${var:suffix}${event-properties:item=ModuleName}-Debug.log" Private Const FILE_NAME_FORMAT_TRACE As String = "${shortdate}-${var:product}${var:suffix}${event-properties:item=ModuleName}-Trace.log" Private Const FILE_NAME_FORMAT_ERROR As String = "${shortdate}-${var:product}${var:suffix}${event-properties:item=ModuleName}-Error.log" + Private Const FILE_NAME_FORMAT_JSON As String = "${shortdate}-${var:product}${var:suffix}${event-properties:item=ModuleName}.log.json" Private Const TARGET_DEFAULT As String = "defaultTarget" Private Const TARGET_ERROR_EX As String = "errorExceptionTarget" Private Const TARGET_ERROR As String = "errorTarget" Private Const TARGET_DEBUG As String = "debugTarget" Private Const TARGET_TRACE As String = "traceTarget" + Private Const TARGET_JSON As String = "jsonTarget" 'Private Const TARGET_MEMORY As String = "memoryTarget" Private Const LOG_FORMAT_BASE As String = "${time}|${logger:shortName=True}|${level:uppercase=true}" @@ -101,8 +103,9 @@ Public Class LogConfig Private ReadOnly _basePath As String = _failSafePath Private _config As LoggingConfiguration - Private _isDebug As Boolean = False - Private _isTrace As Boolean = False + Private _EnableDebugLogging As Boolean = False + Private _EnableTraceLogging As Boolean = False + Private _EnableJsonLogging As Boolean = False #End Region #Region "Public Properties" @@ -137,21 +140,31 @@ Public Class LogConfig ''' True, if debug log will be written. False otherwise. Public Property Debug As Boolean Get - Return _isDebug + Return _EnableDebugLogging End Get - Set(isDebug As Boolean) - _isDebug = isDebug - ReloadConfig(isDebug, _isTrace) + Set(value As Boolean) + _EnableDebugLogging = value + ReloadConfig() End Set End Property Public Property Trace As Boolean Get - Return _isTrace + Return _EnableTraceLogging End Get - Set(isTrace As Boolean) - _isTrace = isTrace - ReloadConfig(_isDebug, isTrace) + Set(value As Boolean) + _EnableTraceLogging = value + ReloadConfig() + End Set + End Property + + Public Property EnableJsonLog As Boolean + Get + Return _EnableJsonLogging + End Get + Set(value As Boolean) + _EnableJsonLogging = value + ReloadConfig() End Set End Property @@ -443,6 +456,7 @@ Public Class LogConfig _config.AddTarget(TARGET_DEFAULT, GetDefaultLogTarget(_basePath)) _config.AddTarget(TARGET_DEBUG, GetDebugLogTarget(_basePath)) _config.AddTarget(TARGET_TRACE, GetTraceLogTarget(_basePath)) + _config.AddTarget(TARGET_JSON, GetJsonLogTarget(_basePath)) '_config.AddTarget(TARGET_MEMORY, GetMemoryDebugTarget()) ' Add default rules @@ -478,9 +492,7 @@ Public Class LogConfig ''' ''' Reconfigures and re-adds all loggers, optionally adding the debug rule. ''' - ''' Adds the Debug rule if true. - ''' Adds the Trace rule if true. - Private Sub ReloadConfig(Optional Debug As Boolean = False, Optional Trace As Boolean = False) + Private Sub ReloadConfig() Dim oLogger = GetLogger() ' Clear Logging Rules @@ -489,15 +501,22 @@ Public Class LogConfig ' Add default rules AddDefaultRules(_config) + ' Add json rule, if configured + If _EnableJsonLogging = True Then + oLogger.Info("JSON Logging is now Enabled.") + _config.AddRule(LogLevel.Debug, LogLevel.Error, TARGET_JSON) + End If + ' Add debug rule, if configured - If Debug = True Then + If _EnableDebugLogging = True Then _config.AddRule(LogLevel.Debug, LogLevel.Error, TARGET_DEBUG) oLogger.Info("DEBUG Logging is now Enabled.") Else oLogger.Debug("DEBUG Logging is now Disabled.") End If - If Trace = True Then + ' Add trace rule, if configured + If _EnableTraceLogging = True Then _config.AddRule(LogLevel.Trace, LogLevel.Error, TARGET_TRACE) End If @@ -506,6 +525,29 @@ Public Class LogConfig End Sub #Region "Log Targets" + Private Function GetJsonLogTarget(basePath As String) As FileTarget + Dim oJsonLayout = New Layouts.JsonLayout + oJsonLayout.Attributes.Add(New Layouts.JsonAttribute("level", "${level}")) + oJsonLayout.Attributes.Add(New Layouts.JsonAttribute("message", "${message}")) + oJsonLayout.Attributes.Add(New Layouts.JsonAttribute("date", "${shortdate}")) + oJsonLayout.Attributes.Add(New Layouts.JsonAttribute("product", "${var:product}")) + oJsonLayout.Attributes.Add(New Layouts.JsonAttribute("suffix", "${var:suffix}")) + oJsonLayout.Attributes.Add(New Layouts.JsonAttribute("module", "${event-properties:item=ModuleName}")) + oJsonLayout.Attributes.Add(New Layouts.JsonAttribute("exception", "${exception:format=Message,StackTrace:innerFormat=Message:maxInnerExceptionLevel=3}")) + + Dim jsonLog As New FileTarget() With { + .FileName = Path.Combine(basePath, FILE_NAME_FORMAT_JSON), + .Name = TARGET_JSON, + .Layout = oJsonLayout, + .MaxArchiveFiles = MAX_ARCHIVE_FILES_DEFAULT, + .ArchiveEvery = ARCHIVE_EVERY, + .KeepFileOpen = KEEP_FILES_OPEN, + .Encoding = Text.Encoding.Unicode + } + + Return jsonLog + End Function + Private Function GetDefaultLogTarget(basePath As String) As FileTarget Dim defaultLog As New FileTarget() With { .FileName = Path.Combine(basePath, FILE_NAME_FORMAT_DEFAULT), @@ -565,9 +607,9 @@ Public Class LogConfig End Function Private Function GetTraceLogTarget(basePath As String) As FileTarget - Dim debugLog As New FileTarget() With { + Dim traceLog As New FileTarget() With { .FileName = Path.Combine(basePath, FILE_NAME_FORMAT_TRACE), - .Name = TARGET_DEBUG, + .Name = TARGET_TRACE, .Layout = LOG_FORMAT_DEBUG, .MaxArchiveFiles = MAX_ARCHIVE_FILES_DEBUG_DETAIL, .ArchiveEvery = ARCHIVE_EVERY, @@ -578,18 +620,7 @@ Public Class LogConfig .Encoding = Text.Encoding.Unicode } - Return debugLog + Return traceLog End Function - - 'Private Function GetMemoryDebugTarget() As MemoryTarget - ' Dim memoryLog As New MemoryTarget() With { - ' .Layout = LOG_FORMAT_DEBUG, - ' .Name = TARGET_MEMORY, - ' .OptimizeBufferReuse = True, - ' .MaxLogsCount = MAX_MEMORY_LOG_COUNT - ' } - - ' Return memoryLog - 'End Function #End Region End Class diff --git a/Logging/My Project/AssemblyInfo.vb b/Logging/My Project/AssemblyInfo.vb index 196b58c3..b416a8b8 100644 --- a/Logging/My Project/AssemblyInfo.vb +++ b/Logging/My Project/AssemblyInfo.vb @@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices - + @@ -31,5 +31,5 @@ Imports System.Runtime.InteropServices ' übernehmen, indem Sie "*" eingeben: ' - - + + diff --git a/Messaging/Messaging.vbproj b/Messaging/Messaging.vbproj index 76b42b35..699ccaad 100644 --- a/Messaging/Messaging.vbproj +++ b/Messaging/Messaging.vbproj @@ -98,6 +98,10 @@ True + + + +
diff --git a/Messaging/My Project/AssemblyInfo.vb b/Messaging/My Project/AssemblyInfo.vb index c306c7fc..1c0d3e79 100644 --- a/Messaging/My Project/AssemblyInfo.vb +++ b/Messaging/My Project/AssemblyInfo.vb @@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices - + diff --git a/Messaging/WCF/Binding.vb b/Messaging/WCF/Binding.vb new file mode 100644 index 00000000..7ff1cf49 --- /dev/null +++ b/Messaging/WCF/Binding.vb @@ -0,0 +1,31 @@ +Imports System.Net +Imports System.ServiceModel +Imports System.Xml + +Namespace WCF + Public Class Binding + Public Shared Function GetBinding(Optional AuthenticationMode As TcpClientCredentialType = TcpClientCredentialType.Windows) As NetTcpBinding + Return New NetTcpBinding() With { + .MaxReceivedMessageSize = Constants.ChannelSettings.MAX_RECEIVED_MESSAGE_SIZE, + .MaxBufferSize = Constants.ChannelSettings.MAX_BUFFER_SIZE, + .MaxBufferPoolSize = Constants.ChannelSettings.MAX_BUFFER_POOL_SIZE, + .TransferMode = TransferMode.Streamed, + .Security = New NetTcpSecurity() With { + .Mode = SecurityMode.Transport, + .Transport = New TcpTransportSecurity() With { + .ClientCredentialType = AuthenticationMode + } + }, + .ReaderQuotas = New XmlDictionaryReaderQuotas() With { + .MaxArrayLength = Constants.ChannelSettings.MAX_ARRAY_LENGTH, + .MaxStringContentLength = Constants.ChannelSettings.MAX_STRING_CONTENT_LENGTH + } + } + End Function + + Public Shared Function GetAddress(pHost As String, pPort As Integer, pName As String) As Uri + Return New Uri($"net.tcp://{pHost}:{pPort}/DigitalData/Services/{pName}") + End Function + End Class + +End Namespace \ No newline at end of file diff --git a/Messaging/WCF/Channel.vb b/Messaging/WCF/Channel.vb new file mode 100644 index 00000000..5d05e590 --- /dev/null +++ b/Messaging/WCF/Channel.vb @@ -0,0 +1,52 @@ +Imports System.ServiceModel +Imports System.Xml +Imports DigitalData.Modules.Base +Imports DigitalData.Modules.Logging + +Namespace WCF + Public Class Channel(Of TChannel As IClientChannel) + Inherits BaseClass + + Private ReadOnly ChannelFactory As ChannelFactory(Of TChannel) + + Public Event Reconnect As EventHandler + + Public Sub New(pLogConfig As LogConfig, pServerAddress As ServerAddress, Optional pName As String = "Main") + MyBase.New(pLogConfig) + ChannelFactory = GetChannelFactory(pServerAddress, pName) + End Sub + + ''' + ''' Creates a channel and adds a Faulted-Handler + ''' + ''' A channel object + Public Function GetChannel() As TChannel + Try + Logger.Debug("Creating channel.") + Dim oChannel = ChannelFactory.CreateChannel() + + AddHandler oChannel.Faulted, Sub() RaiseEvent Reconnect(Me, Nothing) + + Return oChannel + Catch ex As Exception + Logger.Error(ex) + Throw ex + End Try + End Function + + ''' + ''' Creates and returns a channel factory with the supplied name and address + ''' + ''' The service name, will be: net.tcp://ip:port/DigitalData/Services/[name] + ''' The service address, in the form of ip address and port + ''' + Private Function GetChannelFactory(pAddress As ServerAddress, pName As String) As ChannelFactory(Of TChannel) + Dim oBinding = Binding.GetBinding() + Dim oAddress = New EndpointAddress(Binding.GetAddress(pAddress.Host, pAddress.Port, pName)) + Dim oFactory = New ChannelFactory(Of TChannel)(oBinding, oAddress) + Return oFactory + End Function + + + End Class +End Namespace diff --git a/Messaging/WCF/Constants.vb b/Messaging/WCF/Constants.vb new file mode 100644 index 00000000..3f34b90f --- /dev/null +++ b/Messaging/WCF/Constants.vb @@ -0,0 +1,20 @@ +Namespace WCF + Public Class Constants + Public Const DEFAULT_SERVICE_PORT = 9000 + + ''' + ''' Infos about MaxBufferSize and MaxBufferPoolSize + ''' https://social.msdn.microsoft.com/Forums/vstudio/en-US/d6e234d3-942f-4e9d-8470-32618d3f3212/maxbufferpoolsize-vs-maxbuffersize?forum=wcf + ''' + Public Class ChannelSettings + Public Const MAX_RECEIVED_MESSAGE_SIZE = 2147483647 ' 1GB + Public Const MAX_BUFFER_SIZE = 104857600 ' 100MB + Public Const MAX_BUFFER_POOL_SIZE = 1048576 ' 1MB + + Public Const MAX_CONNECTIONS = 500 + Public Const MAX_ARRAY_LENGTH = 2147483647 + Public Const MAX_STRING_CONTENT_LENGTH = 2147483647 + End Class + End Class + +End Namespace diff --git a/Messaging/WCF/ServerAddress.vb b/Messaging/WCF/ServerAddress.vb new file mode 100644 index 00000000..aa63cac7 --- /dev/null +++ b/Messaging/WCF/ServerAddress.vb @@ -0,0 +1,7 @@ +Namespace WCF + Public Structure ServerAddress + Public Host As String + Public Port As Integer + End Structure + +End Namespace \ No newline at end of file diff --git a/Windream/Windream.vb b/Windream/Windream.vb index d7f5f53f..71bcec9e 100644 --- a/Windream/Windream.vb +++ b/Windream/Windream.vb @@ -637,6 +637,7 @@ Public Class Windream Return False End Try End Function + ''' ''' Archives windream object immediately ''' @@ -653,7 +654,7 @@ Public Class Windream End Try End Function - Public Function NewFolder(Path As String, pExtension As String) As Boolean + Public Function NewFolder(Path As String) As Boolean If Not TestSessionLoggedIn() Then Return False End If @@ -664,13 +665,11 @@ Public Class Windream Dim oFolderObject As WMObject Dim oCurrentPath As String = String.Empty - For Each oFolder In oFolders - If oFolder.ToString.EndsWith(pExtension) Then - Exit For - ElseIf oFolder = String.Empty Then + If oFolder = String.Empty Then Continue For End If + oCurrentPath = Combine(oCurrentPath, oFolder) If TestFolderExists(oCurrentPath) = False Then @@ -706,13 +705,13 @@ Public Class Windream Public Function NewFileStream(ByVal FilenameSource As String, ByVal FilenameTarget As String, ByVal WMObjecttypeName As String) As Boolean NewDocumentID = 0 - Dim oExtension As String = Path.GetExtension(FilenameSource) If Not TestSessionLoggedIn() Then Return False End If Dim oTargetDrive As String = Path.GetDirectoryName(FilenameTarget) + FilenameTarget = GetNormalizedPath(FilenameTarget, True) _logger.Debug($"Preparing to stream file from {FilenameSource} to {FilenameTarget}") @@ -721,7 +720,7 @@ Public Class Windream Dim oFileIO As WMFileIO Dim oWMStream As WMStream - NewFolder(FilenameTarget, oExtension) + NewFolder(oTargetDrive) 'Indexierungsdialog der Session unterdrücken Session.SwitchEvents(Constants.COM_EVENT_SESSION_NEED_INDEX, False) @@ -777,7 +776,6 @@ Public Class Windream oWMStream.Close() _logger.Debug("Saving new object") - oWMObject.aObjectType = GetObjectByName(WMObjecttypeName, WMEntityObjectType) oWMObject.Save() @@ -817,24 +815,20 @@ Public Class Windream End Try End Function - Public Function GetNormalizedPath(Path As String, pCleanPath As Boolean) As String - _logger.Debug("Normalizing Path: [{0}]", Path) - Dim oNormalizedPath As String = Path - If pCleanPath = True Then - oNormalizedPath = Language.Utils.RemoveInvalidCharacters(Path) - _logger.Debug("path after RemoveInvalidCharacters: [{0}]", oNormalizedPath) - End If - Try - ' Convert any forward slashes / and double slashes \\ into backslashes \ - ' See: https://stackoverflow.com/questions/3144492/how-do-i-get-nets-path-combine-to-convert-forward-slashes-to-backslashes - If IsPathRooted(oNormalizedPath) Then - oNormalizedPath = GetFullPath(oNormalizedPath) - End If + Public Function GetNormalizedPath(pPath As String, pCleanPath As Boolean) As String + _logger.Debug("Normalizing Path: [{0}]", pPath) + Dim oNormalizedPath As String = pPath + If pCleanPath = True Then + oNormalizedPath = Utils.RemoveInvalidCharacters(pPath) + _logger.Debug("Path after RemoveInvalidCharacters: [{0}]", oNormalizedPath) + End If + + Try ' Remove Driveletter, eg. W:\ If oNormalizedPath.StartsWith($"{ClientDriveLetter}:\") Then oNormalizedPath = oNormalizedPath.Substring(ClientDriveLetter.Length + 2) - _logger.Debug($"path after replaced ClientDriveLetter: [{oNormalizedPath}]") + _logger.Debug($"Path after replaced ClientDriveLetter: [{oNormalizedPath}]") End If ' Remove Windream Base Path, eg. \\windream\objects\ @@ -845,22 +839,41 @@ Public Class Windream ' Handle misconfigured drive-letter If oNormalizedPath.Contains(":") Then - _logger.Warn($"oNormalizedPath still contains a drive name!!") + _logger.Warn($"NormalizedPath [{oNormalizedPath}] still contains a drive name!!") _logger.Warn($"Check Your config ClientDriveLetter [{ClientDriveLetter}] // ClientBasePath [{ClientBasePath}]") oNormalizedPath = oNormalizedPath.Substring(3) End If + + ' Convert any forward slashes / and double slashes \\ into backslashes \ + ' See: https://stackoverflow.com/questions/3144492/how-do-i-get-nets-path-combine-to-convert-forward-slashes-to-backslashes + If IsPathRooted(oNormalizedPath) Then + ' This breaks because it converts the path "\SomeFolder" into "C:\SomeFolder" LOL + 'oNormalizedPath = GetFullPath(oNormalizedPath) + ' Lets just be pragmatic here + oNormalizedPath = oNormalizedPath.Replace("\\", "\") + oNormalizedPath = oNormalizedPath.Replace("/", "\") + + _logger.Debug("Path after converting slashes: [{0}]", oNormalizedPath) + End If + If oNormalizedPath.StartsWith("\") = False Then oNormalizedPath = $"\{oNormalizedPath}" End If - _logger.Debug($"oNormalizedPath: [{oNormalizedPath}]") + + _logger.Debug($"NormalizedPath: [{oNormalizedPath}]") Return oNormalizedPath Catch ex As Exception - _logger.Warn($"Unexpected error in GetNormalizedPath - oNormalizedPath [{oNormalizedPath}] - Error: [{ex.Message}]") + _logger.Warn($"Unexpected error in GetNormalizedPath - NormalizedPath [{oNormalizedPath}] - Error: [{ex.Message}]") Return "" End Try End Function + Public Function GetAbsolutePath(pPath As String) As String + Dim oNormalizedPath = GetNormalizedPath(pPath, False) + Return $"\\windream\objects{oNormalizedPath}" + End Function + ''' ''' Returns the result of a search file '''