Imports System.Collections.Specialized Imports System.Net.Http Imports System.Text Imports System.Xml Imports DigitalData.Modules.Database Imports DigitalData.Modules.Filesystem Imports DigitalData.Modules.Logging Imports MultiTool.Common.Documents Imports MultiTool.Common.Exceptions Imports MultiTool.Common.Templates Imports MultiTool.Common.Templates.GeneralConfig Imports MultiTool.Common.Winline.Entities Namespace Winline Public Class WebServiceData Inherits BaseClass Private ReadOnly Config As WebServiceConfig Private ReadOnly Serializer As Serializer Private ReadOnly Winline As WinlineData Private ReadOnly FileEx As File Private ReadOnly Patterns As Patterns Public Event WebServiceProgress As EventHandler(Of String) Public Sub New(pLogConfig As LogConfig, pDatabase As MSSQLServer, pWinline As WinlineData, pWebserviceConfig As WebServiceConfig, pGeneralConfig As GeneralConfig) MyBase.New(pLogConfig, pDatabase) Serializer = New Serializer(pLogConfig) Config = pWebserviceConfig Patterns = New Patterns(pLogConfig, pGeneralConfig) FileEx = New File(LogConfig) Winline = pWinline End Sub Public Sub RaiseWebServiceProgress(pMessage As String) RaiseEvent WebServiceProgress(Me, pMessage) End Sub #Region "Import" ''' ''' Transfers a document to winline via Webservices ''' ''' ''' ''' ''' ''' ''' ''' ''' True if request was successful. Public Async Function TransferDocumentToWinline(pDocument As Documents.Document, pTemplate As Template, pMandator As Mandator, Optional pIsTest As Boolean = False) As Task(Of Boolean) Dim oWS = Config RaiseEvent WebServiceProgress(Me, "Einstellungen laden") Logger.Debug("Loading setting and creating directories") ' --- Build all teh filenamez and pathz Dim oBaseFileName As String = FileEx.GetDateTimeString() Dim oFileName = $"{pTemplate.Name}-{oBaseFileName}-Request.xml" ' --- Get and create path for request/response files Dim oOutputDirectory = FileEx.CreateDateDirectory(pTemplate.OutputWebserviceDirectory) Dim oOutputFilePath = IO.Path.Combine(oOutputDirectory, oFileName) ' Generate absolute path to copy xml file to Dim oAbsolutePath = IO.Path.Combine(oWS.ImportBasePath, oWS.ImportRelativePath) oAbsolutePath = FileEx.CreateDateDirectory(oAbsolutePath) Dim oImportAbsoluteFilePath = IO.Path.Combine(oAbsolutePath, oFileName) ' Generate relative path to supply to winline Dim oRelativePath = IO.Path.Combine(oWS.ImportRelativePath) oRelativePath = FileEx.GetDateDirectory(oRelativePath) Dim oImportRelativeFilePath = IO.Path.Combine(oRelativePath, oFileName) RaiseEvent WebServiceProgress(Me, "Dateien schreiben") Logger.Debug("Writing request file to [{oOutputFilePath}]") ' If configured, prevent printing the document and instead create it as a draft in the "Nicht gedruckt" section If pTemplate.PrintDocument = False Then pDocument.PrintVoucher = 0 End If ' --- Serialize Data into XML string Dim oBytes As Byte() = GetBytesFromDocument(pDocument) IO.File.WriteAllBytes(oOutputFilePath, oBytes) ' --- Copy file to Winline Import Directory Try Logger.Debug("Copying request file to [{oImportAbsoluteFilePath}]") IO.File.Copy(oOutputFilePath, oImportAbsoluteFilePath, True) Catch ex As Exception Logger.Error(ex) Throw ex End Try ' --- Prepare URL and HTTP Client Dim oTemplateType = pDocument.TemplateType Dim oTemplateName = pDocument.TemplateName Logger.Debug("Using Template [{0}/{1}]", oTemplateName, oTemplateType) ' ActionCode: Should this be a test or not? ' 0 = Test call ' 1 = Real call Dim oActionCode = 1 If pIsTest = True Then oActionCode = 0 End If Logger.Debug("Using ActionCode [{0}]", oActionCode) ' Byref: Should data be supplied as file or as string? ' 0 = As String ' 1 = As File (relative to Winline Server directory) Dim oByref = 1 Dim oParams As New NameValueCollection() From { {"User", oWS.Username}, {"Password", oWS.Password}, {"Company", pMandator.Id}, {"Type", oTemplateType}, {"Vorlage", oTemplateName}, {"ActionCode", oActionCode}, {"Byref", oByref}, {"Data", oImportRelativeFilePath} } Dim oURL As String = $"{oWS.BaseUrl}/ewlservice/import{ToQueryString(oParams)}" 'Dim oURL As String = $"{oWS.BaseUrl}/ewlservice/import?User={oWS.Username}&Password={oWS.Password}&Company={pMandator.Id}&Type={oTemplateType}&Vorlage={oTemplateName}&ActionCode={oActionCode}&Byref={oByref}&Data={oImportRelativeFilePath}" Dim oClient As New HttpClient With { .Timeout = TimeSpan.FromSeconds(Constants.HTTP_REQUEST_TIMEOUT_IN_SECONDS) } Logger.Info("Creating HTTP Request to [{0}]", oWS.BaseUrl) RaiseEvent WebServiceProgress(Me, "Anfrage absenden") ' --- Bring the action! Try Dim oResponse As HttpResponseMessage = Await oClient.GetAsync(oURL) Logger.Debug("HTTP Response recevied!") Await HandleImportResponse(oResponse, pTemplate, oBaseFileName) Return True Catch ex As Exception Logger.Error(ex) Throw ex Finally oClient.Dispose() End Try End Function Private Async Function HandleImportResponse(pResponse As HttpResponseMessage, pTemplate As Template, pBaseFileName As String) As Task pResponse.EnsureSuccessStatusCode() Dim oResponseBody As String = Await pResponse.Content.ReadAsStringAsync() Dim oContentType = pResponse.Content.Headers.ContentType.MediaType Dim oSerializer = Serializer.GetSerializer(GetType(Templates.Entities.MESOWebServiceResult)) Dim oOutputDirectory = FileEx.CreateDateDirectory(pTemplate.OutputWebserviceDirectory) RaiseEvent WebServiceProgress(Me, "Antwort verarbeiten") Logger.Debug("Response ContentType: [{0}]", oContentType) Select Case oContentType Case "text/xml" Logger.Debug("Got XML Response. Checking.") WriteResponseFile(oOutputDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.xml") Dim oBytes As Byte() = Encoding.UTF8.GetBytes(oResponseBody) Using oStream As New IO.MemoryStream(oBytes) Dim oResponseObject As Templates.Entities.MESOWebServiceResult = oSerializer.Deserialize(oStream) Dim oErrorStrings As New List(Of String) If oResponseObject.ResultDetails IsNot Nothing Then For Each oDetails As Templates.Entities.MESOWebServiceResultResultDetails In oResponseObject.ResultDetails If oDetails.Success = True Then Logger.Debug("KeyValue: [{0}]", oDetails.KeyValue) Logger.Debug("VoucherNumber: [{0}]", oDetails.VoucherNumber) Else Logger.Warn("ErrorCode: [{0}]", oDetails.ErrorCode) Logger.Warn("ErrorText: [{0}]", oDetails.ErrorText) oErrorStrings.Add($"[{oDetails.ErrorCode}] {oDetails.ErrorText}") End If Next End If If oResponseObject.OverallSuccess = False Then Dim oMessage = $"Request to Webservice was unsuccessful:{vbNewLine}{vbNewLine}{String.Join(vbNewLine, oErrorStrings.ToArray)}" Logger.Warn("Overall Success was [false]") Throw New WebServiceException(oMessage) End If End Using Case "text/html" Logger.Debug("Got TEXT/HTML Response. Throwing.") WriteResponseFile(oOutputDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.xml") Throw New WebServiceException(oResponseBody) Case Else Logger.Debug("Unknown Response ContentType: [{0}]", oContentType) Throw New WebServiceException(oResponseBody) End Select End Function Private Function GetBytesFromDocument(pDocument As Documents.Document) As Byte() Using oStream As New IO.MemoryStream() Dim w = XmlWriter.Create(oStream) w.WriteStartDocument() w.WriteStartElement("MESOWebService") w.WriteAttributeString("Template", pDocument.TemplateName) w.WriteAttributeString("TemplateType", pDocument.TemplateType) w.WriteAttributeString("option", pDocument.Option) w.WriteAttributeString("printVoucher", pDocument.PrintVoucher) pDocument.Rows.Sort() For Each oRow In pDocument.Rows w.WriteStartElement(oRow.TableName) For Each oField As KeyValuePair(Of String, DocumentRow.FieldValue) In oRow.Fields If oField.Value.Final = String.Empty Then Continue For End If If oField.Value.IsVirtual Then Continue For End If w.WriteStartElement(oField.Key) w.WriteValue(oField.Value.Final) w.WriteEndElement() ' Field Next w.WriteEndElement() ' Row Next w.WriteEndElement() ' MESOWebService w.WriteEndDocument() ' Document w.Close() Return oStream.ToArray() End Using End Function #End Region #Region "Export" Async Function ExportDocumentFromWinline(pDocument As ExportDocument, pTemplate As Template, pMandator As Mandator, Optional pIsTest As Boolean = False) As Task(Of Boolean) Dim oWS = Config Logger.Info("Exporting document from Winline") ' --- Build all teh filenamez and pathz Dim oBaseFileName As String = FileEx.GetDateTimeString() Dim oFileName = $"{pTemplate.Name}-{oBaseFileName}.xml" 'Dim oFileName = FileEx.GetFilenameWithPrefix(oBaseFileName, pTemplate.Name, "xml") Logger.Info("Filename will be [{0}]", oFileName) ' Save the filename to the document pDocument.FilenameExport = oFileName RaiseEvent WebServiceProgress(Me, "Abfrage vorbereiten") Logger.Debug("Preparing WebService call") ' --- Prepare URL and HTTP Client Dim oTemplateType = 30 Dim oTemplateName = pDocument.Schema.Name Dim oKey = $"{pDocument.Account.Id}-{pDocument.RunningNumber}" ' ActionCode: Should this be a test or not? ' 0 = Test call ' 1 = Real call Dim oActionCode = 1 If pIsTest = True Then oActionCode = 0 End If Logger.Debug("This is a test: [{0}]", pIsTest) Dim oURL As String = $"{oWS.BaseUrl}/ewlservice/export?User={oWS.Username}&Password={oWS.Password}&Company={pMandator.Id}&Type={oTemplateType}&Vorlage={oTemplateName}&ActionCode={oActionCode}&Key={oKey}" Dim oClient As New HttpClient() Logger.Info("Creating HTTP Request to [{0}]", oWS.BaseUrl) RaiseEvent WebServiceProgress(Me, "Anfrage absenden") ' --- Bring the action! Try Dim oResponse As HttpResponseMessage = Await oClient.GetAsync(oURL) Logger.Info("Request ended with code: [{0}]", oResponse.StatusCode) Await HandleExportResponse(oResponse, pDocument, pTemplate, pMandator, oBaseFileName) Return True Catch ex As Exception Logger.Error(ex) Throw ex Finally oClient.Dispose() End Try End Function Private Async Function HandleExportResponse(pResponse As HttpResponseMessage, pDocument As Entities.ExportDocument, pTemplate As Template, pMandator As Mandator, pBaseFileName As String) As Task pResponse.EnsureSuccessStatusCode() Dim oResponseBody As String = Await pResponse.Content.ReadAsStringAsync() Dim oContentType = pResponse.Content.Headers.ContentType.MediaType Dim oSerializer = Serializer.GetSerializer(GetType(Templates.Entities.MESOWebServiceResult)) RaiseEvent WebServiceProgress(Me, "Antwort verarbeiten") Logger.Debug("Postprocessing response.") oResponseBody = ApplyItemFunctionsForExport(pDocument, pTemplate, pMandator, oResponseBody) Select Case oContentType Case "text/xml" ' Webservice WriteResponseFile(pTemplate.OutputWebserviceDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.xml") ' XML WriteResponseFile(pTemplate.OutputXmlFileDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}.xml") ' Archive WriteResponseFile(FileEx.CreateDateDirectory(pTemplate.ArchiveDirectory), oResponseBody, $"{pTemplate.Name}-{pBaseFileName}.xml") Case "text/html" WriteResponseFile(pTemplate.OutputWebserviceDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.txt") Throw New ApplicationException(oResponseBody) Case Else Throw New ApplicationException(oResponseBody) End Select End Function Private Function ApplyItemFunctionsForExport(pDocument As Entities.ExportDocument, pTemplate As Template, pMandator As Mandator, oResponseBody As String) As String Dim oDoc As New XmlDocument() oDoc.LoadXml(oResponseBody) For Each oTable In pTemplate.Tables Logger.Debug("Processing Table [{0}]", oTable.Name) For Each oItem As Template.Column In oTable.Columns Dim oTableName As String = oTable.Name Dim oItemName As String = oItem.Name If oItem.Config.Function Is Nothing Then Continue For End If Logger.Debug("Processing item [{0}]", oItemName) Dim oFunction = oItem.Config.Function.Name Dim oPath = $"//MESOWebService/{oTableName}/{oItemName}" Dim oNodes As XmlNodeList = oDoc.SelectNodes(oPath) Logger.Debug("Calling function [{0}] on note [{1}]", oFunction, oPath) For Each oNode As XmlNode In oNodes If oItem.Config.Function.Name = "GLN" Then Dim oGLN = Winline.TryGetGLN(oNode.InnerText, pMandator) 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) 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 Dim oArray As Byte() Using oStream As New IO.MemoryStream oDoc.Save(oStream) oArray = oStream.ToArray() End Using Dim oXml = Text.Encoding.UTF8.GetString(oArray) oResponseBody = oXml Return oResponseBody End Function #End Region Private Function WriteResponseFileWithSuffix(pPath As String, pBaseFileName As String, pResponseBody As String, pExtension As String, pSuffix As String) As Boolean Try Dim oRequestFileName As String = FileEx.GetFilenameWithSuffix(pBaseFileName, pSuffix, pExtension) Dim oFilePath As String = IO.Path.Combine(pPath, oRequestFileName) IO.File.WriteAllText(oFilePath, pResponseBody) Return True Catch ex As Exception Logger.Error(ex) Return False End Try End Function Private Function WriteResponseFileWithPrefix(pPath As String, pBaseFileName As String, pResponseBody As String, pExtension As String, pPrefix As String) As Boolean Try Dim oRequestFileName As String = FileEx.GetFilenameWithPrefix(pBaseFileName, pPrefix, pExtension) Dim oFilePath As String = IO.Path.Combine(pPath, oRequestFileName) IO.File.WriteAllText(oFilePath, pResponseBody) Return True Catch ex As Exception Logger.Error(ex) Return False End Try End Function Private Function WriteResponseFile(pPath As String, pResponseBody As String, pFileName As String) As Boolean Try Dim oFilePath As String = IO.Path.Combine(pPath, pFileName) IO.File.WriteAllText(oFilePath, pResponseBody) Return True Catch ex As Exception Logger.Error(ex) Return False End Try End Function Private Function ToQueryString(ByVal nvc As NameValueCollection) As String Dim sb As StringBuilder = New StringBuilder("?") Dim first As Boolean = True For Each key As String In nvc.AllKeys For Each value As String In nvc.GetValues(key) If Not first Then sb.Append("&") End If sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value)) first = False Next Next Return sb.ToString() End Function End Class End Namespace