Imports System.Text.RegularExpressions Imports DigitalData.Modules.Logging Imports DigitalData.Modules.Database Imports DigitalData.Modules.Language Imports DigitalData.Modules.Filesystem Imports MultiTool.Common.Winline.Entities Imports MultiTool.Common.Constants Imports MultiTool.Common.Templates Imports MultiTool.Common.Exceptions Imports MultiTool.Common.Documents Namespace Winline Public Class WinlineData Inherits BaseClass Private ReadOnly Property Config As GeneralConfig Private ReadOnly Property MandatorConfig As MandatorConfig Private ReadOnly Property MappingConfig As MappingConfig Private ReadOnly Property Patterns As Patterns Private ReadOnly Property FileEx As File Public Property Articles As New List(Of Article) Public Property Accounts As New List(Of Account) Public Property PackingUnits As New List(Of PackingUnit) Public Property Mandators As New List(Of Mandator) Public Property DocumentKinds As New List(Of DocumentKind) Public Property Years As List(Of Integer) Public Sub New(pLogConfig As LogConfig, pDatabase As MSSQLServer, pConfig As GeneralConfig, pMappingConfig As MappingConfig, pMandatorConfig As MandatorConfig) MyBase.New(pLogConfig, pDatabase) Patterns = New Patterns(pLogConfig, pConfig) Config = pConfig MandatorConfig = pMandatorConfig MappingConfig = pMappingConfig FileEx = New File(pLogConfig) Years = LoadEconomicYears() End Sub ''' ''' Winline Doctype. ''' ''' Used for filtering Documents when exporting and determining the type of document (sales/purchasing) ''' Public Enum DocumentType Undefined = 0 OutgoingOffer = 1 OutgoingOrder = 2 OutgoingDeliveryNote = 3 OutgoingInvoice = 4 IncomingOffer = 5 IncomingOrder = 6 IncomingDeliveryNote = 7 IncomingInvoice = 8 End Enum Public Enum DocumentTypeCategory Undefined = 0 Outgoing = 1 Incoming = 2 End Enum Public Class GetDocumentArgs Public Property Account As Account Public Property Kinds As List(Of DocumentKind) Public Property DateFrom As Date Public Property DateTo As Date Public Property DocNumberFrom As String Public Property DocNumberTo As String Public Property ShowExported As Boolean Public Property Year As Integer End Class Private Function LoadEconomicYears() As IEnumerable(Of Integer) Dim oCurrentYear = Now.Year Dim oRange As IEnumerable(Of Integer) = Enumerable.Range(oCurrentYear - 10, 12).ToList() Return oRange End Function Public Async Function LoadArticlesAsync(pMandator As Mandator) As Task Logger.Info("Loading Articles for Mandator [{0}]", pMandator) Dim oYear = Config.GetWinLineYear() Try Dim oSQL = $" SELECT DISTINCT [c002], -- Artikelnummer [c003], -- Bezeichnung [c075] -- EAN FROM [{pMandator.Server}].[{pMandator.Database}].[dbo].[v021] WHERE mesocomp = '{pMandator.Id}' AND mesoyear = {oYear}" Dim oTable = Await Database.GetDatatableAsync(oSQL) Dim oArticles As New List(Of Article) For Each oRow As DataRow In oTable.Rows Dim oArticleId As String = ItemEx(oRow, V21_ARTICLENUMBER, String.Empty) Dim oArticleDescription As String = ItemEx(oRow, V21_ARTICLEDESCRIPTION, String.Empty) Dim oEAN As String = ItemEx(oRow, V21_EAN, String.Empty) oArticles.Add(New Article With { .Id = oArticleId, .Name = oArticleDescription, .EAN = oEAN, .Mandator = pMandator }) Next Articles.AddRange(oArticles) Logger.Info("[{0}] Articles loaded for Mandator [{1}]", oArticles.Count, pMandator) Catch ex As Exception Logger.Warn("Could not load Articles for Mandator [{0}]", pMandator) Logger.Error(ex) End Try End Function Public Async Function LoadPackingUnitsAsync(pMandator As Mandator) As Task Logger.Info("Loading Packing Units for Mandator [{0}]", pMandator) Dim oYear = Config.GetWinLineYear() Try Dim oSQL = $" SELECT DISTINCT [c002], -- Name [c003], -- Description [c004], -- Description 2 [c005], -- Unit [c006], -- Value/Factor [c007] -- Flag (?) FROM [{pMandator.Server}].[{pMandator.Database}].[dbo].[t346] WHERE mesocomp = '{pMandator.Id}' AND mesoyear = {oYear}" Dim oTable = Await Database.GetDatatableAsync(oSQL) Dim oPackingUnits As New List(Of PackingUnit) For Each oRow As DataRow In oTable.Rows Dim oName As String = ItemEx(oRow, "c002", String.Empty) Dim oDescription As String = ItemEx(oRow, "c003", String.Empty) Dim oDescription2 As String = ItemEx(oRow, "c004", String.Empty) Dim oUnit As Decimal = ItemEx(oRow, "c005", 0.0) Dim oFactor As Decimal = ItemEx(oRow, "c006", 0.0) Dim oFlag As Integer = ItemEx(oRow, "c007", 0) oPackingUnits.Add(New PackingUnit With { .Name = oName, .Description = oDescription, .Description2 = oDescription2, .Unit = oUnit, .Factor = oFactor, .Flag = oFlag, .Mandator = pMandator }) Next PackingUnits.AddRange(oPackingUnits) If oPackingUnits.Count = 0 Then Logger.Warn("No Packing Units loaded for Mandator [{0}]", pMandator) End If Logger.Info("[{0}] Packing Units loaded for Mandator [{1}]", oPackingUnits.Count, pMandator) Catch ex As Exception Logger.Warn("Could not load Packing Units for Mandator [{0}]", pMandator) Logger.Error(ex) End Try End Function Public Async Function LoadAccountsAsync(pMandator As Mandator) As Task Logger.Info("Loading Accounts for Mandator [{0}]", pMandator) Dim oYear = Config.GetWinLineYear() Try Dim oSQL = $" SELECT DISTINCT [c002], -- Kundennummer [c003], -- Kundenname [c050], -- Straße [c052], -- Ort [c051], -- PLZ [c260] -- GLN FROM [{pMandator.Server}].[{pMandator.Database}].[dbo].[v050] WHERE c139 IS NULL -- Kontentyp AND c105 IS NULL -- Inaktiv AND mesocomp = '{pMandator.Id}' AND mesoyear = {oYear}" Dim oTable = Await Database.GetDatatableAsync(oSQL) Dim oAccounts As New List(Of Account) For Each oRow As DataRow In oTable.Rows Dim oAccountNumber As String = ItemEx(oRow, V50_ACCOUNTNUMBER, String.Empty) Dim oAccountName As String = ItemEx(oRow, V50_ACCOUNTNAME, String.Empty) Dim oStreetName As String = ItemEx(oRow, V50_STREETNAME, String.Empty) Dim oZipCode As String = ItemEx(oRow, V50_ZIPCODE, String.Empty) Dim oCityName As String = ItemEx(oRow, V50_CITYNAME, String.Empty) Dim oGLN As String = ItemEx(oRow, V50_GLN, String.Empty) oAccounts.Add(New Account With { .Id = oAccountNumber, .Name = oAccountName, .StreetName = oStreetName, .ZipCode = oZipCode, .CityName = oCityName, .GLN = oGLN, .Mandator = pMandator }) Next Accounts.AddRange(oAccounts) If oAccounts.Count = 0 Then Logger.Warn("No Accounts loaded for Mandator [{0}]", pMandator) End If Logger.Info("[{0}] Accounts loaded for Mandator [{1}]", oAccounts.Count, pMandator) Catch ex As Exception Logger.Warn("Could not load Accounts for Mandator [{0}]", pMandator) Logger.Error(ex) End Try End Function Public Async Function LoadMandatorsAsync() As Task Try Dim oSQL = "SELECT [c000], [c003], [c004] FROM [cwlsystem].[dbo].[T001SRV] (NOLOCK)" Dim oTable = Await Database.GetDatatableAsync(oSQL) Mandators.Clear() For Each oRow As DataRow In oTable.Rows Dim oDbInfo = SplitConnectionInfo(oRow) Dim oMandator = New Mandator With { .Id = ItemEx(oRow, T01_MANDATORID, String.Empty), .Name = ItemEx(oRow, T01_MANDATORNAME, String.Empty), .Database = oDbInfo.Item1, .Server = oDbInfo.Item2 } Dim oMandatorConfig As MandatorConfigItem = MandatorConfig.Items. Where(Function(item) item.Name = oMandator.Id). SingleOrDefault() If oMandatorConfig IsNot Nothing Then oMandator.IsWhitelisted = True oMandator.Order = oMandatorConfig.OrderKey End If Mandators.Add(oMandator) Next Logger.Info("[{0}] Mandators loaded", Mandators.Count) Catch ex As Exception Logger.Warn("Could not load Mandators") Logger.Error(ex) End Try End Function Public Async Function LoadDocumentKindsAsync(pMandator As Mandator) As Task Dim oYear As Integer = Config.GetWinLineYear() Try ' TODO: This is Schaum specific, maybe move to config later Dim oSQL = $" SELECT [c030], [c001], [mesocomp] FROM [{pMandator.Database}].[dbo].[t357] (NOLOCK) WHERE ( [c001] LIKE 'Werk%(VK)' OR [c001] LIKE 'Werk%(WK)' ) AND c129 IS NULL -- Inaktiv AND [mesocomp] = '{pMandator.Id}' AND [mesoyear] = {oYear}" Dim oTable As DataTable = Await Database.GetDatatableAsync(oSQL) Dim oKinds As New List(Of DocumentKind) For Each oRow As DataRow In oTable.Rows oKinds.Add(New DocumentKind With { .Id = ItemEx(oRow, T357_KINDID, String.Empty), .Name = ItemEx(oRow, T357_KINDNAME, String.Empty), .Mandator = pMandator }) Next DocumentKinds.AddRange(oKinds) Logger.Info("[{0}] DocumentKinds loaded for [{1}]", Mandators.Count, pMandator) Catch ex As Exception Logger.Warn("Could not load DocumentKinds") Logger.Error(ex) End Try End Function Public Function TryGetAccount(pGLN As String, pMandator As Mandator) As Account Return TryGetAccount(pGLN, pMandator, "c260", String.Empty) End Function Public Function TryGetAccount(pGLN As String, pMandator As Mandator, pSearchField As String) As Account Return TryGetAccount(pGLN, pMandator, pSearchField, String.Empty) End Function Public Function TryGetAccount(pIdentifier As String, pMandator As Mandator, pSearchField As String, pAlternativeField As String) As Account Try If pIdentifier Is Nothing OrElse pIdentifier = String.Empty Then Return Nothing End If Dim oYear As Integer = Config.GetWinLineYear() Dim oSQL = $" SELECT [c002], -- Kundennummer [c003], -- Kundenname [c050], -- Straße [c052], -- Ort [c051], -- PLZ * -- Everything else FROM [{pMandator.Database}].[dbo].[v050] WHERE [c004] IN (2, 3) -- KontoTyp Debitor/Kreditor AND [{pSearchField}] = '{pIdentifier}' AND [mesocomp] = '{pMandator.Id}' and [mesoyear] = {oYear}" Dim oTable As DataTable = Database.GetDatatable(oSQL) ' GLN not found in this Mandator, continue to next one If oTable.Rows.Count = 0 Then Logger.Debug("GLN [{0}] was not found in Mandator: [{1}]", pIdentifier, pMandator.Id) Return Nothing End If ' Duplicate GLN, exit and do nothing about this number If oTable.Rows.Count > 1 Then Logger.Warn("GLN [{0}] was found more than once in Mandator: [{1}]", pIdentifier, pMandator.Id) Return Nothing End If Dim oRow As DataRow = oTable.Rows.Item(0) Dim oAccountNumber As String = ItemEx(oRow, V50_ACCOUNTNUMBER, String.Empty) Dim oAccountName As String = ItemEx(oRow, V50_ACCOUNTNAME, String.Empty) Dim oStreetName As String = ItemEx(oRow, V50_STREETNAME, String.Empty) Dim oZipCode As String = ItemEx(oRow, V50_ZIPCODE, String.Empty) Dim oCityName As String = ItemEx(oRow, V50_CITYNAME, String.Empty) If pAlternativeField <> String.Empty Then Dim oAlternativeValue = ItemEx(oRow, pAlternativeField, String.Empty) If oAlternativeValue <> String.Empty Then Return TryGetAccount(oAlternativeValue, pMandator, "c002") End If End If Return New Account With { .Id = oAccountNumber, .Name = oAccountName, .StreetName = oStreetName, .CityName = oCityName, .ZipCode = oZipCode, .Mandator = pMandator } Catch ex As Exception Logger.Warn("Error while trying to get account for GLN [{0}]", pIdentifier) Logger.Error(ex) Return Nothing End Try End Function Public Function TryGetGLN(pAccountId As String, pMandator As Mandator) As String Try If pAccountId Is Nothing OrElse pAccountId = String.Empty Then Return Nothing End If Dim oYear As Integer = Config.GetWinLineYear() Dim oSQL = $" SELECT [c260] -- GLN FROM [{pMandator.Database}].[dbo].[v050] WHERE [c004] IN (2, 3) -- KontoTyp Debitor/Kreditor AND [c002] = '{pAccountId}' AND [mesocomp] = '{pMandator.Id}' and [mesoyear] = {oYear}" Dim oGLN As String = Database.GetScalarValue(oSQL) ' GLN not found in this Mandator, continue to next one If oGLN Is Nothing Then Logger.Debug("Account [{0}] was not found in Mandator: [{1}]", pAccountId, pMandator.Id) Return Nothing End If Return oGLN Catch ex As Exception Logger.Warn("Error while trying to get GLN for Account [{0}]", pAccountId) Logger.Error(ex) Return Nothing End Try End Function Public Async Function TryGetArticlePriceAsync(pArticle As String, pAccountNumber As String, pQuantity As String, pDocumentDate As Date, pMandator As Mandator, pTemplate As Template) As Task(Of Double) Return Await TryGetArticlePriceAsync(pArticle, pAccountNumber, pQuantity, pDocumentDate, pMandator, pTemplate, 0) End Function Public Async Function TryGetArticlePriceAsync(pArticle As String, pAccountNumber As String, pQuantity As String, pDocumentDate As Date, pMandator As Mandator, pTemplate As Template, pWaitingDays As Integer) As Task(Of Double) Try Dim oUserName = Environment.UserName Dim oYear As Integer = Config.GetWinLineYear() Dim oDebug = Convert.ToInt32(LogConfig.Debug) ' TODO: pGroupPropertyID in config ' TODO: pTempTableSuffix in config (nice to have) Dim oDateFrom = pDocumentDate.AddDays(pWaitingDays * -1) Dim oDateTo = pDocumentDate Dim oSQL As String = $" SET SHOWPLAN_ALL OFF; SET NOCOUNT ON; SET ARITHABORT ON; EXEC [{pMandator.Database}].[dbo].[PRCUST_GET_ACCOUNT_PRICE_CONDITION_VALUES] @pAccountNr = '{pAccountNumber}', @pGroupNr = '*', @pProductNr = '{pArticle}', @pProductQuantity = '<={pQuantity}', @pProductPriceDateFrom = '{oDateFrom:dd.MM.yyyy}', @pProductPriceDateTo = '{oDateTo:dd.MM.yyyy}', @pmesocomp = '{pMandator.Id}', @pmesoyear = {oYear}, @pGroupPropertyID = 1008, @pTempTableSuffix = 'MT_USER_{oUserName}', @pBatchID = NULL, @pPrintDebug = 0, @pLog2DB = {oDebug}, @pComment = 'Multitool/{pTemplate.Name}', @pResultType = 'CalcPricing'; SET NOCOUNT OFF; " Dim oTable As DataTable = Await Database.GetDatatableAsync(oSQL) If oTable.Rows.Count = 0 Then Logger.Debug("Price for article [{0}] and Account [{1}] was not found in Mandator: [{2}]", pArticle, pAccountNumber, pMandator.Id) Return Nothing End If Dim oRow As DataRow = oTable.Rows.Item(0) Dim oPrice As Double = oRow.Item("PRICE") Dim oPackingUnit As PackingUnit = MaybeGetPackingUnit(pTemplate, oRow, pMandator) If oPackingUnit IsNot Nothing AndAlso oPackingUnit.Factor > 0 Then oPrice *= oPackingUnit.Factor End If Return oPrice Catch ex As Exception Logger.Warn("Error while trying to get Price for Article [{0}] and Account [{1}]", pArticle, pAccountNumber) Logger.Error(ex) Return Nothing End Try End Function Private Function MaybeGetPackingUnit(pTemplate As Template, oRow As DataRow, pMandator As Mandator) As PackingUnit Dim oPackingUnit As PackingUnit = Nothing Select Case pTemplate.DocTypeCategory Case DocumentTypeCategory.Incoming Dim oPackingUnitPurchasingName = oRow.ItemEx("PACKINGUNIT_PURCHASING", "") Dim oPackingUnitPurchasing = PackingUnits. Where(Function(unit) unit.Name.Equals(oPackingUnitPurchasingName) And unit.Mandator.Equals(pMandator)). FirstOrDefault() oPackingUnit = oPackingUnitPurchasing Case DocumentTypeCategory.Outgoing Dim oPackingUnitSalesName = oRow.ItemEx("PACKINGUNIT_SALES", "") Dim oPackingUnitSales = PackingUnits. Where(Function(unit) unit.Name.Equals(oPackingUnitSalesName) And unit.Mandator.Equals(pMandator)). FirstOrDefault() oPackingUnit = oPackingUnitSales End Select Return oPackingUnit End Function ''' ''' This function is completely SCHAUM related. ''' Public Async Function TryGetWaitingDaysAsync(pDocumentKind As Integer, pMandator As Mandator) As Task(Of Integer) Try Dim oSql = $" SELECT [Karenztage].[u012] FROM [{pMandator.Database}].[dbo].[t670] As [Werksdefinition] INNER JOIN [{pMandator.Database}].[dbo].[t670] AS [Werkszuordnung] ON [Werksdefinition].[u007] = [Werkszuordnung].[u007] INNER JOIN [{pMandator.Database}].[dbo].[t670] AS [Karenztage] ON [Werksdefinition].[u000] = [Karenztage].[u032] WHERE [Werksdefinition].[u011] In ({pDocumentKind},{pDocumentKind}*10) AND [Werkszuordnung].[u006] = '%ConditionAccountNr%' AND [Karenztage].[u002] = 'Karenztage' " Dim oWaitingDays As Integer = Await Database.GetScalarValueAsync(oSql) If IsNothing(oWaitingDays) Then oWaitingDays = 0 End If Return oWaitingDays Catch ex As Exception Logger.Error(ex) Return 0 End Try End Function Public Function TryGetArticleNumber(pEAN As String, pMandator As Mandator) As String Try If pEAN = String.Empty Then Logger.Warn("Could not get ArticleNumber, EAN is empty.") Return Nothing End If Dim oYear As Integer = Config.GetWinLineYear() Dim oSQL As String = $" SELECT [c011], -- Artikelnummer [c003], -- Artikelbezeichnung [c075], -- EAN-Nummer [c123] -- Ersatzartikelnummer FROM [{pMandator.Database}].[dbo].[v021] WHERE [c075] = '{pEAN}' AND [c038] IS NULL -- Aktiv AND [mesocomp] = '{pMandator.Id}' AND [mesoyear] = {oYear}" Dim oTable As DataTable = Database.GetDatatable(oSQL) ' EAN not found in this Mandator If oTable.Rows.Count = 0 Then Logger.Debug("EAN [{0}] was not found in Mandator: [{1}]", pEAN, pMandator.Id) Return Nothing End If ' Duplicate EAN, exit and do nothing about this number If oTable.Rows.Count > 1 Then Logger.Warn("EAN [{0}] was found more than once", pEAN) Return Nothing End If Dim oRow As DataRow = oTable.Rows.Item(0) Dim oArticleNumber As String = ItemEx(oRow, V21_MAINARTICLENUMBER, String.Empty) Logger.Info("Found ArticleNumber [{0}] in Column [{1}]", oArticleNumber, V21_MAINARTICLENUMBER) Return oArticleNumber Catch ex As Exception Logger.Error(ex) Return Nothing End Try End Function Public Function TryGetEAN(pArticleNumber As String, pMandator As Mandator) As String Try Dim oYear As Integer = Config.GetWinLineYear() Dim oSQL As String = $" SELECT [c075] -- EAN-Nummer FROM [{pMandator.Database}].[dbo].[v021] WHERE [c011] = '{pArticleNumber}' AND [mesocomp] = '{pMandator.Id}' AND [mesoyear] = {oYear}" Dim oEAN As String = Database.GetScalarValue(oSQL) ' EAN not found in this Mandator, continue to next one If oEAN Is Nothing Then Logger.Debug("ArticleNumber [{0}] was not found in Mandator: [{1}]", pArticleNumber, pMandator.Id) Return Nothing End If Return oEAN Catch ex As Exception Logger.Error(ex) Return Nothing End Try End Function Public Function GetContacts(pAccountNumber As String, pMandator As Mandator) As List(Of Contact) Try Dim oContacts As New List(Of Contact) Dim oYear As Integer = Config.GetWinLineYear() Dim oSQL As String = $" SELECT [c000], -- Key [c001], -- Name [c063] -- Kontaktnummer FROM [{pMandator.Database}].[dbo].[t045] WHERE [c063] LIKE '{pAccountNumber}-%' AND [mesocomp] = '{pMandator.Id}' AND [mesoyear] = {oYear}" Dim oTable As DataTable = Database.GetDatatable(oSQL) ' Contact not found in this Mandator, continue to next one If oTable.Rows.Count = 0 Then Logger.Debug("Contact for Account [{0}] was not found in Mandator: [{1}]", pAccountNumber, pMandator.Id) Return Nothing End If For Each oRow In oTable.Rows oContacts.Add(New Contact With { .Id = ItemEx(Of String)(oRow, T45_KEY, Nothing), .Name = ItemEx(Of String)(oRow, T45_NAME, Nothing), .Number = ItemEx(Of String)(oRow, T45_CONTACTNUMBER, Nothing) }) Next Return oContacts Catch ex As Exception Logger.Error(ex) Return Nothing End Try End Function Public Function GetReplacementArticleNumber(pArticleNumber As String, pMandator As Mandator) Try Dim oYear As Integer = Config.GetWinLineYear() Dim oSQL As String = $" SELECT [c011], -- Artikelnummer [c003], -- Artikelbezeichnung [c075], -- EAN-Nummer [c123] -- Ersatzartikelnummer FROM [{pMandator.Database}].[dbo].[v021] WHERE [c011] = '{pArticleNumber}' AND [mesocomp] = '{pMandator.Id}' AND [mesoyear] = {oYear}" Dim oTable As DataTable = Database.GetDatatable(oSQL) ' ArticleNumber not found in this Mandator, continue to next one If oTable.Rows.Count = 0 Then Logger.Debug("ArticleNumber [{0}] was not found in Mandator: [{1}]", pArticleNumber, pMandator.Id) Return Nothing End If ' Duplicate EAN, exit and do nothing about this number If oTable.Rows.Count > 1 Then Logger.Warn("ArticleNumber [{0}] was found more than once", pArticleNumber) Return Nothing End If Dim oRow As DataRow = oTable.Rows.Item(0) Dim oReplacementArticleNumber = ItemEx(Of String)(oRow, V21_REPLACEMENTARTICLENUMBER, Nothing) If oReplacementArticleNumber Is Nothing Then Return pArticleNumber End If Return GetReplacementArticleNumber(oReplacementArticleNumber, pMandator) Catch ex As Exception Logger.Error(ex) Return Nothing End Try End Function Public Function FindMatchingMandatorFromOrder(pData As Documents.Document) As Mandator Dim oPositions = pData.Rows. Where(Function(r) r.TableName.ToUpper.EndsWith("T026")). ToList() Dim oEANNumbers = oPositions. Select(Function(p) If p.Fields.ContainsKey("Artikelnummer") Then Return p.Fields.Item("Artikelnummer").Original Else Return Nothing End If End Function). Where(Function(ean) ean IsNot Nothing). Distinct(). ToList() Dim oYear = Config.GetWinLineYear() Dim oMandatorId As String = String.Empty Dim oWhitelistedMandators = Mandators. Where(Function(m) m.IsWhitelisted = True). OrderBy(Function(m) m.Order). ToList() For Each oEANNumber In oEANNumbers For Each oMandator In oWhitelistedMandators Dim oSQL As String = $" SELECT [c011], -- Artikelnummer [c003], -- Artikelbezeichnung [c075], -- EAN-Nummer [c123] -- Ersatzartikelnummer FROM [{oMandator.Database}].[dbo].[v021] WHERE [c075] = '{oEANNumber}' AND [c038] IS NULL AND [mesocomp] = '{oMandator.Id}' AND [mesoyear] = {oYear}" Dim oTable As DataTable = Database.GetDatatable(oSQL) ' EAN not found in this Mandator, continue to next one If oTable.Rows.Count = 0 Then Logger.Debug("EAN [{0}] was not found in Mandator: [{1}]", oEANNumber, oMandator.Id) Continue For End If ' Duplicate EAN, exit and do nothing about this manda If oTable.Rows.Count > 1 Then Logger.Warn("EAN [{0}] was found more than once. Skipping Mandator [{1}]", oEANNumber, oMandator.Id) Exit For End If Dim oRow As DataRow = oTable.Rows.Item(0) Dim oArticleNumber As String = ItemEx(oRow, V21_MAINARTICLENUMBER, String.Empty) ' EAN was found, now we need to check it against the Regex of the current Mandantor, if one exists Dim oMappingConfigItems = MappingConfig.Items. Where(Function(item) item.DestinationName = "MANDATOR" And item.DestinationValue = oMandator.Id And item.SourceName = "ARTICLE"). ToList() Logger.Debug("Processing [{0}] Mappings for Articlenumber [{1}]", oMappingConfigItems.Count, oArticleNumber) ' If not match was found, continune to next mandator. ' For a catch all mandator, a regex like ".+" is needed. For Each oItem In oMappingConfigItems Try Dim oRegex As New Regex(oItem.SourceRegex) Dim oMatch = oRegex.Match(oArticleNumber) ' If ArticleNumber matches the regex, we assign it and exit If oMatch.Success Then Return oMandator Else ' If it does not match, continue to the next mandator End If Catch ex As Exception Logger.Error(ex) Logger.Warn("Regex [{0}] could not be parsed. Skipping.", oMandator.Regex) End Try Next ' CONFIG ITEM Next ' MANDATOR Next ' EAN Return Nothing End Function Public Function GetDocuments(pMandator As Mandator, pTemplate As Template, pDocumentType As DocumentType, pOptions As GetDocumentArgs) As List(Of ExportDocument) Try Dim oYear As Integer = Config.GetWinLineYear() If pOptions.Year > 0 Then oYear = Config.GetWinLineYear(pOptions.Year) End If Dim oTypeConstraint, oTypeNumberColumn As String Select Case pDocumentType Case DocumentType.OutgoingOffer oTypeConstraint = $"T.c137 = 2 AND c139 = 1 AND " oTypeNumberColumn = "T.c043" Case DocumentType.OutgoingOrder oTypeConstraint = $"T.c137 = 2 AND c139 = 2 AND " oTypeNumberColumn = "T.c044" Case DocumentType.OutgoingDeliveryNote oTypeConstraint = $"T.c137 = 2 AND (c139 = 3 OR c139 = -3) AND " oTypeNumberColumn = "T.c045" Case DocumentType.OutgoingInvoice oTypeConstraint = $"T.c137 = 2 AND c139 = 4 AND " oTypeNumberColumn = "T.c055" Case DocumentType.IncomingOffer oTypeConstraint = $"T.c137 = 3 AND c139 = 1 AND " oTypeNumberColumn = "T.c043" Case DocumentType.IncomingOrder oTypeConstraint = $"T.c137 = 3 AND c139 = 2 AND " oTypeNumberColumn = "T.c044" Case DocumentType.IncomingDeliveryNote oTypeConstraint = $"T.c137 = 3 AND c139 = 3 AND " oTypeNumberColumn = "T.c045" Case DocumentType.IncomingInvoice oTypeConstraint = $"T.c137 = 3 AND c139 = 4 AND " oTypeNumberColumn = "T.c055" Case Else oTypeConstraint = "" oTypeNumberColumn = "" End Select Dim oAccountConstraint = "" If pOptions.Account IsNot Nothing Then oAccountConstraint = $"T.c021 = '{pOptions.Account.Id}' AND " End If Dim oKindConstraint = "" If pOptions.Kinds IsNot Nothing AndAlso pOptions.Kinds.Count > 0 Then Dim oKindIdList = pOptions.Kinds.Select(Function(kind) kind.Id) Dim oKindIdString = String.Join(",", oKindIdList) oKindConstraint = $"T.c035 IN ({oKindIdString}) AND " End If Dim oDateFromConstraint = "" If pOptions.DateFrom <> Date.MinValue Then oDateFromConstraint = $"T2.DATE >= CAST('{pOptions.DateFrom:yyyy-MM-dd}' as date) AND " End If Dim oDateToConstraint = "" If pOptions.DateTo <> Date.MinValue Then oDateToConstraint = $"T2.DATE <= CAST('{pOptions.DateTo:yyyy-MM-dd}' as date) AND " End If Dim oDocNumberConstraint = "" If pDocumentType <> DocumentType.Undefined Then If pOptions.DocNumberFrom <> String.Empty Then oDocNumberConstraint &= $"{oTypeNumberColumn} >= '{pOptions.DocNumberFrom}' AND " End If If pOptions.DocNumberTo <> String.Empty Then oDocNumberConstraint &= $"{oTypeNumberColumn} <= '{pOptions.DocNumberTo}' AND " End If Else oDocNumberConstraint &= $" (T.c043 >= '{pOptions.DocNumberFrom}' AND T.c043 <= '{pOptions.DocNumberTo}') OR (T.c044 >= '{pOptions.DocNumberFrom}' AND T.c044 <= '{pOptions.DocNumberTo}') OR (T.c045 >= '{pOptions.DocNumberFrom}' AND T.c045 <= '{pOptions.DocNumberTo}') OR (T.c055 >= '{pOptions.DocNumberFrom}' AND T.c055 <= '{pOptions.DocNumberTo}') " End If ' Build the constraint so that the default view will show all documents that do not have an exported ' flag set to the current document type. ' Ex. Search for orders will not show exported flag = 2 by default Dim oDocType As Integer = Math.Abs(Convert.ToInt32(pDocumentType)) Dim oExportedConstraint = $"(T.U010 IS NULL OR T.U010 <> {oDocType}) AND" If pOptions.ShowExported Then oExportedConstraint = "" End If Dim oSql = $" SELECT T2.DATE, T.c139 DOCUMENT_TYPE, T.c035 DOCUMENT_KIND, T.c021 ACCOUNT_NUMBER, T.c022 RUNNING_NUMBER, T.c043 OFFER_NUMBER, T.c027 OFFER_DATE, T.c044 ORDER_NUMBER, T.c028 ORDER_DATE, T.c045 DELIVERY_NUMBER, T.c029 DELIVERY_DATE, T.c055 INVOICE_NUMBER, T.c032 INVOICE_DATE, T.c100 GROSS_AMOUNT, T.c114 NET_AMOUNT, T.U010 ALREADY_EXPORTED, T.U011 EXPORTED_WHO, T.U012 EXPORTED_WHEN, T.U013 EXPORTED_FILE FROM [{pMandator.Database}].[dbo].[T025] T INNER JOIN (SELECT * FROM ( SELECT c021, c022, mesoyear, mesocomp, c027 [DATE] FROM [{pMandator.Database}].[dbo].[T025] WHERE c139 = 1 OR c139 = -1 UNION SELECT c021, c022, mesoyear, mesocomp, c028 [DATE] FROM [{pMandator.Database}].[dbo].[T025] WHERE c139 = 2 OR c139 = -2 UNION SELECT c021, c022, mesoyear, mesocomp, c029 [DATE] FROM [{pMandator.Database}].[dbo].[T025] WHERE c139 = 3 OR c139 = -3 UNION SELECT c021, c022, mesoyear, mesocomp, c032 [DATE] FROM [{pMandator.Database}].[dbo].[T025] WHERE c139 = 4 OR c139 = -4 ) QUERY) T2 ON T.c021 = T2.c021 AND T.c022 = T2.c022 AND T.mesoyear = T2.mesoyear AND T.mesocomp = T2.mesocomp WHERE {oTypeConstraint} {oAccountConstraint} {oKindConstraint} {oExportedConstraint} {oDocNumberConstraint} {oDateFromConstraint} {oDateToConstraint} T.[mesocomp] = '{pMandator.Id}' AND T.[mesoyear] = {oYear}" Dim oTable As DataTable = Database.GetDatatable(oSql) Dim oDocuments As New List(Of ExportDocument) For Each oRow As DataRow In oTable.Rows Try Dim oDocument = GetDocumentFromDataRow(oRow, pDocumentType) oDocument.Schema = pTemplate oDocuments.Add(oDocument) Catch ex As Exception Logger.Error(ex) End Try Next Return oDocuments Catch ex As Exception Logger.Warn("Error while loading documents for mandator [{0}] and document type [{1}]", pMandator, pDocumentType) Logger.Error(ex) Return Nothing End Try End Function Private ReadOnly RunningNumberVersionRegex As Regex = New Regex("~\d{1,3}$") Private ReadOnly RunningNumberMaximumLength As Integer = 20 Public Async Function GetVersionedRunningNumberAsync(pDocument As Document, pMandator As Mandator, pAccountNumber As String, pRunningNumber As String) As Task(Of String) Try Dim oYear As Integer = Config.GetWinLineYear() Dim oSql As String = $" SELECT COUNT(*) FROM [{pMandator.Database}].[dbo].[v250] WHERE [c021] = '{pAccountNumber}' AND -- Account ' [c022] = '{pRunningNumber}' AND -- Running Number ' --[c035] = '4711' -- Belegart, needed? [c144] IS NULL AND -- Stornonummer Angebot [c145] IS NULL AND -- Stornonummer Auftrag [c146] IS NULL AND -- Stornonummer Lieferschein [c147] IS NULL AND -- Stornonummer Faktura [mesocomp] = '{pMandator.Id}' --AND [mesoyear] = {oYear} " Dim oExistingCount = Await Database.GetScalarValueAsync(oSql) If oExistingCount = 0 Then Logger.Debug("Running number [{0}] does not exist yet. Returning.", pRunningNumber) Return pRunningNumber Else Logger.Debug("Running number [{0}] already exists. Checking again.", pRunningNumber) Dim oVersionResult = FileEx.GetVersionedString(pRunningNumber, "~"c) Dim oNewVersion = oVersionResult.Item2 + 1 Dim oFinalLength As Integer = oVersionResult.Item1.Count + oNewVersion.ToString.Count + 1 If oFinalLength > RunningNumberMaximumLength Then Logger.Warn("Running number is too long ({0} chars total) and cannot be versioned. Versioning needs at least 2 characters.", oFinalLength) pDocument.AddDocumentError(DocumentErrorType.AttributeValidationFailed, "Das Feld Laufnummer hat die zulässige Länge überschritten.") Return pRunningNumber End If Return Await GetVersionedRunningNumberAsync(pDocument, pMandator, pAccountNumber, $"{oVersionResult.Item1}~{oNewVersion}") End If Catch ex As MultiToolException Logger.Error(ex) Throw ex Catch ex As Exception Logger.Warn("Error while getting versioned running number for mandator [{0}] and running number [{1}]", pMandator, pRunningNumber) Logger.Error(ex) Return Nothing End Try End Function Public Function VersionNumber(pNumber As String) As String Dim oRunningNumberBase As String If RunningNumberVersionRegex.IsMatch(pNumber) Then Dim oSplitNumber = pNumber.Split("~") oRunningNumberBase = oSplitNumber.First() Dim oRunningNumberVersion As Integer = Integer.Parse(oSplitNumber.Last()) Return $"{oRunningNumberBase}~{oRunningNumberVersion + 1}" Else Return $"{pNumber}~2" End If End Function Public Async Function ExecuteFinalSQLForExport(pDocument As ExportDocument, pTemplate As Template, pMandator As Mandator) As Task(Of Boolean) Try Dim oSql As String = Patterns.ReplaceForExport(pDocument, pMandator, pTemplate.FinalSQL) Return Await Database.ExecuteNonQueryAsync(oSql) Catch ex As Exception Logger.Error(ex) Return False End Try End Function Public Async Function ExecuteFinalSQLForImport(pDocument As Document, pTemplate As Template, pReportFilename As String) As Task(Of Boolean) Try Dim oSql As String = Patterns.ReplaceForImportFinalSQL(pDocument, pReportFilename, pTemplate.FinalSQL) Return Await Database.ExecuteNonQueryAsync(oSql) Catch ex As Exception Logger.Error(ex) Return False End Try End Function Private Function GetDocumentFromDataRow(pDataRow As DataRow, pDocumentType As DocumentType) As ExportDocument Dim oAccountNumber = pDataRow.Item("ACCOUNT_NUMBER") Dim oRunningNumber As String = pDataRow.Item("RUNNING_NUMBER") Dim oDocumentType As Integer = pDataRow.Item("DOCUMENT_TYPE") Dim oDocumentKind As Integer = pDataRow.Item("DOCUMENT_KIND") Dim oGrossAmount As Double = pDataRow.Item("GROSS_AMOUNT") Dim oNetAmount As Double = pDataRow.Item("NET_AMOUNT") Dim oExportedType As Integer = pDataRow.ItemEx("ALREADY_EXPORTED", 0) Dim oExportedWho As String = pDataRow.ItemEx("EXPORTED_WHO", "") Dim oExportedWhen As Date = pDataRow.ItemEx(Of Date)("EXPORTED_WHEN", Nothing) Dim oExportedFile As String = pDataRow.ItemEx("EXPORTED_FILE", "") Dim oIsExported As Boolean = GetIsExported(oExportedType, pDocumentType) Dim oDocumentNumber As String = Nothing Dim oDocumentDate As Date = Nothing Dim oDocumentDateColumn As String = Nothing Dim oAccount = Accounts. Where(Function(acc) acc.Id = oAccountNumber). FirstOrDefault() Dim oKind = DocumentKinds. Where(Function(kind) kind.Id = oDocumentKind). FirstOrDefault() Select Case oDocumentType Case 1, -1 oDocumentNumber = pDataRow.Item("OFFER_NUMBER") oDocumentDate = pDataRow.Item("OFFER_DATE") oDocumentDateColumn = "c027" Case 2, -2 oDocumentNumber = pDataRow.Item("ORDER_NUMBER") oDocumentDate = pDataRow.Item("ORDER_DATE") oDocumentDateColumn = "c028" Case 3, -3 oDocumentNumber = pDataRow.Item("DELIVERY_NUMBER") oDocumentDate = pDataRow.Item("DELIVERY_DATE") oDocumentDateColumn = "c029" Case 4, -4 oDocumentNumber = pDataRow.Item("INVOICE_NUMBER") oDocumentDate = pDataRow.Item("INVOICE_DATE") oDocumentDateColumn = "c032" End Select Dim oDocument As New ExportDocument With { .Account = oAccount, .RunningNumber = oRunningNumber, .Number = oDocumentNumber, .[Date] = oDocumentDate, .DateColumn = oDocumentDateColumn, .Kind = oKind, .GrossAmount = oGrossAmount, .NetAmount = oNetAmount, .ExportedWhen = oExportedWhen, .ExportedWho = oExportedWho, .FilenameExport = oExportedFile, .IsExported = oIsExported } Return oDocument End Function Private Function GetIsExported(pExportedType As Integer, pDocumentType As DocumentType) As Boolean Dim oIsExported = False If pExportedType = 1 AndAlso (pDocumentType = DocumentType.IncomingOffer Or pDocumentType = DocumentType.OutgoingOffer) Then oIsExported = True ElseIf pExportedType = 2 AndAlso (pDocumentType = DocumentType.IncomingOrder Or pDocumentType = DocumentType.OutgoingOrder) Then oIsExported = True ElseIf pExportedType = 3 AndAlso (pDocumentType = DocumentType.IncomingDeliveryNote Or pDocumentType = DocumentType.OutgoingDeliveryNote) Then oIsExported = True ElseIf pExportedType = 4 AndAlso (pDocumentType = DocumentType.IncomingInvoice Or pDocumentType = DocumentType.OutgoingInvoice) Then oIsExported = True End If Return oIsExported End Function ''' ''' Turns a database info like "SQLCWLDATEN on SERVER\INSTANCE" into a Tuple of two strings ''' ''' ''' Private Function SplitConnectionInfo(pRow As DataRow) As Tuple(Of String, String) Dim oDbInfo = pRow.Item(T01_DATABASEINFO).ToString() Dim oSplittedInfo = SplitAtString(oDbInfo.ToUpper, "ON") Dim oServer = oSplittedInfo.Item(1).Trim() Dim oDatabase = oSplittedInfo.Item(0).Trim() If oDatabase.StartsWith("SQL") Then oDatabase = oDatabase.Remove(0, 3) End If Return New Tuple(Of String, String)(oDatabase, oServer) End Function Private Function SplitAtString(pStringToSplit As String, pDelimiter As String) As List(Of String) Dim oDelimiter As String() = New String(0) {pDelimiter} Return pStringToSplit. Split(oDelimiter, StringSplitOptions.None). ToList() End Function End Class End Namespace