diff --git a/Interfaces/ZUGFeRDInterface.vb b/Interfaces/ZUGFeRDInterface.vb
index ab62b7e4..8221ad5d 100644
--- a/Interfaces/ZUGFeRDInterface.vb
+++ b/Interfaces/ZUGFeRDInterface.vb
@@ -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
'''
@@ -125,9 +134,10 @@ Public Class ZUGFeRDInterface
'''
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
'''
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()
diff --git a/Jobs/Exceptions.vb b/Jobs/Exceptions.vb
index 36400b9f..13ba4b2b 100644
--- a/Jobs/Exceptions.vb
+++ b/Jobs/Exceptions.vb
@@ -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
diff --git a/Jobs/ZUGFeRD/EmailStrings.vb b/Jobs/ZUGFeRD/EmailStrings.vb
index fbecd5bb..034510b0 100644
--- a/Jobs/ZUGFeRD/EmailStrings.vb
+++ b/Jobs/ZUGFeRD/EmailStrings.vb
@@ -20,6 +20,10 @@
Public Const EMAIL_MD5_ERROR = "Die von Ihnen gesendete Rechnung wurde bereits von unserem System verarbeitet.
"
+ Public Const EMAIL_VALIDATION_ERROR = "
+ Die von Ihnen gesendete Rechnung hat die ZUGFeRD Validierung nicht bestanden.
+ Die folgenden Felder sind nicht korrekt:
"
+
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 ({0}) enthielt keine ZUGFeRD-Dokumente.
"
diff --git a/Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb b/Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb
index 5ffe0cb9..2fb8dee2 100644
--- a/Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb
+++ b/Jobs/ZUGFeRD/ImportZUGFeRDFiles.vb
@@ -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 += $"Element '{oError.ElementName}' mit Wert '{oError.ElementValue}': {oError.ErrorMessage}"
+ 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)