Add support for multiple functions per field, add ADDRESS function

This commit is contained in:
Jonathan Jenne 2023-06-26 11:27:51 +02:00
parent 7846e660b9
commit 68e4c59e63
9 changed files with 274 additions and 154 deletions

View File

@ -9,6 +9,7 @@
Public Const FUNCTION_SQL = "SQL"
Public Const FUNCTION_FIELD = "FIELD"
Public Const FUNCTION_RUNNINGNUMBER = "RUNNINGNUMBER"
Public Const FUNCTION_ADDRESS = "ADDRESS"
Public Const PLACEHOLDER_CONST = "CONST"
Public Const PLACEHOLDER_FIELD = "FIELD"
@ -65,6 +66,8 @@
MissingValue
AccountNotFound
ArticleNotFound
PriceNotCalculated
MissingParameter
End Enum
Public Class FieldError

View File

@ -8,6 +8,7 @@ Imports MultiTool.Common.Winline
Imports MultiTool.Common.Winline.Entities
Imports MultiTool.Common.Constants
Imports DigitalData.Modules.Database
Imports DevExpress.Utils.CommonDialogs
Namespace Documents
Public Class DocumentLoader
@ -88,7 +89,7 @@ Namespace Documents
Public Async Function LoadFile(pFileInfo As FileInfo, pTemplate As Template, pMandator As Mandator) As Task(Of Document)
Logger.Debug("Creating new Document object for file [{0}]", pFileInfo.Name)
Dim oDocument As Document = New Document With {
Dim oDocument As New Document With {
.File = pFileInfo,
.Schema = pTemplate
}
@ -349,26 +350,37 @@ Namespace Documents
Private Function ApplySQLFunctionForImport(pDocument As Document, pSQLConfig As List(Of FieldConfig)) As Document
For Each oSQLConfigItem In pSQLConfig
' FieldList is a list of fields that will be changed
' Example: Setting SQL for Article StorageLocation will invoke the sql for each row
Dim oRowList = pDocument.Rows.
Where(Function(row) row.Fields.Any(Function(field) field.Key = oSQLConfigItem.Name)).
ToList()
For Each oFunction In oSQLConfigItem.Functions
For Each oRow As DocumentRow In oRowList
Dim oSQL = oSQLConfigItem.Function.Params
Dim oField = oRow.Fields.
Where(Function(field) field.Key = oSQLConfigItem.Name).
SingleOrDefault()
oSQL = Patterns.ReplaceForImport(pDocument, oRow, oSQL)
Dim oValue = Database.GetScalarValue(oSQL)
If oValue IsNot Nothing Then
oField.Value.SetExternalValue(oValue)
If Not oFunction.Name = FUNCTION_SQL Then
Continue For
End If
' FieldList is a list of fields that will be changed
' Example: Setting SQL for Article StorageLocation will invoke the sql for each row
Dim oRowList = pDocument.Rows.
Where(Function(row) row.Fields.Any(Function(field) field.Key = oSQLConfigItem.Name)).
ToList()
For Each oRow As DocumentRow In oRowList
Dim oSQL = oFunction.Params
Dim oField = oRow.Fields.
Where(Function(field) field.Key = oSQLConfigItem.Name).
SingleOrDefault()
oSQL = Patterns.ReplaceForImport(pDocument, oRow, oSQL)
Dim oValue = Database.GetScalarValue(oSQL)
If oValue IsNot Nothing Then
oField.Value.SetExternalValue(oValue)
End If
Next
Next
Next
Return pDocument
@ -400,22 +412,27 @@ Namespace Documents
Continue For
End If
Dim oFunctionName = oColumn.Config.FunctionName
Dim oFunctionParams = oColumn.Config.FunctionParams
For Each oFunction As FieldConfig.ColumnFunction In oColumn.Config.Functions
If oFunctionName = String.Empty Then
Continue For
End If
Logger.Debug("Running Function: [{0}]", oFunctionName)
Logger.Debug("With Parameters: [{0}]", oFunctionParams)
Dim oFunctionName = oFunction.Name
Dim oFunctionParams = oFunction.Params
Dim oParamsDict = Parameters.Parse(oFunctionParams)
If oFunctionName = Constants.FUNCTION_PRICE Then
Await SetPrice(oRow, oField.Key, oParamsDict, pDocument, pMandator, pTemplate)
End If
If oFunctionName = String.Empty Then
Continue For
End If
Logger.Debug("Running Function: [{0}]", oFunctionName)
Logger.Debug("With Parameters: [{0}]", oFunctionParams)
Dim oParamsDict = Parameters.Parse(oFunctionParams)
If oFunctionName = Constants.FUNCTION_PRICE Then
Await SetPrice(oRow, oField.Key, oParamsDict, pDocument, pMandator, pTemplate)
End If
Next
Next
Next
@ -431,40 +448,49 @@ Namespace Documents
Exit For
End If
Dim oColumn = oTable.Columns.Where(Function(c) c.Name = oField.Key).SingleOrDefault()
Dim oItemName As String = oField.Key
Dim oColumn = oTable.Columns.Where(Function(c) c.Name = oItemName).SingleOrDefault()
If oColumn Is Nothing Then
Continue For
End If
Dim oFunctionName = oColumn.Config.FunctionName
Dim oFunctionParams = oColumn.Config.FunctionParams
For Each oFunction As FieldConfig.ColumnFunction In oColumn.Config.Functions
Dim oFunctionName = oFunction.Name
Dim oFunctionParams = oFunction.Params
' The code below needs a defined function
If oFunctionName = String.Empty Then
Continue For
End If
' The code below needs a defined function
If oFunctionName = String.Empty Then
Continue For
End If
Dim oParamsDict = Parameters.Parse(oFunctionParams)
Dim oParamsDict = Parameters.Parse(oFunctionParams)
' The main identifier will be checked for String.empty and not required.
' This makes sure that optional fields do not generate errors.
Dim oIdentifier As DocumentRow.FieldValue = oRow.Fields.GetOrDefault(oField.Key)
If oIdentifier.Original = String.Empty And oIdentifier.IsRequired = False Then
Continue For
End If
' The main identifier will be checked for String.empty and not required.
' This makes sure that optional fields do not generate errors.
Dim oIdentifier As DocumentRow.FieldValue = oRow.Fields.GetOrDefault(oItemName)
If oIdentifier.Original = String.Empty And oIdentifier.IsRequired = False Then
Continue For
End If
Select Case oFunctionName
Case FUNCTION_GLN
SetAccountByGLN(oRow, pMandator, oField.Key, Nothing, oParamsDict)
Select Case oFunctionName
Case FUNCTION_GLN
SetAccountByGLN(oRow, pMandator, oItemName, Nothing, oParamsDict)
Case FUNCTION_EAN
SetArticleByEAN(oRow, pMandator, oField.Key)
Case FUNCTION_EAN
SetArticleByEAN(oRow, pMandator, oItemName)
Case FUNCTION_RUNNINGNUMBER
Await SetVersionedRunningNumber(pDocument, oRow, pMandator, oField.Key, oParamsDict)
Case FUNCTION_RUNNINGNUMBER
Await SetVersionedRunningNumber(pDocument, oRow, pMandator, oItemName, oParamsDict)
Case FUNCTION_ADDRESS
Await SetAddressByAccountNumber(pDocument, oRow, pMandator, oItemName, oParamsDict)
End Select
Next
End Select
Next
Next
@ -489,48 +515,53 @@ Namespace Documents
End If
Dim oFunctionName = oColumn.Config.FunctionName
Dim oFunctionParams = oColumn.Config.FunctionParams
Dim oParamsDict = Parameters.Parse(oFunctionParams)
For Each oFunction As FieldConfig.ColumnFunction In oColumn.Config.Functions
If oFunctionName = Constants.FUNCTION_FIELD Then
Try
Logger.Debug("Applying function FIELD to field [{0}]", oField.Key)
Dim oFunctionName = oFunction.Name
Dim oFunctionParams = oFunction.Params
Dim oParamsDict = Parameters.Parse(oFunctionParams)
Dim oParam = oParamsDict.FirstOrDefault()
If oFunctionName = FUNCTION_FIELD Then
Try
Logger.Debug("Applying function FIELD to field [{0}]", oField.Key)
If IsNothing(oParam) Then
Logger.Warn("Function FIELD needs exactly one parameter. Skipping")
Continue For
Dim oParam = oParamsDict.FirstOrDefault()
End If
If IsNothing(oParam) Then
Logger.Warn("Function FIELD needs exactly one parameter. Skipping")
Continue For
Dim oFieldName = oParam.Key
Dim oSubKey = oParam.Value
End If
Dim oReferencedField = oRow.Fields.
Dim oFieldName = oParam.Key
Dim oSubKey = oParam.Value
Dim oReferencedField = oRow.Fields.
Where(Function(field) field.Key = oFieldName).
FirstOrDefault()
If IsNothing(oReferencedField) = False Then
Dim oRawValue = oReferencedField.Value?.GetValue(oSubKey)
Dim oValue As String = Utils.NotNull(oRawValue, String.Empty)
If IsNothing(oReferencedField) = False Then
Dim oRawValue = oReferencedField.Value?.GetValue(oSubKey)
Dim oValue As String = Utils.NotNull(oRawValue, String.Empty)
If oValue <> String.Empty Then
oField.Value.SetExternalValue(oValue)
End If
Else
Logger.Warn("Referenced Field [{0}] was not found. Skipping.", oFieldName)
Continue For
If oValue <> String.Empty Then
oField.Value.SetExternalValue(oValue)
End If
Else
Logger.Warn("Referenced Field [{0}] was not found. Skipping.", oFieldName)
Catch ex As Exception
Logger.Warn("Function FIELD could not be applied to field [{0}]. Skipping.", oField.Key)
Continue For
End If
Catch ex As Exception
Logger.Warn("Function FIELD could not be applied to field [{0}]. Skipping.", oField.Key)
Continue For
End Try
End If
Next
End Try
End If
Next
Next
@ -629,6 +660,7 @@ Namespace Documents
Logger.Info("Price for Item [{0}] set to [{1}]", pPriceField, oArticlePrice)
Else
Logger.Warn("Price for Item [{0}] could not be found!", pPriceField)
oPriceItem.AddFieldError(FieldErrorType.PriceNotCalculated, "Der Preis für diese Position konnte nicht ermittelt werden.")
End If
End Function
@ -662,8 +694,8 @@ Namespace Documents
End If
' Try to find an account that matches the GLN
Dim oAlternateField = pParams.GetOrDefault("AltField", String.Empty)
Dim oAccount = Winline.TryGetAccount(oNumberItem.Original, pMandator, "c260", oAlternateField)
Dim oAlternateField As String = pParams.GetOrDefault("AltField", String.Empty)
Dim oAccount As Account = Winline.TryGetAccount(oNumberItem.Original, pMandator, "c260", oAlternateField)
' If an account was found, set it for External and Final value
If oAccount IsNot Nothing Then
@ -715,6 +747,68 @@ Namespace Documents
Throw ex
End Try
End Function
Public Function SetAddressByAccountNumber(pDocument As Document, pRow As DocumentRow, pMandator As Mandator, pAccountField As String, pParams As Dictionary(Of String, String)) As Task
Try
Const PARAMETER_NAME = "Name"
Const PARAMETER_STREET = "Street"
Const PARAMETER_ZIP = "Zip"
Const PARAMETER_CITY = "City"
Dim oAccountNumberItem As DocumentRow.FieldValue = pRow.Fields.GetOrDefault(pAccountField)
Dim oAccountNumber = oAccountNumberItem.Final
Dim oNameField As String = pParams.GetOrDefault(PARAMETER_NAME, Nothing)
Dim oNameFieldItem As DocumentRow.FieldValue = pRow.Fields.GetOrDefault(oNameField)
If oNameField Is Nothing Then
Logger.Warn("Parameter '{0}' not found for Function ADDRESS", PARAMETER_NAME)
oAccountNumberItem.AddFieldError(FieldErrorType.MissingParameter, $"Parameter '{PARAMETER_NAME}' wurde nicht gefüllt.")
End If
Dim oStreetField As String = pParams.GetOrDefault(PARAMETER_STREET, Nothing)
Dim oStreetFieldItem As DocumentRow.FieldValue = pRow.Fields.GetOrDefault(oStreetField)
If oStreetField Is Nothing Then
Logger.Warn("Parameter '{0}' not found for Function ADDRESS", PARAMETER_STREET)
oAccountNumberItem.AddFieldError(FieldErrorType.MissingParameter, $"Parameter '{PARAMETER_STREET}' wurde nicht gefüllt.")
End If
Dim oZipField As String = pParams.GetOrDefault(PARAMETER_ZIP, Nothing)
Dim oZipFieldItem As DocumentRow.FieldValue = pRow.Fields.GetOrDefault(oZipField)
If oZipField Is Nothing Then
Logger.Warn("Parameter '{0}' not found for Function ADDRESS", PARAMETER_ZIP)
oAccountNumberItem.AddFieldError(FieldErrorType.MissingParameter, $"Parameter '{PARAMETER_ZIP}' wurde nicht gefüllt.")
End If
Dim oCityField As String = pParams.GetOrDefault(PARAMETER_CITY, Nothing)
Dim oCityFieldItem As DocumentRow.FieldValue = pRow.Fields.GetOrDefault(oCityField)
If oCityField Is Nothing Then
Logger.Warn("Parameter '{0}' not found for Function ADDRESS", PARAMETER_CITY)
oAccountNumberItem.AddFieldError(FieldErrorType.MissingParameter, $"Parameter '{PARAMETER_CITY}' wurde nicht gefüllt.")
End If
Dim oAccount = Winline.Accounts.Where(Function(a) a.Id = oAccountNumber And a.Mandator.Equals(pMandator)).SingleOrDefault()
If oAccount Is Nothing Then
Logger.Warn("Account with Id [{0}] in Mandator [{1}] could not be found.", oAccountNumber, pMandator.Id)
End If
oNameFieldItem.SetExternalValue(oAccount.Name)
oStreetFieldItem.SetExternalValue(oAccount.StreetName)
oZipFieldItem.SetExternalValue(oAccount.ZipCode)
oCityFieldItem.SetExternalValue(oAccount.CityName)
Catch ex As Exception
Logger.Error(ex)
Throw ex
End Try
Return Task.CompletedTask
End Function
End Class
End Namespace

View File

@ -112,18 +112,20 @@ Public Class ReportGenerator(Of TReport As IReport)
ToList()
For Each oRow As DocumentRow In oRowList
Dim oSQL = oSQLConfigItem.Function.Params
Dim oField = oRow.Fields.
Where(Function(field) field.Key = oSQLConfigItem.Name).
SingleOrDefault()
For Each oFunction In oSQLConfigItem.Functions
Dim oSQL = oFunction.Params
Dim oField = oRow.Fields.
Where(Function(field) field.Key = oSQLConfigItem.Name).
SingleOrDefault()
oSQL = Patterns.ReplaceForImportFinalSQL(pDocument, pReportFileName, oSQL)
oSQL = Patterns.ReplaceForImportFinalSQL(pDocument, pReportFileName, oSQL)
Dim oValue = Database.GetScalarValue(oSQL)
Dim oValue = Database.GetScalarValue(oSQL)
If oValue IsNot Nothing Then
oField.Value.SetExternalValue(oValue)
End If
If oValue IsNot Nothing Then
oField.Value.SetExternalValue(oValue)
End If
Next
Next
Next

View File

@ -3,6 +3,7 @@ Imports DigitalData.Modules.Language
Namespace Templates
Public Class FieldConfig
Public Property Id As Integer
Public Property Name As String
Public Property Table As String
Public Property Type As ColumnType
@ -16,19 +17,7 @@ Namespace Templates
Public Property IsVirtual As Boolean
Public Property PreferExternalValue As Boolean
Public Property [Function] As ColumnFunction
Public ReadOnly Property FunctionName As String
Get
Return Utils.NotNull([Function]?.Name, String.Empty)
End Get
End Property
Public ReadOnly Property FunctionParams As String
Get
Return Utils.NotNull([Function]?.Params, String.Empty)
End Get
End Property
Public Property Functions As New List(Of ColumnFunction)
Public Class ColumnFunction
Public Id As XmlFunction

View File

@ -11,7 +11,7 @@ Namespace Templates
Public ReadOnly Property SqlItems As List(Of FieldConfig)
Get
Return Items.
Where(Function(item) item.Function.Name = Constants.FUNCTION_SQL).
Where(Function(item) item.Functions.Any(Function(f) f.Name = Constants.FUNCTION_SQL)).
ToList()
End Get
End Property

View File

@ -19,6 +19,7 @@ Namespace Templates
Private Const SQL_TBMT_FILTERS = "SELECT * FROM [DD_ECM].[dbo].[VWMT_FILTERS]"
Private Const SQL_VWMT_ITEMS = "SELECT * FROM [DD_ECM].[dbo].[VWMT_ITEMS] ORDER BY TEMPLATE_NAME, TABLE_NAME"
Private Const SQL_VWMT_FUNCTIONS = "SELECT * FROM [DD_ECM].[dbo].[VWMT_FUNCTIONS] ORDER BY ITEM_ID, SEQUENCE"
Private Const SQL_VWMT_MAPPING = "SELECT * FROM [DD_ECM].[dbo].[VWMT_MAPPING]"
Private Const SQL_TBMT_MANDATORS = "SELECT * FROM [DD_ECM].[dbo].[TBMT_MANDATORS] ORDER BY ORDER_KEY"
Private Const SQL_TBMT_CONFIG = "SELECT * FROM [DD_ECM].[dbo].[TBMT_CONFIG]"
@ -191,6 +192,7 @@ Namespace Templates
For Each oRow As DataRow In oTable.Rows
Dim oColumn As New FieldConfig() With {
.Id = oRow.ItemEx("ITEM_ID", 0),
.Template = oRow.ItemEx("TEMPLATE_NAME", String.Empty),
.Table = oRow.ItemEx("TABLE_NAME", String.Empty),
.Name = oRow.ItemEx("ITEM_NAME", String.Empty),
@ -202,11 +204,7 @@ Namespace Templates
.IsVirtual = oRow.ItemEx("IS_VIRTUAL", False),
.IsHead = oRow.ItemEx("IS_HEAD", True),
.PreferExternalValue = oRow.ItemEx("PREFER_EXTERNAL", True),
.[Function] = New FieldConfig.ColumnFunction With {
.Id = oRow.ItemEx("FUNCTION_ID", 0),
.Name = oRow.ItemEx("FUNCTION_NAME", String.Empty),
.Params = oRow.ItemEx("FUNCTION_PARAMETERS", String.Empty)
}
.Functions = New List(Of FieldConfig.ColumnFunction)
}
Logger.Debug("Creating Template Item for Table [{0}]: [{1}]", oColumn.Table, oColumn.Name)
@ -227,6 +225,37 @@ Namespace Templates
End Try
End Function
Public Async Function LoadTemplateFunctions() As Task(Of Boolean)
Try
Dim oTable As DataTable = Await Database.GetDatatableAsync(SQL_VWMT_FUNCTIONS)
Dim oItems As New List(Of FieldConfig)
For Each oRow As DataRow In oTable.Rows
Dim oTemplateItemId = oRow.ItemEx("ITEM_ID", 0)
Dim oTemplateItem = TemplateConfiguration.Items.SingleOrDefault(Function(i) i.Id = oTemplateItemId)
If oTemplateItem Is Nothing Then
Logger.Warn("Function configuration could not be assigned to an existing template item, item id was [{0}]", oTemplateItemId)
Continue For
End If
oTemplateItem.Functions.Add(New FieldConfig.ColumnFunction With {
.Id = oRow.ItemEx("FUNCTION_ID", 0),
.Name = oRow.ItemEx("FUNCTION_NAME", String.Empty),
.Params = oRow.ItemEx("FUNCTION_PARAMETERS", String.Empty)
})
Next
Return True
Catch ex As Exception
Logger.Error(ex)
Return False
End Try
End Function
Public Function UpdateTemplateFromFile(pTemplate As Template, pInputDirectory As String) As Template
Dim oFullPath = Path.Combine(pInputDirectory, pTemplate.FileName)

View File

@ -375,59 +375,61 @@ Namespace Winline
For Each oTable In pTemplate.Tables
Logger.Debug("Processing Table [{0}]", oTable.Name)
For Each oItem As Template.Column In oTable.Columns
For Each oColumn As Template.Column In oTable.Columns
Dim oTableName As String = oTable.Name
Dim oItemName As String = oItem.Name
Dim oItemName As String = oColumn.Name
Logger.Debug("Processing item [{0}]", oItemName)
If oItem.Config.Function Is Nothing Then
Continue For
End If
For Each oFunction As FieldConfig.ColumnFunction In oColumn.Config.Functions
Dim oFunction = oItem.Config.Function.Name
Dim oFunctionName = oFunction.Name
Dim oFunctionParams = oFunction.Params
Dim oPath = $"//MESOWebService/{oTableName}/{oItemName}"
Dim oNodes As XmlNodeList = oXMLDocument.SelectNodes(oPath)
Dim oPath = $"//MESOWebService/{oTableName}/{oItemName}"
Dim oNodes As XmlNodeList = oXMLDocument.SelectNodes(oPath)
Logger.Debug("Calling function [{0}] on node [{1}]", oFunction, oPath)
Logger.Debug("Calling function [{0}] on node [{1}]", oFunctionName, oPath)
For Each oNode As XmlNode In oNodes
If oItem.Config.Function.Name = "GLN" Then
Dim oGLN = Winline.TryGetGLN(oNode.InnerText, pMandator)
For Each oNode As XmlNode In oNodes
If oFunctionName = Constants.FUNCTION_GLN Then
Dim oGLN = Winline.TryGetGLN(oNode.InnerText, pMandator)
If oGLN Is Nothing Then
Throw New MissingAttributeException(Constants.FUNCTION_GLN)
End If
oNode.InnerText = oGLN
ElseIf oFunctionName = Constants.FUNCTION_EAN Then
Dim oEAN = Winline.TryGetEAN(oNode.InnerText, pMandator)
If oEAN Is Nothing Then
' 21.04.2022: Relax the EAN Check
' Since it is possible to have articles without a proper EAN in export,
' we dont throw here, but leave the original value in case of a failure.
' Throw New Exceptions.MissingAttributeException("EAN")
Logger.Warn("EAN could not be retrieved for Node {0}. Skipping.", oNode.Name)
Continue For
End If
oNode.InnerText = oEAN
ElseIf oFunctionName = Constants.FUNCTION_SQL Then
Dim oSQL = Patterns.ReplaceForExport(pDocument, pMandator, oFunctionParams)
Dim oValue = Database.GetScalarValue(oSQL)
If oValue Is Nothing Then
Throw New MissingAttributeException(Constants.FUNCTION_SQL)
End If
oNode.InnerText = oValue
If oGLN Is Nothing Then
Throw New MissingAttributeException("GLN")
End If
oNode.InnerText = oGLN
ElseIf oItem.Config.Function.Name = "EAN" Then
Dim oEAN = Winline.TryGetEAN(oNode.InnerText, pMandator)
If oEAN Is Nothing Then
' 21.04.2022: Relax the EAN Check
' Since it is possible to have articles without a proper EAN in export,
' we dont throw here, but leave the original value in case of a failure.
' Throw New Exceptions.MissingAttributeException("EAN")
Logger.Warn("EAN could not be retrieved for Node {0}. Skipping.", oNode.Name)
Continue For
End If
oNode.InnerText = oEAN
ElseIf oItem.Config.Function.Name = "SQL" Then
Dim oSQL = Patterns.ReplaceForExport(pDocument, pMandator, oItem.Config.Function.Params)
Dim oValue = Database.GetScalarValue(oSQL)
If oValue Is Nothing Then
Throw New MissingAttributeException("SQL")
End If
oNode.InnerText = oValue
End If
Next
Next
Next
Next

View File

@ -110,6 +110,7 @@ Public Class frmMain
TemplateLoader = New TemplateLoader(LogConfig, Database)
Await TemplateLoader.LoadTemplates()
Await TemplateLoader.LoadTemplateConfiguration()
Await TemplateLoader.LoadTemplateFunctions()
Await TemplateLoader.LoadGeneralConfiguration()
Await TemplateLoader.LoadMappingConfiguration()
Await TemplateLoader.LoadMandatorConfiguration()

View File

@ -272,11 +272,11 @@ Public Class frmRowEditor
Exit Sub
End If
If oColumn.Config?.Function?.Name = FUNCTION_GLN Then
If oColumn.Config?.Functions.Any(Function(f) f.Name = FUNCTION_GLN) Then
e.RepositoryItem = AccountPicker
End If
If oColumn.Config?.Function?.Name = FUNCTION_EAN Then
If oColumn.Config?.Functions.Any(Function(f) f.Name = FUNCTION_EAN) Then
e.RepositoryItem = ArticlePicker
End If