Modules/Interfaces/ZUGFeRDInterface/PropertyValues.vb

379 lines
15 KiB
VB.net

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 Description As String
Public Value As String
Public XMLPath As String
Public ItemType As Integer = 0
End Class
Public Class MissingProperty
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} default 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
_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
' Flatten result value
oPropertyValues = GetFinalPropValue(oPropertyValues)
' Add to list
oPropertyList.Add(oProperty.Value, oPropertyValues)
' check the first batch of values to determine the row count
If oRowCount = 0 Or oGroupScope = "POSITIONS" Then '08.04.2025 MS Added as Workaround for Positions!
oRowCount = oPropertyValues.Count
End If
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 oRowCounter = oRowIndex + oGlobalGroupCounter + 1
' Returns nothing if oColumn.Value contains an empty list
Dim oPropertyValue = oColumn.Value.ElementAtOrDefault(oRowIndex)
_logger.Debug("Processing itemSpecification *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 specification [{oTableColumn}] is empty or not found but is required. Continuing with Empty String.")
Dim oMissingProperty = New MissingProperty() With {
.Description = oPropertyDescription,
.XMLPath = oPropertyPath
}
oResult.MissingProperties.Add(oMissingProperty)
Else
_logger.Debug($"{MessageId} # oPropertyValue for specification [{oTableColumn}] is empty or not found. Continuing with Empty String.")
End If
oPropertyValue = String.Empty
End If
_logger.Debug("ItemSpecification [{0}] has value '{1}'", oTableColumn, oPropertyValue)
oResult.ValidProperties.Add(New ValidProperty() With {
.MessageId = MessageId,
.Description = oPropertyDescription,
.Value = oPropertyValue,
.GroupCounter = oRowCounter,
.TableName = oTableName,
.TableColumn = oTableColumn,
.IsRequired = oIsRequired,
.XMLPath = oPropertyPath,
.ItemType = oItemType
})
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
Try
oPropertyValueList = GetPropValue(pDocument, oItem.Key)
Catch ex As Exception
_logger.Warn("{2} # Unknown error occurred while fetching specification [{0}] in group [{1}]:", oPropertyDescription, 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", oPropertyDescription)
oPropertyValue = Nothing
End If
End Select
End If
Catch ex As Exception
_logger.Warn("Unknown error occurred while processing specification [{0}]:", oPropertyDescription)
_logger.Error(ex)
oPropertyValue = Nothing
End Try
If IsNothing(oPropertyValue) OrElse String.IsNullOrEmpty(oPropertyValue) Then
If oItem.Value.IsRequired Then
_logger.Warn("{0} # Specification [{1}] is empty, but marked as required! Skipping.", MessageId, oPropertyDescription)
Dim oMissingProperty = New MissingProperty With
{
.Description = oPropertyDescription,
.XMLPath = oPropertyPath
}
oResult.MissingProperties.Add(oMissingProperty)
Continue For
Else
_logger.Debug("{0} # oPropertyValue for specification [{1}] is empty or not found. Skipping.", MessageId, oPropertyDescription)
Continue For
End If
End If
' Statt dem Zahlenwert des Enums, wollen wir die Währunsgbezeichnung
If oTableColumn = "INVOICE_CURRENCY" Then
oPropertyValue = oPropertyValue.ToString()
End If
oResult.ValidProperties.Add(New ValidProperty() With {
.MessageId = MessageId,
.Description = oDescription,
.Value = oPropertyValue,
.TableName = oTableName,
.TableColumn = oTableColumn,
.IsRequired = oIsRequired,
.XMLPath = oPropertyPath,
.ItemType = oItemType
})
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)
' TODO: This code should check for array properties by itself
' and should not rely on the user to
'If oInfo.PropertyType.IsArray Then
' Obj = Obj(0)
'End If
If oHasIndex Then
Obj = Obj(0)
End If
If IsArray(Obj) And Not oHasIndex 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
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 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