From 65d58a227417226d74bc3a673737ce1929b6941c Mon Sep 17 00:00:00 2001 From: Jonathan Jenne Date: Wed, 21 Dec 2022 14:02:44 +0100 Subject: [PATCH] Zugferd: prepare loading different specs per document schema version --- Interfaces/ZUGFeRDInterface.vb | 76 ++++++++++++------- .../ZUGFeRDInterface/XmlItemProperty.vb | 10 ++- Jobs/Jobs.vbproj | 4 +- Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb | 31 ++++++-- 4 files changed, 85 insertions(+), 36 deletions(-) diff --git a/Interfaces/ZUGFeRDInterface.vb b/Interfaces/ZUGFeRDInterface.vb index 0c04fed8..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, @@ -39,6 +46,13 @@ Public Class ZUGFeRDInterface 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 + ''' ''' Create a new instance of ZUGFeRDInterface ''' @@ -91,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 ''' @@ -106,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 ''' @@ -122,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 @@ -150,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 @@ -172,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 @@ -202,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 @@ -216,37 +233,41 @@ 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 oObject As Object = Nothing + Dim oSpecification As String = Nothing - Dim oAllowedTypes As New List(Of Type) + Dim oAllowedTypes As New Dictionary(Of String, Type) If _Options.AllowZugferd_1_0_Schema Then - oAllowedTypes.Add(GetType(ZUGFeRD.Version1_0.CrossIndustryDocumentType)) + oAllowedTypes.Add(ZUGFERD_SPEC_10, GetType(ZUGFeRD.Version1_0.CrossIndustryDocumentType)) End If If _Options.AllowZugferd_2_x_Schema Then - oAllowedTypes.AddRange(New List(Of Type) From { - GetType(ZUGFeRD.Version2_0.CrossIndustryInvoiceType), - GetType(ZUGFeRD.Version2_1_1.CrossIndustryInvoiceType), - GetType(ZUGFeRD.Version2_2_FacturX.CrossIndustryInvoiceType) - }) + 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 - _logger.Debug("Trying Type [{0}]", oType.FullName) - Dim oSerializer As New XmlSerializer(oType) + Dim oTypeName As String = oType.Value.FullName + Dim oSerializer As New XmlSerializer(oType.Value) + _logger.Debug("Trying Type [{0}]", oTypeName) Try oReader = oNavigator.ReadSubtree() + oObject = oSerializer.Deserialize(oReader) - _logger.Debug("Serializing with type [{0}] succeeded", oType.FullName) + 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 @@ -256,7 +277,10 @@ Public Class ZUGFeRDInterface Throw New ApplicationException("No Types matched the given document. Document could not be serialized.") End If - Return New Tuple(Of String, Object)(pDocument.Item1, oObject) + pResult.Specification = oSpecification + pResult.SchemaObject = oObject + + Return pResult Catch ex As Exception _logger.Error(ex) 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 eb622bad..aa97236c 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/ZUGFeRD/ImportZUGFeRDFiles.vb b/Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb index 5c61659e..f8a0171b 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) + 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,7 +401,6 @@ 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) @@ -412,7 +414,6 @@ 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) @@ -424,7 +425,6 @@ 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) @@ -436,7 +436,6 @@ 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) @@ -584,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,