2 Commits

Author SHA1 Message Date
Jonathan Jenne
00cff028c9 Base: Add ScreenEx 2023-05-26 15:05:00 +02:00
Jonathan Jenne
76cba215fe Interfaces/Job: Add check for currencyId format in ZUGFeRD documents 2023-05-26 15:04:44 +02:00
6 changed files with 176 additions and 3 deletions

View File

@@ -57,6 +57,7 @@
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@@ -99,6 +100,7 @@
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<Compile Include="PerformanceEx.vb" />
<Compile Include="ScreenEx.vb" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="My Project\Resources.resx">

48
Base/ScreenEx.vb Normal file
View File

@@ -0,0 +1,48 @@
Imports System
Imports System.Drawing
Imports System.Windows.Forms
Public Class ScreenEx
Public Shared Function GetLocationWithinScreen(pLocation As Point) As Point?
For Each screen As Screen In Screen.AllScreens
If screen.Bounds.Contains(pLocation) Then
Return New Point(pLocation.X - screen.Bounds.Left, pLocation.Y - screen.Bounds.Top)
End If
Next
Return Nothing
End Function
Public Shared Sub RestoreFormPosition(pForm As Form, pPosition As Point)
Dim oLocationWithinScreen As Point? = GetLocationWithinScreen(pPosition)
If oLocationWithinScreen Is Nothing Then
Dim oPrimaryScreen = Screen.PrimaryScreen
pForm.StartPosition = FormStartPosition.CenterScreen
Else
pForm.StartPosition = FormStartPosition.Manual
pForm.Location = pPosition
End If
End Sub
Public Shared Sub RestoreFormState(pForm As Form, pFormState As FormWindowState)
If pFormState = FormWindowState.Maximized Then
pForm.WindowState = FormWindowState.Normal
pForm.WindowState = FormWindowState.Maximized
ElseIf pFormState = FormWindowState.Minimized Then
pForm.WindowState = FormWindowState.Normal
pForm.WindowState = FormWindowState.Minimized
Else
pForm.WindowState = FormWindowState.Normal
End If
End Sub
Public Shared Sub RestoreFormState(pForm As Form, pFormState As String)
Dim oFormState As FormWindowState
If Not [Enum].TryParse(pFormState, oFormState) Then
oFormState = FormWindowState.Normal
End If
RestoreFormState(pForm, oFormState)
End Sub
End Class

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)