Imports System.Globalization Imports System.IO Imports DigitalData.Modules.Logging Imports MultiTool.Shared.Schemas Imports MultiTool.Shared.Winline Namespace Documents Public Class DocumentLoader Inherits BaseClass Private ReadOnly Winline As Winline.Data Public Property Files As New List(Of Document) Public Sub New(pLogConfig As LogConfig, pWinline As Winline.Data) 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 Dim oDocument = LoadFile(oFile, pSchema, pMandator) Files.Add(oDocument) 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)). 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 ''' ''' Loads a single document from the FullName Property in the Document Object ''' ''' ''' ''' A document might look like this: ''' ''' ''' ''' ''' ''' ''' 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) 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 oSubElement As XElement In oSubElements Dim oSchemaField = oTable.Columns. Where(Function(c) c.Name = oSubElement.Name). SingleOrDefault() 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 }) Next ' Create a DocumentRow object for each Top Level Element Dim oRow = New DocumentRow With { .Name = oTopLevelElement.Name.ToString, .Fields = oFields } 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) 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) pDocument.Mandator = oMandator Return pDocument End Function Private Function MatchDocumentData(pDocument As Document, pMandator As Winline.Mandator) As Document Dim oYear = Winline.GetWinLineYear() If pMandator Is Nothing Then Return pDocument End If 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 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 oRow.Fields.Add(pNameField, New DocumentRow.FieldValue() With { .External = oAccount.Name, .Final = oAccount.Name }) End If End If End Sub Private Function TryGetDictionaryItem(Of T)(pDictionary As IDictionary(Of String, T), pKey As String) As T If pDictionary.ContainsKey(pKey) Then Return pDictionary.Item(pKey) Else Return Nothing End If End Function Private Function WrapFileInfo(pFileInfo As FileInfo) As Document Return New Document With {.File = pFileInfo} End Function End Class End Namespace