MultiTool/MultiTool.Common/Winline/WebServiceData.vb
2022-06-10 11:48:41 +02:00

480 lines
20 KiB
VB.net

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"
''' <summary>
''' Transfers a document to winline via Webservices
''' </summary>
''' <param name="pDocument"></param>
''' <param name="pTemplate"></param>
''' <param name="pMandator"></param>
''' <param name="pIsTest"></param>
''' <exception cref="HttpRequestException"></exception>
''' <exception cref="WebServiceException"></exception>
''' <exception cref="TaskCanceledException"></exception>
''' <returns>True if request was successful.</returns>
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