Interfaces/Job: Add check for currencyId format in ZUGFeRD documents

This commit is contained in:
Jonathan Jenne 2023-05-26 15:04:44 +02:00
parent f491d4dd24
commit 76cba215fe
4 changed files with 126 additions and 3 deletions

View File

@ -1,4 +1,6 @@
Imports System.IO
Imports System.Collections.Generic
Imports System.IO
Imports System.Reflection.Emit
Imports System.Xml
Imports System.Xml.Serialization
Imports System.Xml.XPath
@ -54,6 +56,13 @@ Public Class ZUGFeRDInterface
Public Property XPathObject As XPathDocument
Public Property SchemaObject As Object
Public Property Specification As String
Public Property ValidationErrors As New List(Of ZugferdValidationError)
End Class
Public Class ZugferdValidationError
Public ElementName As String
Public ElementValue As String
Public ErrorMessage As String
End Class
''' <summary>
@ -125,9 +134,10 @@ Public Class ZUGFeRDInterface
''' <exception cref="ZUGFeRDExecption"></exception>
Public Function ExtractZUGFeRDFileWithGDPicture(Path As String) As ZugferdResult
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Path)
oResult = ValidateZUGFeRDDocument(oResult)
'If IsNothing(oResult.SchemaObject) Then
' Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.")
Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.")
'End If
Return SerializeZUGFeRDDocument(oResult)
@ -140,6 +150,7 @@ Public Class ZUGFeRDInterface
''' <exception cref="ZUGFeRDExecption"></exception>
Public Function ExtractZUGFeRDFileWithGDPicture(Stream As Stream) As ZugferdResult
Dim oResult = ValidateZUGFeRDFileWithGDPicture(Stream)
oResult = ValidateZUGFeRDDocument(oResult)
'If IsNothing(oResult.SchemaObject) Then
' Throw New ZUGFeRDExecption(ErrorType.NoZugferd, "Datei ist keine ZUGFeRD Datei.")
@ -256,6 +267,72 @@ Public Class ZUGFeRDInterface
Public Specification As String
End Class
Public Function ValidateZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
Dim oNavigator As XPathNavigator = pResult.XPathObject.CreateNavigator()
Dim oNamespaceManager As New XmlNamespaceManager(oNavigator.NameTable)
oNamespaceManager.AddNamespace("ram", "urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12")
oNamespaceManager.AddNamespace("rsm", "urn:ferd:CrossIndustryDocument:invoice:1p0")
' CurrencyCode Nodes
Try
Dim oCurrencyCodeIterator As XPathNodeIterator = oNavigator.
Select("//ram:InvoiceCurrencyCode | //ram:TaxCurrencyCode | //ram:TaxCurrencyCode | //ram:SourceCurrencyCode", oNamespaceManager)
While oCurrencyCodeIterator.MoveNext()
Dim oNode As XPathNavigator = oCurrencyCodeIterator.Current
Dim oValid = ValidateCurrencyCode(oNode.Value)
If oValid = False Then
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
.ElementName = oNode.Name,
.ElementValue = oNode.Value,
.ErrorMessage = "Invalid CurrencyCode. Only 3-Character codes are allowed."
})
End If
End While
Catch ex As Exception
_logger.Error(ex)
End Try
' currencyID
Try
Dim oCurrencyIDIterator As XPathNodeIterator = oNavigator.Select("//*[@currencyID]")
While oCurrencyIDIterator.MoveNext()
Dim oNode As XPathNavigator = oCurrencyIDIterator.Current
Dim oCurrencyID As String = oNode.GetAttribute("currencyID", "")
' CurrencyID is optional per spec
If String.IsNullOrWhiteSpace(oCurrencyID) Then
Continue While
End If
Dim oValid = ValidateCurrencyCode(oCurrencyID)
If oValid = False Then
pResult.ValidationErrors.Add(New ZugferdValidationError() With {
.ElementName = oNode.Name,
.ElementValue = oCurrencyID,
.ErrorMessage = "Invalid currencyID. Only 3-Character codes or empty values are allowed."
})
End If
End While
Catch ex As Exception
_logger.Error(ex)
End Try
Return pResult
End Function
Private Function ValidateCurrencyCode(pValue As String) As Boolean
Dim oValueRegex As New Text.RegularExpressions.Regex("[A-Z]{3}")
If oValueRegex.IsMatch(pValue) = False Then
Return False
End If
Return True
End Function
Public Function SerializeZUGFeRDDocument(pResult As ZugferdResult) As ZugferdResult
Try
Dim oNavigator As XPathNavigator = pResult.XPathObject.CreateNavigator()

View File

@ -1,4 +1,6 @@
Imports System.IO
Imports System.Collections.Generic
Imports System.IO
Imports DigitalData.Modules.Interfaces.ZUGFeRDInterface
Public Class Exceptions
Public Class MissingValueException
@ -75,4 +77,14 @@ Public Class Exceptions
MyBase.New(pInfo)
End Sub
End Class
Public Class ValidationException
Inherits ApplicationException
Public ValidationErrors As List(Of ZugferdValidationError)
Public Sub New()
MyBase.New("ZUGFeRD document found but validation failed!")
End Sub
End Class
End Class

View File

@ -20,6 +20,10 @@
Public Const EMAIL_MD5_ERROR = "<p>Die von Ihnen gesendete Rechnung wurde bereits von unserem System verarbeitet.</p>"
Public Const EMAIL_VALIDATION_ERROR = "
<p>Die von Ihnen gesendete Rechnung hat die ZUGFeRD Validierung nicht bestanden.</p>
<p>Die folgenden Felder sind nicht korrekt:<ul>{0}</ul></p>"
Public Const EMAIL_TOO_MUCH_FERDS = "<p>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</p>"
Public Const EMAIL_NO_FERDS = "<p>Ihre Email ({0}) enthielt keine ZUGFeRD-Dokumente.</p>"

View File

@ -254,6 +254,7 @@ Public Class ImportZUGFeRDFiles
Try
oDocument = _zugferd.ExtractZUGFeRDFileWithGDPicture(oFile.FullName)
Catch ex As ZUGFeRDExecption
Select Case ex.ErrorType
Case ZUGFeRDInterface.ErrorType.NoZugferd
@ -275,6 +276,17 @@ Public Class ImportZUGFeRDFiles
End Select
End Try
' These validation errors check the document according to the specification.
' Things like this will be checked:
' - Currency Codes
' - Country Codes
' - DateTime Formats
If oDocument.ValidationErrors.Any() Then
Throw New ValidationException() With {
.ValidationErrors = oDocument.ValidationErrors
}
End If
' 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`.
@ -375,6 +387,24 @@ Public Class ImportZUGFeRDFiles
oIsSuccess = True
oMoveDirectory = oArgs.SuccessDirectory
Catch ex As ValidationException
_logger.Error(ex)
Dim oErrors = ex.ValidationErrors
Dim oMessage = "REJECTED - ZUGFeRD yes but formal validation failed!"
Update_HistoryEntry(oMessageId, oMD5CheckSum, oMessage, oFBTransaction)
Dim oErrorList As String = ""
For Each oError In oErrors
oErrorList += $"<li>Element '{oError.ElementName}' mit Wert '{oError.ElementValue}': {oError.ErrorMessage}</li>"
Next
Dim oBody = String.Format(EmailStrings.EMAIL_VALIDATION_ERROR, oErrorList)
Dim oEmailData = MoveAndRenameEmailToRejected(oArgs, oMessageId)
_email.AddToEmailQueueMSSQL(oMessageId, oBody, oEmailData, "ValidationException", _EmailOutAccountId, oArgs.NamePortal)
AddRejectedState(oMessageId, "ValidationException", "Die Rechnungsvalidierung ist fehlgeschlagen!", "", oSQLTransaction)
Catch ex As MD5HashException
_logger.Error(ex)