Jonathan Jenne c1b6e2ed4d small tweaks
2021-11-18 15:35:02 +01:00

383 lines
16 KiB
VB.net

Imports System.Globalization
Imports System.IO
Imports DigitalData.Modules.Logging
Imports MultiTool.Shared.Exceptions
Imports MultiTool.Shared.Schemas
Imports MultiTool.Shared.Winline
Namespace Documents
Public Class DocumentLoader
Inherits BaseClass
Private ReadOnly Winline As WinlineData
Public Property Files As New List(Of Document)
Public Event FileLoadComplete As EventHandler(Of FileLoadInfo)
Public Structure FileLoadInfo
Public FilesLoaded As Integer
Public FilesTotal As Integer
End Structure
Public Sub New(pLogConfig As LogConfig, pWinline As WinlineData)
MyBase.New(pLogConfig, pLogConfig.GetLogger())
Winline = pWinline
End Sub
Public Function LoadFiles(pInputDirectory As String, pSchema As Schema, pMandator As Mandator) As Boolean
If pInputDirectory = String.Empty Then
Throw New ArgumentNullException("InputDirectory")
End If
Logger.Info("Loading files from directory [{0}]", pInputDirectory)
Files.Clear()
Try
Dim oDirectory As New DirectoryInfo(pInputDirectory)
Dim oFiles = oDirectory.GetFiles()
Logger.Debug("Found [{0}] files in directory [{1}]", oFiles.Count, oDirectory)
For Each oFile In oFiles
Try
Dim oDocument = LoadFile(oFile, pSchema, pMandator)
Files.Add(oDocument)
Dim oInfo As FileLoadInfo
oInfo.FilesLoaded = Files.Count
oInfo.FilesTotal = oFiles.Count
RaiseEvent FileLoadComplete(Me, oInfo)
Catch ex As MissingAttributeException
Logger.Error(ex)
Throw New DocumentLoaderException($"Missing Attribute '{ex.Message}' in File '{oFile.Name}'")
Catch ex As Exception
Logger.Error(ex)
Throw ex
End Try
Next
Return True
Catch ex As Exception
Logger.Error(ex)
Throw ex
End Try
End Function
Public Function LoadFile(pFileInfo As FileInfo, pSchema As Schema, pMandator As Mandator) As Document
Dim oFileList As New List(Of FileInfo) From {pFileInfo}
Logger.Info("Loading file [{0}]", pFileInfo.Name)
Try
Return oFileList.
Select(AddressOf WrapFileInfo).
Select(Function(d) IncludeSchema(d, pSchema)).
Select(Function(d) LoadDocumentData(d, pSchema)).
Select(Function(d) MatchDataFromWinLine(d, Winline.Mandators, pMandator, pSchema)).
SingleOrDefault()
Catch ex As Exception
Logger.Error(ex)
Throw ex
End Try
End Function
Private Function IncludeSchema(pDocument As Document, pSchema As Schema) As Document
pDocument.Schema = pSchema
Return pDocument
End Function
''' <summary>
''' Loads a single document from the FullName Property in the Document Object
''' </summary>
''' <example>
'''
''' A document might look like this:
''' <MESOWebService>
''' <Row1></Row1>
''' <Row2></Row2>
''' <Row3></Row3>
''' </MESOWebService>
'''
''' </example>
Private Function LoadDocumentData(pDocument As Document, pSchema As Schema) As Document
Dim oText As String = IO.File.ReadAllText(pDocument.FullName)
Dim oDoc = XDocument.Parse(oText)
Dim oRootElement As XElement = XmlData.GetElement(oDoc, "MESOWebService")
If oRootElement Is Nothing Then
Throw New Exceptions.MalformedXmlException("Datei enthält kein MESOWebService-Element")
End If
Dim oTemplateName = XmlData.GetElementAttribute(oRootElement, "Template")
If oTemplateName Is Nothing Then
Throw New Exceptions.MalformedXmlException("Datei enthält kein Template-Attribut")
End If
Dim oTemplateType = XmlData.GetElementAttribute(oRootElement, "TemplateType")
If oTemplateType Is Nothing Then
Throw New Exceptions.MalformedXmlException("Datei enthält kein TemplateType-Attribut")
End If
Dim oOption = XmlData.GetElementAttribute(oRootElement, "option")
If oOption Is Nothing Then
Throw New Exceptions.MalformedXmlException("Datei enthält kein option-Attribut")
End If
Dim oPrintVoucher = XmlData.GetElementAttribute(oRootElement, "printVoucher")
If oPrintVoucher Is Nothing Then
Throw New Exceptions.MalformedXmlException("Datei enthält kein printVoucher-Attribut")
End If
' The first level of Elements are the document Rows
Dim oTopLevelElements As List(Of XElement) = oRootElement.Elements.ToList
Dim oDocumentRows As New List(Of DocumentRow)
Dim oSortKey As Integer = 0
' TODO: Somehow add all fields in the correct order
'
' Right now, the method of
' - first the filled field from xml
' - then the rest from schema
'
' leads to unordered fields.
For Each oTopLevelElement As XElement In oTopLevelElements
Dim oFields As New Dictionary(Of String, DocumentRow.FieldValue)
Dim oSubElements = oTopLevelElement.Descendants().ToList()
Dim oTable = pSchema.Tables.
Where(Function(t) t.Name = oTopLevelElement.Name).
FirstOrDefault()
For Each oColumn In oTable.Columns
Dim oSubElement = oSubElements.
Where(Function(e) e.Name = oColumn.Name).
SingleOrDefault()
If oSubElement IsNot Nothing Then
Dim oRequired = oColumn.IsRequired
Dim oValue = oSubElement.Value.Trim()
' TODO: Needed when we have time for date times
'If oSchemaField.DataType = Constants.ColumnType.Date Then
' Dim oDate = Date.ParseExact(oValue, "yyyy-MM-dd", CultureInfo.InvariantCulture)
' oValue = oDate.ToString("d")
'End If
oFields.Add(oSubElement.Name.ToString, New DocumentRow.FieldValue With {
.Original = oValue,
.Final = oValue,
.DataType = oColumn.DataType,
.Required = oRequired
})
Else
Dim oColumnError = DocumentRow.FieldError.None
If oColumn.Config?.IsRequired Then
oColumnError = DocumentRow.FieldError.MissingValue
End If
oFields.Add(oColumn.Name, New DocumentRow.FieldValue With {
.[Error] = oColumnError
})
End If
Next
'For Each oSubElement As XElement In oSubElements
' Dim oSchemaField = oTable.Columns.
' Where(Function(c) c.Name = oSubElement.Name).
' SingleOrDefault()
' Dim oRequired = oSchemaField.IsRequired
' Dim oValue = oSubElement.Value.Trim()
' ' TODO: Needed when we have time for date times
' 'If oSchemaField.DataType = Constants.ColumnType.Date Then
' ' Dim oDate = Date.ParseExact(oValue, "yyyy-MM-dd", CultureInfo.InvariantCulture)
' ' oValue = oDate.ToString("d")
' 'End If
' oFields.Add(oSubElement.Name.ToString, New DocumentRow.FieldValue With {
' .Original = oValue,
' .Final = oValue,
' .DataType = oSchemaField.DataType,
' .Required = oRequired
' })
'Next
'' All fields in the schema are generated,
'' only creating the ones with values leads to wrong visual cues when asking for
'' docs/rows/fields with errors
'For Each oColumn In oTable.Columns
' If oFields.ContainsKey(oColumn.Name) Then
' Continue For
' End If
' Dim oColumnError = DocumentRow.FieldError.None
' If oColumn.Config?.IsRequired Then
' oColumnError = DocumentRow.FieldError.MissingValue
' End If
' oFields.Add(oColumn.Name, New DocumentRow.FieldValue With {
' .[Error] = oColumnError
' })
'Next
' Create a DocumentRow object for each Top Level Element
Dim oRow = New DocumentRow With {
.SortKey = oSortKey,
.Name = oTopLevelElement.Name.ToString,
.Fields = oFields
}
oSortKey += 1
oDocumentRows.Add(oRow)
Next
' Update the document
pDocument.TemplateName = oTemplateName
pDocument.TemplateType = oTemplateType
pDocument.Option = oOption
pDocument.PrintVoucher = oPrintVoucher
pDocument.Rows = oDocumentRows
Return pDocument
End Function
Private Function MatchDataFromWinLine(pDocument As Document, pMandators As List(Of Mandator), pMandator As Mandator, pSchema As Schema) As Document
Dim oMandators As List(Of Winline.Mandator) = pMandators.
Where(Function(m) m.IsWhitelisted = True).
OrderBy(Function(m) m.Order).
ToList()
Dim oMandator As Mandator = Nothing
If pMandator IsNot Nothing Then
oMandator = pMandator
Else
oMandator = Winline.FindMatchingMandatorFromOrder(pDocument)
End If
If oMandator Is Nothing Then
Logger.Warn("Mandator not found for File [{0}]", pDocument.File.Name)
Throw New Exceptions.NoMandatorException($"Mandator not found for file [{pDocument.File.Name}]")
End If
pDocument = MatchDocumentData(pDocument, oMandator, pSchema)
pDocument.Mandator = oMandator
Return pDocument
End Function
Private Function MatchDocumentData(pDocument As Document, pMandator As Winline.Mandator, pSchema As Schema) As Document
Dim oYear = Winline.GetWinLineYear()
If pMandator Is Nothing Then
Return pDocument
End If
For Each oRow As DocumentRow In pDocument.Rows
Dim oTable = pSchema.Tables.Where(Function(t) t.Name = oRow.Name).SingleOrDefault()
For Each oField In oRow.Fields
If oTable Is Nothing Then
Exit For
End If
Dim oColumn = oTable.Columns.Where(Function(c) c.Name = oField.Key).SingleOrDefault()
If oColumn Is Nothing Then
Continue For
End If
Dim oFunctionName = oColumn.Config?.Function?.Name
If oFunctionName = "GLN" Then
SetAccountByGLN(oRow, pMandator, oField.Key, Nothing)
End If
If oFunctionName = "EAN" Then
Dim oNumberItem As DocumentRow.FieldValue = oRow.Fields.GetOrDefault(oField.Key)
Dim oArticleNumber = Winline.TryGetArticleNumber(oNumberItem.Original, pMandator)
If oArticleNumber IsNot Nothing Then
oNumberItem.External = oArticleNumber
oNumberItem.Final = oArticleNumber
Else
oNumberItem.Error = DocumentRow.FieldError.ArticleNotFound
End If
End If
Next
Next
'Dim oHead As DocumentRow = pDocument.Rows.
' Where(Function(r) r.Name.ToUpper.EndsWith("T025")).
' SetValue(Sub(r As DocumentRow) SetAccountByGLN(r, pMandator, "Fakt_Kontonummer", "Fakt_Name")).
' SetValue(Sub(r As DocumentRow) SetAccountByGLN(r, pMandator, "Lief_Kontonummer", "Lief_Name")).
' FirstOrDefault()
'Dim oPositions As List(Of DocumentRow) = pDocument.Rows.
' Where(Function(r) r.Name.ToUpper.EndsWith("T026")).
' SetValue(Sub(oRow As DocumentRow)
' Dim oNumberItem As DocumentRow.FieldValue = oRow.Fields.GetOrDefault("Artikelnummer")
' If oNumberItem Is Nothing Then
' Exit Sub
' End If
' Dim oArticleNumber = Winline.TryGetArticleNumber(oNumberItem.Original, pMandator)
' If oArticleNumber IsNot Nothing Then
' oNumberItem.External = oArticleNumber
' oNumberItem.Final = oArticleNumber
' Else
' oNumberItem.Error = DocumentRow.FieldError.ArticleNotFound
' End If
' End Sub).
' ToList()
'Dim oList As New List(Of DocumentRow) From {oHead}
'pDocument.Rows = oList.Concat(oPositions).ToList()
Return pDocument
End Function
Private Sub SetAccountByGLN(oRow As DocumentRow, pMandator As Winline.Mandator, pNumberField As String, pNameField As String)
' Try to read the Account number (which is a GLN really) and account Name
Dim oNumberItem As DocumentRow.FieldValue = oRow.Fields.GetOrDefault(pNumberField)
Dim oNameItem As DocumentRow.FieldValue = oRow.Fields.GetOrDefault(pNameField)
Dim oContainsAccountName As Boolean = Not IsNothing(oNameItem)
If oNumberItem Is Nothing Then
Exit Sub
End If
' Try to find an account that matches the GLN
Dim oAccount = Winline.TryGetAccount(oNumberItem.Original, pMandator)
' If an account was found, set it for External and Final value
If oAccount IsNot Nothing Then
oNumberItem.External = oAccount.Id
oNumberItem.Final = oAccount.Id
If oContainsAccountName Then
oNameItem.External = oAccount.Name
oNameItem.Final = oAccount.Name
Else
' TODO: What to to if name field is missing or not set?
'oRow.Fields.Add(pNameField, New DocumentRow.FieldValue() With {
' .External = oAccount.Name,
' .Final = oAccount.Name
'})
End If
Else
oNumberItem.Error = DocumentRow.FieldError.AccountNotFound
End If
End Sub
Private Function WrapFileInfo(pFileInfo As FileInfo) As Document
Return New Document With {.File = pFileInfo}
End Function
End Class
End Namespace