Zugferd: prepare loading different specs per document schema version

This commit is contained in:
Jonathan Jenne 2022-12-21 14:02:44 +01:00
parent 902c835c37
commit 65d58a2274
4 changed files with 86 additions and 37 deletions

View File

@ -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
''' <summary>
''' Create a new instance of ZUGFeRDInterface
''' </summary>
@ -91,14 +105,14 @@ Public Class ZUGFeRDInterface
''' </summary>
''' <param name="Path"></param>
''' <exception cref="ZUGFeRDExecption"></exception>
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
''' <summary>
@ -106,14 +120,14 @@ Public Class ZUGFeRDInterface
''' </summary>
''' <param name="Stream"></param>
''' <exception cref="ZUGFeRDExecption"></exception>
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
''' <summary>
@ -122,7 +136,7 @@ Public Class ZUGFeRDInterface
''' <param name="pStream"></param>
''' <exception cref="ZUGFeRDExecption"></exception>
''' <returns>The embedded xml data as an XPath document</returns>
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
''' <param name="pPath"></param>
''' <exception cref="ZUGFeRDExecption"></exception>
''' <returns>The embedded xml data as an XPath document</returns>
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 oAllowedTypes As New List(Of Type)
Dim oObject As Object = Nothing
Dim oSpecification As String = Nothing
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)

View File

@ -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
''' <summary>
''' Document version, eg. ZUGFeRD Schema version
''' </summary>
Public Specification As String
End Class

View File

@ -116,9 +116,9 @@
</Compile>
</ItemGroup>
<ItemGroup>
<Reference Include="DigitalData.Modules.Language, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="DigitalData.Modules.Language, Version=1.7.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Interfaces\bin\Debug\DigitalData.Modules.Language.dll</HintPath>
<HintPath>..\Language\bin\Debug\DigitalData.Modules.Language.dll</HintPath>
</Reference>
<Reference Include="FirebirdSql.Data.FirebirdClient, Version=7.5.0.0, Culture=neutral, PublicKeyToken=3750abcc3150b00c, processorArchitecture=MSIL">
<HintPath>..\packages\FirebirdSql.Data.FirebirdClient.7.5.0\lib\net452\FirebirdSql.Data.FirebirdClient.dll</HintPath>

View File

@ -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,