Modules/Interfaces/ZUGFeRDInterface/PropertyValues.vb

467 lines
19 KiB
VB.net

Imports System.Globalization
Imports System.Reflection
Imports System.Text.RegularExpressions
Imports DigitalData.Modules.Logging
Public Class PropertyValues
Private _logger As Logger
Private _logConfig As LogConfig
Private _indexPattern = "\((\d+)\)"
Private _indexRegex As New Regex(_indexPattern)
Public Sub New(LogConfig As LogConfig)
_logConfig = LogConfig
_logger = LogConfig.GetLogger()
End Sub
Public Class CheckPropertyValuesResult
Public MissingProperties As New List(Of MissingProperty)
Public ValidProperties As New List(Of ValidProperty)
End Class
Public Class ValidProperty
Public MessageId As String
Public TableName As String
Public TableColumn As String
Public IsRequired As Boolean
Public GroupCounter As Integer = -1
Public EN16931_ID As String
Public Description As String
Public Value As String
Public XMLPath As String
Public ItemType As Integer = 0
End Class
Public Class MissingProperty
Public EN16931_ID As String
Public Description As String
Public XMLPath As String
Public Overrides Function ToString() As String
Return XMLPath
End Function
End Class
Public Function CheckPropertyValues(pDocument As Object, PropertyMap As Dictionary(Of String, XmlItemProperty), MessageId As String) As CheckPropertyValuesResult
Dim oGlobalGroupCounter = 0
Dim oMissingProperties As New List(Of String)
Dim oResult As New CheckPropertyValuesResult()
' PropertyMap items with `IsGrouped = False` are handled normally
Dim oDefaultProperties As Dictionary(Of String, XmlItemProperty) = PropertyMap.
Where(Function(Item) Item.Value.IsGrouped = False).
ToDictionary(Function(Item) Item.Key,
Function(Item) Item.Value)
_logger.Debug("Found {0} ungrouped properties.", oDefaultProperties.Count)
' PropertyMap items with `IsGrouped = True` are grouped by group scope
Dim oGroupedProperties = PropertyMap.
Where(Function(Item) Item.Value.IsGrouped = True).
ToLookup(Function(Item) Item.Value.GroupScope, ' Lookup key is group scope
Function(Item) Item)
_logger.Debug($"Found [{PropertyMap.Count - oDefaultProperties.Count}] properties grouped in [{oGroupedProperties.Count}] group(s)")
' Iterate through groups to get group scope and group items
For Each oGroup In oGroupedProperties
Dim oGroupScope As String = oGroup.Key
Dim oPropertyList As New Dictionary(Of XmlItemProperty, List(Of Object))
Dim oRowCount = 0 ' TODO - Es wird anhand der Anzahl XML-Knoten ermittelt wieviele Rows gelesen werden???????
_logger.Debug($"Fetching Property values for group [{oGroupScope}].")
' get properties as a nested object, see `oPropertyList`
For Each oProperty As KeyValuePair(Of String, XmlItemProperty) In oGroup
Dim oPropertyValues As List(Of Object)
_logger.Debug($"Fetching value for itemSpecification [{oProperty.Value.TableColumn}].")
Try
oPropertyValues = GetPropValue(pDocument, oProperty.Key)
Catch ex As Exception
_logger.Warn($"{MessageId} - Unknown error occurred while fetching property/TColumn [{0}] in group [{1}]:", oProperty.Value.TableColumn, oGroupScope)
_logger.Error(ex)
oPropertyValues = New List(Of Object)
End Try
' check the first batch of values to determine the row count
If oRowCount = 0 Then '08.04.2025 MS Added as Workaround for Positions Or oGroupScope = "POSITIONS" !
oRowCount = oPropertyValues.Count
End If
' Flatten result value
oPropertyValues = GetFinalPropValue(oPropertyValues)
' Add to list
oPropertyList.Add(oProperty.Value, oPropertyValues)
Next
' Structure of oPropertyList
' [ # Propertyname # Row 1 # Row 2
' PositionsMenge: [BilledQuantity1, BilledQuantity2, ...],
' PositionsSteuersatz: [ApplicablePercent1, ApplicablePercent2, ...],
' ...
' ]
For oRowIndex = 0 To oRowCount - 1
_logger.Debug("Processing row {0}", oRowIndex)
For Each oColumn As KeyValuePair(Of XmlItemProperty, List(Of Object)) In oPropertyList
Dim oTableName As String = oColumn.Key.TableName
Dim oTableColumn As String = oColumn.Key.TableColumn
Dim oIsRequired As Boolean = oColumn.Key.IsRequired
Dim oPropertyDescription As String = oColumn.Key.Description
Dim oPropertyPath As String = oColumn.Key.XMLPath
Dim oItemType As Integer = oColumn.Key.ItemType
Dim oEN16931Value As String = oColumn.Key.EN16931_ID
Dim oRowCounter = oRowIndex + oGlobalGroupCounter + 1
' Returns nothing if oColumn.Value contains an empty list
Dim oPropertyValue = oColumn.Value.ElementAtOrDefault(oRowIndex)
_logger.Debug("Processing itemColumn *TableColumn* [{0}].", oTableColumn)
If oTableColumn = "INVOICE_SELLER_EMAIL" Then
Console.WriteLine("INVOICE_SELLER_EMAIL")
ElseIf oTableColumn = "INVOICE_POSITION_ARTICLE" Then
Console.WriteLine("INVOICE_POSITION_ARTICLE")
End If
If IsNothing(oPropertyValue) OrElse String.IsNullOrEmpty(oPropertyValue) Then
If oColumn.Key.IsRequired Then
_logger.Warn($"{MessageId} - oPropertyValue for column [{oTableColumn}] is empty or not found but is required. Continuing with Empty String.")
Dim oMissingProperty = New MissingProperty() With {
.EN16931_ID = oEN16931Value,
.Description = oPropertyDescription,
.XMLPath = oPropertyPath
}
oResult.MissingProperties.Add(oMissingProperty)
Else
_logger.Debug($"{MessageId} - oPropertyValue for column [{oTableColumn}] is empty or not found. Continuing with Empty String.")
End If
oPropertyValue = String.Empty
End If
If (oPropertyValue IsNot Nothing) Then
Dim logValue As String = oPropertyValue.ToString()
If logValue.Length > 50 Then
_logger.Debug("Item [{0}] has value '{1}...'", oTableColumn, logValue.Substring(1, 50))
Else
_logger.Debug("Item [{0}] has value '{1}'", oTableColumn, oPropertyValue)
End If
End If
If oTableColumn = "INVOICE_CURRENCY" Or oItemType = 4 Then
Dim oValuestring = oPropertyValue.ToString()
' Bei Listenelementen entfernen wir den String Item, um den Wert zu erhalten
If oValuestring.Contains("Item") Then
oValuestring = oValuestring.Replace("Item", "")
End If
oPropertyValue = oValuestring
End If
oResult.ValidProperties.Add(New ValidProperty() With {
.MessageId = MessageId,
.Description = oPropertyDescription,
.Value = oPropertyValue,
.GroupCounter = oRowCounter,
.TableName = oTableName,
.TableColumn = oTableColumn,
.IsRequired = oIsRequired,
.XMLPath = oPropertyPath,
.ItemType = oItemType,
.EN16931_ID = oEN16931Value
})
Next
Next
oGlobalGroupCounter += oRowCount
Next
' Iterate through default properties
For Each oItem As KeyValuePair(Of String, XmlItemProperty) In oDefaultProperties
Dim oPropertyValueList As List(Of Object)
Dim oTableColumn As String = oItem.Value.TableColumn
Dim oPropertyDescription As String = oItem.Value.Description
Dim oPropertyPath As String = oItem.Value.XMLPath
Dim oPropertyValue As Object = Nothing
Dim oTableName = oItem.Value.TableName
Dim oIsRequired = oItem.Value.IsRequired
Dim oDescription = oItem.Value.Description
Dim oItemType = oItem.Value.ItemType
Dim oEN16931_ID = oItem.Value.EN16931_ID
Try
oPropertyValueList = GetPropValue(pDocument, oItem.Key)
Catch ex As Exception
_logger.Warn("{2} - Unknown error occurred while fetching specification [{0}] in group [{1}]:", oTableColumn, oItem.Value.GroupScope, MessageId)
_logger.Warn("ERROR-MESSAGE [{0}]", ex.Message)
_logger.Error(ex)
oPropertyValueList = New List(Of Object)
End Try
Try
If IsNothing(oPropertyValueList) Then
oPropertyValue = Nothing
ElseIf TypeOf oPropertyValueList Is List(Of Object) Then
Select Case oPropertyValueList.Count
Case 0
oPropertyValue = Nothing
Case Else
Dim oList As List(Of Object) = DirectCast(oPropertyValueList, List(Of Object))
oPropertyValue = oList.Item(0)
' This should hopefully show config errors
If TypeOf oPropertyValue Is List(Of Object) Then
_logger.Warn("Item with specification [{0}] may be configured incorrectly", oTableColumn)
oPropertyValue = Nothing
End If
End Select
End If
Catch ex As Exception
_logger.Warn("Unknown error occurred while processing specification [{0}]:", oTableColumn)
_logger.Error(ex)
oPropertyValue = Nothing
End Try
If IsNothing(oPropertyValue) OrElse String.IsNullOrEmpty(oPropertyValue) Then
If oItem.Value.IsRequired Then
_logger.Warn("Specification [{0}] is empty, but marked as required! Skipping.", oTableColumn)
Dim oMissingProperty = New MissingProperty With
{
.Description = oPropertyDescription,
.XMLPath = oPropertyPath
}
oResult.MissingProperties.Add(oMissingProperty)
Continue For
Else
_logger.Debug("oPropertyValue for specification [{0}] is empty or not found. Skipping.", oTableColumn)
Continue For
End If
End If
' Statt dem Zahlenwert des Enums, wollen wir die Währungsbezeichnung
If oTableColumn = "INVOICE_CURRENCY" Or oItemType = 4 Then
Dim oValuestring = oPropertyValue.ToString()
' Bei Listenelementen entfernen wir den String Item, um den Wert zu erhalten
If oValuestring.Contains("Item") Then
oValuestring = oValuestring.Replace("Item", "")
End If
oPropertyValue = oValuestring
End If
oResult.ValidProperties.Add(New ValidProperty() With {
.MessageId = MessageId,
.Description = oDescription,
.Value = oPropertyValue,
.TableName = oTableName,
.TableColumn = oTableColumn,
.IsRequired = oIsRequired,
.XMLPath = oPropertyPath,
.ItemType = oItemType,
.EN16931_ID = oEN16931_ID
})
Next
Return oResult
End Function
Public Function GetPropValue(Obj As Object, PropertyName As String) As List(Of Object)
Dim oNameParts As String() = PropertyName.Split("."c)
If IsNothing(Obj) Then
_logger.Debug("`Obj` is Nothing. Exiting.")
Return New List(Of Object)
End If
If oNameParts.Length = 1 Then
Dim oPropInfo As PropertyInfo = Obj.GetType().GetProperty(PropertyName)
If IsNothing(oPropInfo) Then
_logger.Debug("Property [{0}] does not exist(1).", PropertyName)
Return New List(Of Object)
Else
Dim oPropValue = oPropInfo.GetValue(Obj, Nothing)
Return New List(Of Object) From {oPropValue}
End If
End If
For Each oPart As String In oNameParts
Dim oType As Type = Obj.GetType()
Dim oPartName = oPart
Dim oIndex As Integer = Nothing
Dim oHasIndex As Boolean = HasIndex(oPartName)
If oHasIndex Then
oPartName = StripIndex(oPart)
oIndex = GetIndex(oPart)
End If
Dim oInfo As PropertyInfo = oType.GetProperty(oPartName)
If IsNothing(oInfo) OrElse IsNothing(oInfo.GetValue(Obj, Nothing)) Then
_logger.Debug("Property [{0}] does not exist(2).", oPartName)
Return New List(Of Object)
End If
Obj = oInfo.GetValue(Obj, Nothing)
If oHasIndex Then
Obj = Obj(0)
End If
If IsArray(Obj) And Not oHasIndex And oPart <> "Value" Then
Dim oCurrentPart As String = oPart
Dim oSplitString As String() = New String() {oCurrentPart & "."}
Dim oPathFragments = PropertyName.Split(oSplitString, StringSplitOptions.None)
Dim oResults As New List(Of Object)
' if path has no more subitems, return an empty list
If oPathFragments.Length = 1 Then
Return oResults
End If
For Each oArrayItem In Obj
Dim oResult As List(Of Object) = GetPropValue(oArrayItem, oPathFragments(1))
If Not IsNothing(oResult) Then
oResults.Add(oResult)
End If
Next
Return oResults
Else
If oPart = "Value" AndAlso Obj IsNot Nothing Then
' Der Name des gefundenen Datentyps
Dim oObjType = oInfo.PropertyType.FullName
If oObjType.Equals("System.DateTime", StringComparison.OrdinalIgnoreCase) Then
Dim d As Date
Dim s As String
Dim oResult As String
s = Convert.ToString(Obj)
If IsDate(s) = True Then
' Hier wird das DEFAULT-Format auf yyyyMMdd gesetzt
Dim dtfi As DateTimeFormatInfo = CultureInfo.CreateSpecificCulture(CultureInfo.InvariantCulture.Name).DateTimeFormat
dtfi.DateSeparator = ""
dtfi.ShortDatePattern = "yyyyMMdd"
d = CDate(s)
oResult = d.ToString("d", dtfi)
'Return New List(Of Object) From {oResult}
Dim oRetValue As List(Of Object) = New List(Of Object) From {
oResult
}
Return oRetValue
End If
ElseIf oObjType.Equals("System.Decimal", StringComparison.OrdinalIgnoreCase) Then
Dim oResult As String
If IsNumeric(Obj) = True Then
Dim decValue As Decimal = CDec(Obj)
' Es wird immer ein . als Dezimaltrenner verwendet, falls nötig
oResult = decValue.ToString(CultureInfo.InvariantCulture)
'Return New List(Of Object) From {oResult}
Dim oRetValue As List(Of Object) = New List(Of Object) From {
oResult
}
Return oRetValue
End If
End If
End If
End If
Next
Return New List(Of Object) From {Obj}
End Function
Public Function GetFinalPropValue(List As List(Of Object)) As List(Of Object)
Dim oResult As New List(Of Object)
For Each Item In List
Dim oItemValue = DoGetFinalPropValue(Item)
If Not IsNothing(oItemValue) Then
oResult.Add(oItemValue)
End If
Next
Return oResult
End Function
Private Function DoGetFinalPropValue(Value As Object) As String
If TypeOf Value Is List(Of Object) Then
Dim oList = DirectCast(Value, List(Of Object))
Dim oCount = oList.Count
Select Case oCount
Case 0
Return Nothing
Case 1
Dim firstElement As Object
firstElement = oList.FirstOrDefault()
If firstElement IsNot Nothing AndAlso IsArray(firstElement) Then
' Attachments sind Byte-Arrays und müssen umgewandelt werden
Return Convert.ToBase64String(firstElement)
Else
Return DoGetFinalPropValue(oList.First())
End If
Case Else
Return DoGetFinalPropValue(oList.First())
End Select
Return DoGetFinalPropValue(Value)
Else
' Nothing kann auch der Default-Wert der Variablen bedeuten. Also auch 0.00
If IsNothing(Value) Then
Return String.Empty
Else
Return Value.ToString
End If
End If
End Function
Private Function GetIndex(Prop As String) As Integer
If Regex.IsMatch(Prop, _indexPattern) Then
Dim oMatch = _indexRegex.Match(Prop)
Dim oGroup = oMatch.Groups.Item(1)
Dim oValue = oGroup.Value
Return Integer.Parse(oValue)
End If
Return Nothing
End Function
Private Function StripIndex(Prop As String) As String
Return Regex.Replace(Prop, _indexPattern, "")
End Function
Private Function HasIndex(Prop As String) As Boolean
Return Regex.IsMatch(Prop, _indexPattern)
End Function
End Class