From bc137b27ee730a5a686db40b1e8b4049f5cd35f3 Mon Sep 17 00:00:00 2001 From: Basimodo Date: Thu, 24 Jun 2021 18:14:47 +0200 Subject: [PATCH] Fixed DbHelper. --- Gremlin/GremlinData/DBClasses/DbHelper.cs | 1536 +++++++++------------ 1 file changed, 691 insertions(+), 845 deletions(-) diff --git a/Gremlin/GremlinData/DBClasses/DbHelper.cs b/Gremlin/GremlinData/DBClasses/DbHelper.cs index 1bf9af7..227168e 100644 --- a/Gremlin/GremlinData/DBClasses/DbHelper.cs +++ b/Gremlin/GremlinData/DBClasses/DbHelper.cs @@ -5,16 +5,15 @@ using Microsoft.VisualBasic.FileIO; using MySqlConnector; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Data; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; +using static Gremlin.GremlinData.EntityClasses.Enums; using Gremlin.GremlinUtilities; using Gremlin.GremlinData.EntityClasses; -using static Gremlin.GremlinData.EntityClasses.Enums; namespace Gremlin.GremlinData.DBClasses { @@ -70,8 +69,8 @@ namespace Gremlin.GremlinData.DBClasses { using (GremlinContext db = new()) { - _ = db.Database.EnsureDeleted(); - _ = db.Database.EnsureCreated(); + db.Database.EnsureDeleted(); + db.Database.EnsureCreated(); } } @@ -117,7 +116,7 @@ namespace Gremlin.GremlinData.DBClasses using (GremlinContext db = new()) { _ = db.ProductLines.Add(PlToBeAdded); - _ = db.SaveChanges(); + db.SaveChanges(); } } @@ -150,8 +149,8 @@ namespace Gremlin.GremlinData.DBClasses using (GremlinContext db = new()) { - _ = db.AccountTypes.Add(ATtoBeAdded); - _ = db.SaveChanges(); + db.AccountTypes.Add(ATtoBeAdded); + db.SaveChanges(); } } @@ -183,8 +182,8 @@ namespace Gremlin.GremlinData.DBClasses using (GremlinContext db = new()) { - _ = db.SubMarkets.Add(SMtoBeAdded); - _ = db.SaveChanges(); + db.SubMarkets.Add(SMtoBeAdded); + db.SaveChanges(); } } @@ -535,7 +534,7 @@ namespace Gremlin.GremlinData.DBClasses // Skip the row with the column names: - if (dataHasHeading) _ = csvParser.ReadLine(); + if (dataHasHeading) csvParser.ReadLine(); while (!csvParser.EndOfData) { @@ -616,7 +615,7 @@ namespace Gremlin.GremlinData.DBClasses foreach (Contact contact in ContactsReadFromFile) { // AccountID aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen. - contact.Account = ResolveAccountById(db, contact.Notes); + contact.Account = ResolveAccountById(db, uint.Parse(contact.Notes)); contact.Notes = ""; contact.DataModificationByUser = "CSVImporter"; contact.DataStatus = Status.Active.ToString(); @@ -629,11 +628,11 @@ namespace Gremlin.GremlinData.DBClasses } db.Contacts.AddRange(ContactsReadFromFile); - _ = db.SaveChanges(); + db.SaveChanges(); } } //Bestätigung senden - _ = MessageBox.Show($"Es wurden {ContactsReadFromFile.Count} Contacts erfolgreich der Datenbank hinzugefügt."); + MessageBox.Show($"Es wurden {ContactsReadFromFile.Count} Contacts erfolgreich der Datenbank hinzugefügt."); return true; } @@ -678,7 +677,7 @@ namespace Gremlin.GremlinData.DBClasses csvParser.HasFieldsEnclosedInQuotes = true; // Skip the row with the column names: - if (dataHasHeading) _ = csvParser.ReadLine(); + if (dataHasHeading) csvParser.ReadLine(); while (!csvParser.EndOfData) { @@ -763,7 +762,7 @@ namespace Gremlin.GremlinData.DBClasses } //Eingelesenen Account in DB schreiben: - using (GremlinContext gremlinContext = new()) + using (GremlinContext db = new()) { DateTime now = DateTime.Now; @@ -772,34 +771,34 @@ namespace Gremlin.GremlinData.DBClasses // AccountType aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen. if (account.AccountType.AccountTypeCode != "") { - AccountType accountType = gremlinContext.AccountTypes + AccountType accountType = db.AccountTypes .Where(a => a.AccountTypeCode == account.AccountType.AccountTypeCode) .First(); account.AccountType = accountType; - account.AccountType = ResolveAccountType(gremlinContext, account.AccountType.AccountTypeCode); + account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode); } else { //Default - account.AccountType = ResolveAccountType(gremlinContext, "FPC"); + account.AccountType = ResolveAccountType(db, "FPC"); } if (account.SubMarket.SubMarketCode != "") { - SubMarket subMarket = gremlinContext.SubMarkets + SubMarket subMarket = db.SubMarkets .Where(a => a.SubMarketCode == account.SubMarket.SubMarketCode) .First(); account.SubMarket = subMarket; //Warum gelöscht? account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode); - account.SubMarket = ResolveSubmarket(gremlinContext, account.SubMarket.SubMarketCode); + account.SubMarket = ResolveSubmarket(db, account.SubMarket.SubMarketCode); } else { //Default - account.SubMarket = ResolveSubmarket(gremlinContext, "COT"); + account.SubMarket = ResolveSubmarket(db, "COT"); } account.DataValidFrom = now; @@ -812,101 +811,16 @@ namespace Gremlin.GremlinData.DBClasses account.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportAccountsFromCSV)"; } - gremlinContext.Accounts.AddRange(AccountsReadFromFile); - _ = gremlinContext.SaveChanges(); + db.Accounts.AddRange(AccountsReadFromFile); + db.SaveChanges(); //Bestätigung senden - _ = MessageBox.Show($"Es wurden {AccountsReadFromFile.Count} Accounts erfolgreich der Datenbank hinzugefügt."); - } - } - return true; - } - - public static bool GenericImporter(string filepath = "")//, string separator = "|") - //Ein (möglichst) generischer Importer - //1. Dateipfad erfassen - //2. Column <-> Property Mapping - //3. Typ der zu importierenden Daten herausfinden - //4. Daten einlesen, konvertieren, validieren, Metadaten setzen und alles in Liste(n) speichern - //5. Datenliste(n) in DB speichern - { - if (filepath == "") filepath = FileHelper.GetFilepathFromUser(); - if (filepath == "") return false; - - using (TextFieldParser csvParser = new(filepath, FileHelper.GetEncoding(filepath))) - { - //Parser configuration: - //csvParser.Delimiters = new string[] { separator }; - csvParser.CommentTokens = new string[] { "#" }; - csvParser.HasFieldsEnclosedInQuotes = true; - - //dynamische Spaltenzuordnung in Dictonary speichern - Dictionary ColumnToPropertyMapping = FileIO.ReadMappingTableFromFile(); - Dictionary columnNumberOf = new(); - string[] fields = csvParser.ReadFields(); - columnNumberOf = GenericColumnMapper(fields, ColumnToPropertyMapping); - - //determine data type to be imported - //prepare lists - - //bool dataIsValid; - - while (!csvParser.EndOfData) - { - //read - //convert - //validate - //set metadata: SetMetadataForImport(IMetadata) - //add to list - - fields = csvParser.ReadFields(); // Read current line fields, pointer moves to the next line. - if (fields[0] == "") break; // Am Ende hängt eine leere Zeile, die im Parser einen Fehler auslösen würde. - } - - using (GremlinContext db = new()) - { - //add to context - //savechanges + MessageBox.Show($"Es wurden {AccountsReadFromFile.Count} Accounts erfolgreich der Datenbank hinzugefügt."); } } return true; } - public static IMetadata SetMetadataForImport(IMetadata entity) - { - entity.DataCreationDate = DateTime.Now; - entity.DataModificationByUser = ""; - entity.DataModificationDate = DateTime.Now; - entity.DataStatus = Status.Active.ToString(); - entity.DataValidFrom = DateTime.Now; - entity.DataValidUntil = FarInTheFuture; - entity.DataVersionNumber++; - entity.DataVersionComment = ""; - - return entity; - } - - public static Dictionary GenericColumnMapper(string[] headings, Dictionary mappingTable) - { - Dictionary result = new(); - for (int i = 0; i < headings.Length; i++) - { - string heading = headings[i].ToLower(CultureInfo.CurrentCulture) - .Trim() - .Replace(" ", "") - .Replace("-", "") - .Replace("_", "") - .Replace(".", "") - .Replace(":", ""); - - if (mappingTable.TryGetValue(heading, out string value)) - { - result.Add(value, i); - } - } - return result; - } - public static bool ImportLSAGContactListToolData(string filepath = "", string separator = ";") ///Importiert Accounts und Contacts aus CSV in die DB @@ -1022,7 +936,7 @@ namespace Gremlin.GremlinData.DBClasses } //Validierten Account der Liste hinzufügen: - if (DataHasError == false) _ = ImportedAccount.AddIfUnique(AccountsReadFromFile); + if (DataHasError == false) ImportedAccount.AddIfUniqueTo(AccountsReadFromFile); //Contact-Daten @@ -1098,24 +1012,25 @@ namespace Gremlin.GremlinData.DBClasses } //Eingelesenen Account in DB schreiben: - using (GremlinContext gremlinContext = new()) + using (GremlinContext db = new()) { - DateTime now = DateTime.Now; + DateTime now; + now = DateTime.Now; foreach (Account account in AccountsReadFromFile) { // AccountType aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen. - AccountType accountType = gremlinContext.AccountTypes + AccountType accountType = db.AccountTypes .Where(a => a.AccountTypeCode == account.AccountType.AccountTypeCode) .First(); account.AccountType = accountType; - SubMarket subMarket = gremlinContext.SubMarkets + SubMarket subMarket = db.SubMarkets .Where(a => a.SubMarketCode == account.SubMarket.SubMarketCode) .First(); account.SubMarket = subMarket; - account.AccountType = ResolveAccountType(gremlinContext, account.AccountType.AccountTypeCode); - account.SubMarket = ResolveSubmarket(gremlinContext, account.SubMarket.SubMarketCode); + account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode); + account.SubMarket = ResolveSubmarket(db, account.SubMarket.SubMarketCode); account.DataValidFrom = now; account.DataValidUntil = FarInTheFuture; @@ -1127,18 +1042,18 @@ namespace Gremlin.GremlinData.DBClasses account.DataStatus = Status.Active.ToString(); } - gremlinContext.Accounts.AddRange(AccountsReadFromFile); - _ = gremlinContext.SaveChanges(); + db.Accounts.AddRange(AccountsReadFromFile); + _ = db.SaveChanges(); } //Eingelesenen Contacts in DB schreiben: - using (GremlinContext gremlinContext = new()) + using (GremlinContext db = new()) { DateTime now = DateTime.Now; foreach (Contact contact in ContactsReadFromFile) { - contact.Account = ResolveAccountById(gremlinContext, contact.Notes); + contact.Account = ResolveAccountById(db, uint.Parse(contact.Notes)); contact.Notes = ""; contact.DataModificationByUser = "CSVImporter"; contact.DataVersionNumber = 1; @@ -1150,190 +1065,14 @@ namespace Gremlin.GremlinData.DBClasses contact.DataModificationDate = now; } - gremlinContext.Contacts.AddRange(ContactsReadFromFile); - _ = gremlinContext.SaveChanges(); + db.Contacts.AddRange(ContactsReadFromFile); + _ = db.SaveChanges(); } }; return true; } - - private static Dictionary ColumnMapping(string[] fields) - { - Dictionary result = new(); - for (int i = 0; i < fields.Length; i++) - { - switch (fields[i] - .ToLower(CultureInfo.CurrentCulture) - .Trim() - .Replace(" ", "") - .Replace("-", "") - .Replace("_", "") - .Replace(".", "") - .Replace(":", "") - ) - { - case "accountid" or "fcustomercd" or "sapaccountnumber": - result.Add("SAPAccountNumber", i); - break; - case "accountname" or "fcustomername": - result.Add("AccountName", i); - break; - case "postalcode" or "fpostcode" or "zip": - result.Add("ZIP", i); - break; - case "city" or "ftown": - result.Add("City", i); - break; - case "street" or "faddress": - result.Add("Street", i); - break; - case "title" or "ftitletext1": - result.Add("AcademicTitle", i); - break; - case "gender" or "fgender1": - result.Add("Gender", i); - break; - case "firstname" or "ffirstname": - result.Add("FirstName", i); - break; - case "lastname" or "flastname": - result.Add("LastName", i); - break; - case "optin" or "femailopt": - result.Add("OptInStatus", i); - break; - case "workemail" or "fworkemail1" or "accountemail": - result.Add("EMail", i); - break; - case "bouncedbackemail" or "fbouncedback": - result.Add("EmailBounced", i); - break; - case "department" or "fdepartment1": - result.Add("Department", i); - break; - case "roomnumber" or "froomnumber": - result.Add("Room", i); - break; - case "workphone" or "fworkphone1" or "phonenumber": - result.Add("PhoneNumber", i); - break; - case "faxnumber": - result.Add("FaxNumber", i); - break; - case "function" or "ffunction": - result.Add("Function", i); - break; - case "contactid" or "fcontactcd": - result.Add("SAPContactNumber", i); - break; - case "contactcreationdate" or "fcreationdate": - result.Add("SAPContactCreationDate", i); - break; - case "mobilephone" or "fmobilephone": - result.Add("MobileNumber", i); - break; - case "jobfunction" or "fjobfunction": - result.Add("MA_JobFunctions", i); - break; - case "productinterest" or "fproductinterest": - result.Add("MA_ProductInterests", i); - break; - case "applicationinterest" or "fapplicationinterest": - result.Add("MA_ApplicationInterests", i); - break; - case "joblevel" or "fjoblevel": - result.Add("MA_JobLevel", i); - break; - case "contactindustry" or "fcontactindustry": - result.Add("MA_ContactIndustry", i); - break; - case "competitiveibase" or "fcompetativeibase": - result.Add("MA_CompetitiveIBase", i); - break; - case "submarketcode" or "fsubmarketcd": - result.Add("SubMarketCode", i); - break; - case "customertypecode" or "fcustomertypecd" or "accounttype" or "accounttypecode": - result.Add("AccountTypeCode", i); - break; - case "phone" or "fphone": - result.Add("AccountPhoneNumber", i); - break; - case "accountcreatedon" or "fcreatedon": - result.Add("AccountCreatedInSAPOn", i); - break; - case "contactchangedon" or "fcontactchangedon": - result.Add("SAPContactModifiedDate", i); - break; - case "notelephonecalls" or "fnotelephonecalls": - result.Add("NoPhoneCalls", i); - break; - case "nohardcopymailing" or "fnohardcopymailing": - result.Add("NoHardcopyMailing", i); - break; - case "accounttypedescription": - result.Add("AccountTypeDescription", i); - break; - case "submarketdescription": - result.Add("SubMarketDescription", i); - break; - case "productlineabbreviation": - result.Add("ProductLineAbbreviation", i); - break; - case "productlinedescription": - result.Add("ProductLineDescription", i); - break; - default: - break; - } - } - return result; - } - - public static async Task UpdateProductsFromCsvAsync() - { - return await Task.Run(() => UpdateProductsFromCSV()); - } - - public static async Task ImportLSAGContactListToolDataAsync() - { - return await Task.Run(() => ImportLSAGContactListToolData()); - } - - public static async Task ImportAccountsFromCSVAsync() - { - return await Task.Run(() => ImportAccountsFromCSV()); - } - - public static async Task ImportContactsFromCSVAsync() - { - return await Task.Run(() => ImportContactsFromCSV()); - } - - public static async Task ImportProductsFromCSVAsync() - { - return await Task.Run(() => ImportProductsFromCSV()); - } - - public static async Task ImportCustomDescriptionsFromDocxAsync() - { - return await Task.Run(() => ImportCustomDescriptionsFromDocx()); - } - - public static async Task ImportCustomDescriptionsFromCsvAsync() - { - return await Task.Run(() => ImportCustomDescriptionsFromCsv()); - } - - public static void DisplayErrorDetails(Exception ex, string errorRaiser) - { - string errorMessage = $"Source: {ex.Source}{Environment.NewLine}" + - $"Message: {ex.Message}{Environment.NewLine}{Environment.NewLine}" + - $"Contact: {errorRaiser}"; - MessageBox.Show(errorMessage); - } - public static bool ImportProductsFromCSV(string filepath = "", string separator = "|", bool dataHasHeading = true) + public static bool ImportProductsFromCsv(string filepath = "", string separator = "|", bool dataHasHeading = true) ///Rückgabe 'false' bei Fehler oder User-Abbruch, ansonsten 'true'. /// ///Optionale Argumente: @@ -1341,160 +1080,296 @@ namespace Gremlin.GremlinData.DBClasses /// 2. string separator: Trennzeichen, Standardwert = ";" /// 3. bool dataHasheadings: true = CSV-Datei enthält Überschriften, false = CSV-Datei enthält keine Überschriften, Standardwert = true { - if (filepath == "") filepath = FileHelper.GetFilepathFromUser(); //Pfad abfragen über Dtei-Öffnen-Dialog. + if (filepath == "") filepath = FileIO.GetFilepathFromUser(); //Pfad abfragen über Dtei-Öffnen-Dialog. if (filepath == "") return false; //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch. List ProductsReadFromFile = new(ParseProductFile(filepath, separator, dataHasHeading)); return InsertProducts(ProductsReadFromFile); } - private static List ParseProductFile(string filepath, string separator, bool dataHasHeading) - ///Importiert Produkt-Daten aus CSV (erstellt aus PriceSurfer-CPL) - /// - Encoding: Latin1 - /// - Separator = ; - /// - fixe Spaltenzahl und -folge (Überschriften werden nicht untersucht/verwendet): - /// 1) Position = ID/PK - /// 2) Partnumber - /// 3) Option - /// 4) (Short) Description - /// 5) Current Month Price (EUR) - /// 6) ProductLineID - /// 7) Status - /// 8) (Long) Description - ///Kommentarzeichen: # (hardcoded, string array) + public static bool ImportCustomDescriptionsFromCsv(string filepath = "", string separator = "|", bool dataHasHeading = true) + ///Importiert Eigene Beschreibungen aus Word-Dokument + ///Abkürzung: CD = CustomDescriptions { - List results = new(100000); - //ENCODING CHANGED TO ISO-8859-1 AS EQUIVALENT (?) TO "LATIN1" (unavailable). TO BE TESTED! - using (TextFieldParser csvParser = new(filepath, FileIO.GetEncoding(filepath))) + if (filepath == "") filepath = FileIO.GetFilepathFromUser("Delimited Data File|*.csv; *.txt; *.tsv"); //Pfad abfragen über Dtei-Öffnen-Dialog: + if (filepath == "") return false; //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch: + + List CDsReadFromFile = new(2500); + using (TextFieldParser csvParser = new(filepath, Encoding.GetEncoding("UTF-8"))) { //Parser configuration: csvParser.Delimiters = new string[] { separator }; csvParser.CommentTokens = new string[] { "#" }; csvParser.HasFieldsEnclosedInQuotes = true; + // Skip the row with the column names: if (dataHasHeading) _ = csvParser.ReadLine(); - //decimal oldListPrice; - //bool listpriceHasChanged; while (!csvParser.EndOfData) { // Read current line fields, pointer moves to the next line. - Product ImportedProduct = new(); - ImportedProduct.ProductLine = new(); string[] fields = csvParser.ReadFields(); - - //Kontrolle, ob Trennzeichen in Daten vorhanden ist: - if (fields.Length > 27) //27 ist der Normalfall + CustomDescription ImportedCD = new() { - //Sonderfall "EnVisionTM G|2": - //suche fields[x], das auf " G" endet und auf das field[x+1] mit "2 " beginnt, kombiniere die beiden und schiebe alle darauffolgenden Felder in diesem Datensatz eins nach links. - for (int i = 0; i < fields.Length - 1; i++) //fields.Length - 1, um durch i+1 nicht aus dem Index zu laufen + ProductNumber = fields[0], + OptionNumber = fields[1] == "" ? null : fields[1], + Heading = fields[3], + DescriptionText = fields[4], + CoverletterText = fields[5], + Notes = fields[6], + DataModificationByUser = "Importer", + DataStatus = Status.Active.ToString(), + DataValidUntil = FarInTheFuture, + DataVersionComment = "Initial Importer by CD-ImporterFomCsv", + }; + ImportedCD.Product = new(); + ImportedCD.Supplier = new(); + ImportedCD.Supplier.AccountName = fields[2] is "" or "RB" ? "Agilent Technologies" : fields[2]; + ImportedCD.DataCreationDate = ImportedCD.DataValidFrom = ImportedCD.DataModificationDate = DateTime.Now; + ImportedCD.DataVersionNumber = 1; + + CDsReadFromFile.Add(ImportedCD); + } + + //Eingelesenen Custum Desciptions in DB schreiben: + //Check for 3PPs that not yet in the db. + //Step 1a: Add 3PP-Products from CD-List to list + //Step 1b: Add list to db. + //Step 2: Add CDs to products. + + using (GremlinContext db = new()) + { + //Step 1a + List thirdPartyProductsFromImportedCDs = new(); + foreach (CustomDescription CD in CDsReadFromFile) + { + if (CD.Supplier.AccountName != "Agilent Technologies") { - if (fields[i].EndsWith(" G")) + Product new3PPProduct = new(); + + new3PPProduct.CustomDescription = CD; + new3PPProduct.HasBreakPrices = false; + new3PPProduct.ListPrice = 0; + new3PPProduct.ProductNumber = CD.ProductNumber; + new3PPProduct.OptionNumber = CD.OptionNumber; + new3PPProduct.ProductStatus = Status.Active.ToString(); + new3PPProduct.SapLongDescription = null; + new3PPProduct.SapShortDescription = null; + new3PPProduct.Weight = 0; + new3PPProduct.IntroductionDate = DateTime.Now.Date; + new3PPProduct.DataVersionNumber = 1; + new3PPProduct.DataVersionComment = "Created at import from Custom Descriptions."; + new3PPProduct.DataStatus = Status.Active.ToString(); + new3PPProduct.DataModificationByUser = "Custom Desciption Importer"; + new3PPProduct.BreakRangeFrom = 0; + new3PPProduct.BreakRangeTo = 0; + new3PPProduct.ProductLine = ResolveProductLine(db, "3P"); + new3PPProduct.CustomDescription.Supplier = ResolveAccountByName(db, new3PPProduct.CustomDescription.Supplier.AccountName); + + thirdPartyProductsFromImportedCDs.Add(new3PPProduct); + } + }; + + //Step 1b + db.Products.AddRange(thirdPartyProductsFromImportedCDs); //Imports both the products and the associated custom descriptions! + _ = db.SaveChanges(); + + // Produkt aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen. + //Step 2 + List importedCDsWithEFReferences = new(100000); + List CDsWithoutEFReferences = new(1000); //nur zur Kontrolle, wird nicht verwendet. + List productsInDb = db.Products.ToList(); + Account agilent = db.Accounts.Where(a => a.AccountName == "Agilent Technologies").Single(); + foreach (CustomDescription CD in CDsReadFromFile) + { + //Skip Desciptions, if it has been already imported above (as part from 3PP) + if (thirdPartyProductsFromImportedCDs.Contains(CD.Product)) continue; + + //Establish EF Reference. If no PN/Opt found, then skip this custom description. + //CD.Product = GetProduct(db, CD.ProductNumber, CD.OptionNumber); //ResolveXY-functions return null, if no match is found in db. + CD.Product = productsInDb.Find(p => p.ProductNumber == CD.ProductNumber && p.OptionNumber == CD.OptionNumber); + if (CD.Product == null) + { + CDsWithoutEFReferences.Add(CD); + continue; + } + + //Establish EF Reference: If no Supplier-Account found, then skip this custom description (shouldn't happen), else set Supplier to Agilent (3PP-Supplier have been processed before and their products should not be part of this list). + if (CD.Supplier == null) + { + CDsWithoutEFReferences.Add(CD); + continue; + } + CD.Supplier = agilent; + + //prepare properties + CD.DataStatus = Status.Active.ToString(); + CD.DataVersionNumber = 1; + CD.DataModificationByUser = "CSVImporter"; + CD.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportCustomDescriptionsFromDocx)"; + + //add to final list of CDs, that will go into the db. + importedCDsWithEFReferences.Add(CD); + } + + db.CustomDescriptions.AddRange(importedCDsWithEFReferences); + db.SaveChanges(); + //Bestätigung senden + MessageBox.Show($"Es wurden {importedCDsWithEFReferences.Count} eigene Beschreibungen erfolgreich der Datenbank hinzugefügt.{Environment.NewLine}Es wurden {thirdPartyProductsFromImportedCDs.Count} 3PP-Produkte neu angelegt."); + } + return true; + } + } + + public static bool ImportCustomDescriptionsFromDocx(string filepath = "") + ///Importiert Eigene Beschreibungen aus Word-Dokument + ///Abkürzung: CD = CustomDescriptions + { + if (filepath == "") filepath = FileIO.GetFilepathFromUser("Word-Dokument|*.doc; *.docx; *.docm"); //Pfad abfragen über Dtei-Öffnen-Dialog: + if (filepath == "") return false; //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch: + + List importedCDs = new(2500); + WordprocessingDocument CDDoc = WordprocessingDocument.Open(filepath, false); //alternativ in using-Block packen, dann kann man nicht das Document.Close vergessen. + Table CDTable = CDDoc.MainDocumentPart.Document.Body.Elements().ElementAt(0); //alternativ: Elements
.First() + if (CDTable != null) + { + CDTable.Descendants().First().Remove(); //Überschriften löschen + foreach (TableRow aRow in CDTable.Descendants()) //einmal durch den XML-Baum wandern: WordprocessingDocument -> MainDocumentPart -> Document -> Body -> Table -> TableRow -> TableCell -> Paragraph -> Run -> Text + { + CustomDescription importedCD = new(); + List stringsRead = new(); + + foreach (TableCell aCell in aRow.Descendants()) + { + StringBuilder sb = new(); + StringBuilder note = new(); + foreach (Paragraph aParagraph in aCell.Descendants()) + { + foreach (Run aRun in aParagraph.Descendants()) { - if (fields[i + 1].StartsWith("2 ")) + foreach (Text aText in aRun.Descendants()) { - fields[i] = fields[i] + fields[i + 1]; - for (int j = i + 2; j < fields.Length; j++) + string temp = ""; + temp = aParagraph.Descendants() + .Where(r => r.RunProperties.Vanish != null) + .Aggregate("", (text, r) => text += r.InnerText); //nach hidden Text filtern, diesen in "note" speichern + _ = note.Append(temp); + + if (temp == "") //true, falls kein hidden Text => normalen Text verketten... { - fields[j - 1] = fields[j]; + _ = sb.Append(aText.InnerText); } } } } + stringsRead.Add(sb.ToString()); //...und zellenweise abspeichern + if (note.ToString() != "") + { + importedCD.Notes = note.ToString(); + } } - //NICHT ausgewertete Felder: - //fields[0] = Position [in CPL] - //fields[8] = Warranty - //fields[10] = PH Code - //fields[11] = PH Description - //fields[15] = Country of Manufacturing - //fields[16] = ECCL - //fields[17] = M41 - //fields[18] = First Supplier Code - //fields[19] = Harmonized Tarif Schedule - //fields[20] = Hazardous Good Flag - //fields[21] = Order instructions - //fields[23] = End of Production Date - //fields[24] = End of Support Date + importedCD.ProductNumber = stringsRead.ElementAt(0); + importedCD.OptionNumber = stringsRead.ElementAt(1) == "" ? null : stringsRead.ElementAt(1); + importedCD.Supplier.AccountName = stringsRead.ElementAt(2) == "" || stringsRead.ElementAt(2) == "RB" ? "Agilent Technologies" : stringsRead.ElementAt(2); + importedCD.Heading = stringsRead.ElementAt(3); + importedCD.DescriptionText = stringsRead.ElementAt(4); + importedCD.CoverletterText = stringsRead.ElementAt(5); - ImportedProduct.ProductNumber = fields[1]; + importedCDs.Add(importedCD); + if (importedCDs.Count >= 1200 && importedCDs.Count % 100 == 0) MessageBox.Show(importedCDs.Count.ToString()); //DEBUGGING - if (fields[2].Length == 4) //Optionsnummer mit führendem Apostroph - ImportedProduct.OptionNumber = fields[2].Substring(1); //schneidet erstes Zeichen/Apostroph weg - else if (fields[2].Length == 3) //3-stellige Optionsnummer übernehmen; keine Aktion bei leerem Feld (keine Optionsnummer) - ImportedProduct.OptionNumber = fields[2]; + } + } + //MessageBox.Show(importedCDs.Count.ToString()); //DEBUGGING + CDDoc.Close(); - ImportedProduct.SapShortDescription = fields[3]; - ImportedProduct.ListPrice = decimal.Parse(fields[4], new CultureInfo("de-de")); //parsing! compare with old value (either from CSV or from DB) -> price change? + //Eingelesenen Custum Desciptions in DB schreiben: + //Check for 3PPs that not yet in the db. + //Step 1a: Add 3PP-Products from CD-List to list + //Step 1b: Add list to db. + //Step 2: Add CDs to products. - //if (fields[5] != "") - // if (decimal.Parse(fields[5], new CultureInfo("de-de")) != ImportedProduct.ListPrice) - // listpriceHasChanged = true; + using (GremlinContext db = new()) + { + //Step 1a + List thirdPartyProductsFromImportedCDs = new(); + foreach (CustomDescription CD in importedCDs) + { + if (CD.Supplier.AccountName != "Agilent Technologies") + { + Product new3PPProduct = new(); + new3PPProduct.CustomDescription = CD; + new3PPProduct.HasBreakPrices = false; + new3PPProduct.ListPrice = 0; + new3PPProduct.ProductNumber = CD.ProductNumber; + new3PPProduct.OptionNumber = CD.OptionNumber; + new3PPProduct.ProductStatus = Status.Active.ToString(); + new3PPProduct.SapLongDescription = null; + new3PPProduct.SapShortDescription = null; + new3PPProduct.Weight = 0; + new3PPProduct.IntroductionDate = DateTime.Now.Date; + new3PPProduct.DataVersionNumber = 1; + new3PPProduct.DataVersionComment = "Created at import from Custom Descriptions."; + new3PPProduct.DataStatus = Status.Active.ToString(); + new3PPProduct.DataModificationByUser = "Custom Desciption Importer"; + new3PPProduct.BreakRangeFrom = 0; + new3PPProduct.BreakRangeTo = 0; + new3PPProduct.ProductLine = ResolveProductLine(db, "3P"); + new3PPProduct.CustomDescription.Supplier = ResolveAccountByName(db, new3PPProduct.CustomDescription.Supplier.AccountName); - ImportedProduct.BreakRangeFrom = fields[6] == "" ? 0 : Convert.ToInt32(fields[6]); - ImportedProduct.HasBreakPrices = ImportedProduct.BreakRangeFrom > 0; - ImportedProduct.BreakRangeTo = fields[7] is "" or "+" ? 0 : Convert.ToInt32(fields[7]); //erfasst sowohl Produkte ohne Break-Preise ("") als auch "+" bei Mengenangaben a la "100+" (= von 100 bis unendlich) + thirdPartyProductsFromImportedCDs.Add(new3PPProduct); + } + }; - ImportedProduct.ProductLine.ProductLineCode = fields[9]; + //Step 1b + db.Products.AddRange(thirdPartyProductsFromImportedCDs); //Imports both the products and the associated custom descriptions! + _ = db.SaveChanges(); - switch (fields[12]) + // Produkt aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen. + //Step 2 + List importedCDsWithEFReferences = new(); + List CDsWithoutEFReferences = new(); //nur zur Kontrolle, wird nicht verwendet. + foreach (CustomDescription CD in importedCDs) + { + //Skip Desciptions, if it has been already imported above (as part from 3PP) + if (thirdPartyProductsFromImportedCDs.Contains(CD.Product)) continue; + + //Establish EF Reference. If no PN/Opt found, then skip this custom description. + CD.Product = ResolveProduct(db, CD.ProductNumber, CD.OptionNumber); //ResolveXY-functions return null, if no match is found in db. + if (CD.Product == null) { - case "Active": - ImportedProduct.DataStatus = Status.Active.ToString(); - ImportedProduct.ProductStatus = Status.Active.ToString(); - break; - case "New Product" or "New": - ImportedProduct.DataStatus = Status.Active.ToString(); - ImportedProduct.ProductStatus = Status.New.ToString(); - break; - case "Price Changed": - ImportedProduct.DataStatus = Status.Active.ToString(); - ImportedProduct.ProductStatus = Status.PriceUpdated.ToString(); - break; - default: - ImportedProduct.DataStatus = Status.StatusUpdated.ToString(); - break; + CDsWithoutEFReferences.Add(CD); + continue; } - if (fields[14] != null) ImportedProduct.Weight = ParseWeight(fields[14]); - if (fields[13] == "G") ImportedProduct.Weight /= 1000; //Umrechnung g in kg - - if (fields[22] != "") ImportedProduct.IntroductionDate = DateTime.Parse(fields[22]); + //Establish EF Reference. If no Supplier-Account found, then skip this custom description. + CD.Supplier = ResolveAccountByName(db, CD.Supplier.AccountName); //ResolveXY-functions return null, if no match is found in db. + if (CD.Supplier == null) + { + CDsWithoutEFReferences.Add(CD); + continue; + } - ImportedProduct.SapLongDescription = fields[25]; + //prepare properties + CD.DataStatus = Status.Active.ToString(); + CD.DataVersionNumber = 1; + CD.DataModificationByUser = "CSVImporter"; + CD.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportCustomDescriptionsFromDocx)"; - results.Add(ImportedProduct); + //add to final list of CDs, that will go into the db. + importedCDsWithEFReferences.Add(CD); } - } - - return results; - } - - public static float ParseWeight(string input) - { - StringBuilder sb = new(); - if (input.StartsWith(".")) - { - input = sb.Append(0).Append(input).ToString(); - } - try - { - return float.Parse(input, new CultureInfo("en-us")); - } - catch (Exception ex) - { - ErrorHandler.ShowErrorInMessageBox(ex, "Unhandled Error in Function 'ParseWeight'"); - return 0; + db.CustomDescriptions.AddRange(importedCDsWithEFReferences); + _ = db.SaveChanges(); } - + return true; } - private static bool UpdateProductsFromCsv(string filepath = "", string separator = "|", bool dataHasHeading = true) + public static bool UpdateProductsFromCsv(string filepath = "", string separator = "|", bool dataHasHeading = true) { if (filepath == "") filepath = FileIO.GetFilepathFromUser(); //Pfad abfragen über Dtei-Öffnen-Dialog. string fileName = ExtractFileName(filepath); @@ -1502,28 +1377,37 @@ namespace Gremlin.GremlinData.DBClasses //Aktiven Produkte aus DB laden List ActiveProductsInDb = new(120000); - - using (GremlinContext gremlinContext = new()) + using (GremlinContext db = new()) { //Aktive Produkte aus der DB einlesen - List PLs = gremlinContext.ProductLines.ToList(); - Microsoft.EntityFrameworkCore.Query.IIncludableQueryable query = gremlinContext.Products - .Where(product => product.DataStatus == Status.Active.ToString()) - .Include(product => product.ProductLine); + List PLs = db.ProductLines.ToList(); + Microsoft.EntityFrameworkCore.Query.IIncludableQueryable query = db.Products + .Where(p => p.DataStatus == Status.Active.ToString()) + .Include(p => p.ProductLine); //var querystring = query.ToQueryString(); ActiveProductsInDb = query.ToList(); - //product.ProductLine = ResolveProductLine(db, product.ProductLine.ProductLineAbbreviation); - product.ProductLine = productLines.Find(x => x.ProductLineCode == product.ProductLine.ProductLineCode); + //Neue CPL einlesen... + List ProductsReadFromFile = new(ParseProductFile(filepath, separator, dataHasHeading)); - product.DataValidFrom = now; - product.DataValidUntil = FarInTheFuture; - product.DataVersionNumber = 1; - product.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportProductsFromCSV)"; - product.DataCreationDate = now; + //...Aufteilung in neue und nicht-neue Produkte... + List NewProducts = new(FilterNewProducts(ProductsReadFromFile)); + List NonNewProducts = new(ProductsReadFromFile.Except(NewProducts)); + + //...Letztere wiederum in obsolete Produkte... + ProductEqualityComparer ProEC = new(); + List ObsoleteProducts = new(ActiveProductsInDb.Except(NonNewProducts, ProEC)); + + DateTime now = DateTime.Now; + + //Obsolete Produkte prozessieren + foreach (Product product in ObsoleteProducts) + { + product.ProductStatus = Status.Obsolete.ToString(); + product.DataStatus = Status.Archived.ToString(); + product.DataValidUntil = now; product.DataModificationDate = now; - product.DataModificationByUser = "CSVImporter"; - product.DataStatus = Status.Active.ToString(); + product.DataModificationByUser = "CPL Updater"; } //den Rest prozessieren @@ -1533,16 +1417,17 @@ namespace Gremlin.GremlinData.DBClasses ////Counter für Debugging //int x = 0; + //Multithreading _ = Parallel.ForEach(NonNewProducts, parallelOptions, (product) => { ////Debugging-Counter //x++; Product ExistingProduct = ActiveProductsInDb - .FirstOrDefault(product => product.ProductNumber == product.ProductNumber - && product.OptionNumber == product.OptionNumber - && product.BreakRangeFrom == product.BreakRangeFrom - && product.BreakRangeTo == product.BreakRangeTo); + .FirstOrDefault(p => p.ProductNumber == product.ProductNumber + && p.OptionNumber == product.OptionNumber + && p.BreakRangeFrom == product.BreakRangeFrom + && p.BreakRangeTo == product.BreakRangeTo); //Checkpoint, falls ein neues Produkt versehentlich auf "Active" statt auf "New" gesetzt ist. if (ExistingProduct == null) @@ -1609,9 +1494,9 @@ namespace Gremlin.GremlinData.DBClasses Weight = product.Weight, }; - UpdatedProduct.ProductLine = product.ProductLine.ProductLineAbbreviation == ExistingProduct.ProductLine.ProductLineAbbreviation + UpdatedProduct.ProductLine = product.ProductLine.ProductLineCode == ExistingProduct.ProductLine.ProductLineCode ? product.ProductLine - : ResolveProductLine(gremlinContext, product.ProductLine.ProductLineAbbreviation); + : ResolveProductLine(db, product.ProductLine.ProductLineCode); NewProducts.Add(UpdatedProduct); } @@ -1619,361 +1504,437 @@ namespace Gremlin.GremlinData.DBClasses }); _ = InsertProducts(NewProducts); //enthält auch ResolvePL() - gremlinContext.Products.UpdateRange(ObsoleteProducts); - gremlinContext.Products.UpdateRange(UpdatedProducts); - int changes = gremlinContext.SaveChanges(); - Debug.WriteLine($"Es wurden {changes} Änderungen an der Datenbank durchgeführt."); + db.Products.UpdateRange(ObsoleteProducts); + db.Products.UpdateRange(UpdatedProducts); + int changes = db.SaveChanges(); + _ = MessageBox.Show($"Es wurden {changes} Änderungen an der Datenbank durchgeführt.\n Davon waren {UpdatedProducts.Count} UpdatedProducts, {ObsoleteProducts.Count} ObsoleteProducts und {NewProducts.Count} NewProducts"); } return true; } - private static string ExtractFileName(string filepath) - { - string[] fields = filepath.Split(@"\"); - string[] filename = fields[fields.Length - 1].Split("."); - if (withExtension) - { - return fields[fields.Length - 1]; - } - else return filename[0]; - } - - private static string Normalize(string input) - { - return input.ToLower() - .Trim() - .Replace(" ", "") - .Replace("-", "") - .Replace("_", "") - .Replace(".", ""); - } - - private static List FilterNewProducts(List products) + private static Dictionary ColumnMapping(string[] fields) { - DateTime now = DateTime.Now; - - List results = new(5000); - foreach (Product product in products) + Dictionary result = new(); + for (int i = 0; i < fields.Length; i++) { - if (product.ProductStatus == Status.New.ToString()) + switch (fields[i] + .ToLower(CultureInfo.CurrentCulture) + .Trim() + .Replace(" ", "") + .Replace("-", "") + .Replace("_", "") + .Replace(".", "") + .Replace(":", "") + ) { - product.DataCreationDate = now; - product.DataModificationDate = now; - product.DataValidFrom = now; - product.DataValidUntil = FarInTheFuture; - product.DataModificationByUser = "CPL Updater"; - product.DataVersionNumber = 1; - product.DataVersionComment = "Initial Import by CSV Importer/Updater"; - results.Add(product); + case "accountid" or "fcustomercd" or "sapaccountnumber": + result.Add("SAPAccountNumber", i); + break; + case "accountname" or "fcustomername": + result.Add("AccountName", i); + break; + case "postalcode" or "fpostcode" or "zip": + result.Add("ZIP", i); + break; + case "city" or "ftown": + result.Add("City", i); + break; + case "street" or "faddress": + result.Add("Street", i); + break; + case "title" or "ftitletext1": + result.Add("AcademicTitle", i); + break; + case "gender" or "fgender1": + result.Add("Gender", i); + break; + case "firstname" or "ffirstname": + result.Add("FirstName", i); + break; + case "lastname" or "flastname": + result.Add("LastName", i); + break; + case "optin" or "femailopt": + result.Add("OptInStatus", i); + break; + case "workemail" or "fworkemail1" or "accountemail": + result.Add("EMail", i); + break; + case "bouncedbackemail" or "fbouncedback": + result.Add("EmailBounced", i); + break; + case "department" or "fdepartment1": + result.Add("Department", i); + break; + case "roomnumber" or "froomnumber": + result.Add("Room", i); + break; + case "workphone" or "fworkphone1" or "phonenumber": + result.Add("PhoneNumber", i); + break; + case "faxnumber": + result.Add("FaxNumber", i); + break; + case "function" or "ffunction": + result.Add("Function", i); + break; + case "contactid" or "fcontactcd": + result.Add("SAPContactNumber", i); + break; + case "contactcreationdate" or "fcreationdate": + result.Add("SAPContactCreationDate", i); + break; + case "mobilephone" or "fmobilephone": + result.Add("MobileNumber", i); + break; + case "jobfunction" or "fjobfunction": + result.Add("MA_JobFunctions", i); + break; + case "productinterest" or "fproductinterest": + result.Add("MA_ProductInterests", i); + break; + case "applicationinterest" or "fapplicationinterest": + result.Add("MA_ApplicationInterests", i); + break; + case "joblevel" or "fjoblevel": + result.Add("MA_JobLevel", i); + break; + case "contactindustry" or "fcontactindustry": + result.Add("MA_ContactIndustry", i); + break; + case "competitiveibase" or "fcompetativeibase": + result.Add("MA_CompetitiveIBase", i); + break; + case "submarketcode" or "fsubmarketcd": + result.Add("SubMarketCode", i); + break; + case "customertypecode" or "fcustomertypecd" or "accounttype" or "accounttypecode": + result.Add("AccountTypeCode", i); + break; + case "phone" or "fphone": + result.Add("AccountPhoneNumber", i); + break; + case "accountcreatedon" or "fcreatedon": + result.Add("AccountCreatedInSAPOn", i); + break; + case "contactchangedon" or "fcontactchangedon": + result.Add("SAPContactModifiedDate", i); + break; + case "notelephonecalls" or "fnotelephonecalls": + result.Add("NoPhoneCalls", i); + break; + case "nohardcopymailing" or "fnohardcopymailing": + result.Add("NoHardcopyMailing", i); + break; + case "accounttypedescription": + result.Add("AccountTypeDescription", i); + break; + case "submarketdescription": + result.Add("SubMarketDescription", i); + break; + case "productlineabbreviation": + result.Add("ProductLineAbbreviation", i); + break; + case "productlinedescription": + result.Add("ProductLineDescription", i); + break; + default: + break; } } - return results; + return result; } - //private static List FilterProductsWithPriceChange(List products) - //{ - // List results = new(); - // foreach (var product in products) - // { - // if (product.ProductStatus == Status.PriceUpdated.ToString()) - // { - // results.Add(product); - // } - // } - // return results; - //} - - public static async Task ImportLSAGContactListToolDataAsync() - { - if (filepath == "") filepath = FileIO.GetFilepathFromUser("Delimited Data File|*.csv; *.txt; *.tsv"); //Pfad abfragen über Dtei-Öffnen-Dialog: - if (filepath == "") return false; //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch: - - List CDsReadFromFile = new(2500); - using (TextFieldParser csvParser = new(filepath, Encoding.GetEncoding("UTF-8"))) + private static List ParseProductFile(string filepath, string separator, bool dataHasHeading) + ///Importiert Produkt-Daten aus CSV (erstellt aus PriceSurfer-CPL) + /// - Encoding: Latin1 + /// - Separator = ; + /// - fixe Spaltenzahl und -folge (Überschriften werden nicht untersucht/verwendet): + /// 1) Position = ID/PK + /// 2) Partnumber + /// 3) Option + /// 4) (Short) Description + /// 5) Current Month Price (EUR) + /// 6) ProductLineID + /// 7) Status + /// 8) (Long) Description + ///Kommentarzeichen: # (hardcoded, string array) + { + List results = new(100000); + //ENCODING CHANGED TO ISO-8859-1 AS EQUIVALENT (?) TO "LATIN1" (unavailable). TO BE TESTED! + using (TextFieldParser csvParser = new(filepath, FileIO.GetEncoding(filepath))) { //Parser configuration: csvParser.Delimiters = new string[] { separator }; csvParser.CommentTokens = new string[] { "#" }; csvParser.HasFieldsEnclosedInQuotes = true; - // Skip the row with the column names: if (dataHasHeading) _ = csvParser.ReadLine(); + //decimal oldListPrice; + //bool listpriceHasChanged; while (!csvParser.EndOfData) { // Read current line fields, pointer moves to the next line. + Product ImportedProduct = new(); + ImportedProduct.ProductLine = new(); string[] fields = csvParser.ReadFields(); - CustomDescription ImportedCD = new() - { - ProductNumber = fields[0], - OptionNumber = fields[1] == "" ? null : fields[1], - Heading = fields[3], - DescriptionText = fields[4], - CoverletterText = fields[5], - Note = fields[6], - DataModificationByUser = "Importer", - DataStatus = Status.Active.ToString(), - DataValidUntil = FarInTheFuture, - DataVersionComment = "Initial Importer by CD-ImporterFomCsv", - }; - ImportedCD.Product = new(); - ImportedCD.Supplier = new(); - ImportedCD.Supplier.AccountName = fields[2] is "" or "RB" ? "Agilent Technologies" : fields[2]; - ImportedCD.DataCreationDate = ImportedCD.DataValidFrom = ImportedCD.DataModificationDate = DateTime.Now; - ImportedCD.DataVersionNumber = 1; - - CDsReadFromFile.Add(ImportedCD); - } - - //Eingelesenen Custum Desciptions in DB schreiben: - //Check for 3PPs that not yet in the db. - //Step 1a: Add 3PP-Products from CD-List to list - //Step 1b: Add list to db. - //Step 2: Add CDs to products. - using (GremlinContext gremlinContext = new()) - { - //Step 1a - List thirdPartyProductsFromImportedCDs = new(); - foreach (CustomDescription CD in CDsReadFromFile) + //Kontrolle, ob Trennzeichen in Daten vorhanden ist: + if (fields.Length > 27) //27 ist der Normalfall { - if (CD.Supplier.AccountName != "Agilent Technologies") + //Sonderfall "EnVisionTM G|2": + //suche fields[x], das auf " G" endet und auf das field[x+1] mit "2 " beginnt, kombiniere die beiden und schiebe alle darauffolgenden Felder in diesem Datensatz eins nach links. + for (int i = 0; i < fields.Length - 1; i++) //fields.Length - 1, um durch i+1 nicht aus dem Index zu laufen { - Product new3PPProduct = new(); + if (fields[i].EndsWith(" G")) + { + if (fields[i + 1].StartsWith("2 ")) + { + fields[i] = fields[i] + fields[i + 1]; + for (int j = i + 2; j < fields.Length; j++) + { + fields[j - 1] = fields[j]; + } + } + } + } + } - new3PPProduct.CustomDescription = CD; - new3PPProduct.HasBreakPrices = false; - new3PPProduct.ListPrice = 0; - new3PPProduct.ProductNumber = CD.ProductNumber; - new3PPProduct.OptionNumber = CD.OptionNumber; - new3PPProduct.ProductStatus = Status.Active.ToString(); - new3PPProduct.SapLongDescription = null; - new3PPProduct.SapShortDescription = null; - new3PPProduct.Weight = 0; - new3PPProduct.IntroductionDate = DateTime.Now.Date; - new3PPProduct.DataVersionNumber = 1; - new3PPProduct.DataVersionComment = "Created at import from Custom Descriptions."; - new3PPProduct.DataStatus = Status.Active.ToString(); - new3PPProduct.DataModificationByUser = "Custom Desciption Importer"; - new3PPProduct.BreakRangeFrom = 0; - new3PPProduct.BreakRangeTo = 0; - new3PPProduct.ProductLine = ResolveProductLine(gremlinContext, "3P"); - new3PPProduct.CustomDescription.Supplier = ResolveAccountByName(gremlinContext, new3PPProduct.CustomDescription.Supplier.AccountName); + //NICHT ausgewertete Felder: + //fields[0] = Position [in CPL] + //fields[8] = Warranty + //fields[10] = PH Code + //fields[11] = PH Description + //fields[15] = Country of Manufacturing + //fields[16] = ECCL + //fields[17] = M41 + //fields[18] = First Supplier Code + //fields[19] = Harmonized Tarif Schedule + //fields[20] = Hazardous Good Flag + //fields[21] = Order instructions + //fields[23] = End of Production Date + //fields[24] = End of Support Date - thirdPartyProductsFromImportedCDs.Add(new3PPProduct); - } - }; + ImportedProduct.ProductNumber = fields[1]; - //Step 1b - gremlinContext.Products.AddRange(thirdPartyProductsFromImportedCDs); //Imports both the products and the associated custom descriptions! - _ = gremlinContext.SaveChanges(); + if (fields[2].Length == 4) //Optionsnummer mit führendem Apostroph + ImportedProduct.OptionNumber = fields[2].Substring(1); //schneidet erstes Zeichen/Apostroph weg + else if (fields[2].Length == 3) //3-stellige Optionsnummer übernehmen; keine Aktion bei leerem Feld (keine Optionsnummer) + ImportedProduct.OptionNumber = fields[2]; - // Produkt aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen. - //Step 2 - List importedCDsWithEFReferences = new(100000); - List CDsWithoutEFReferences = new(1000); //nur zur Kontrolle, wird nicht verwendet. - List productsInDb = gremlinContext.Products.ToList(); - Account agilent = gremlinContext.Accounts.Where(a => a.AccountName == "Agilent Technologies").Single(); - foreach (CustomDescription CD in CDsReadFromFile) - { - //Skip Desciptions, if it has been already imported above (as part from 3PP) - if (thirdPartyProductsFromImportedCDs.Contains(CD.Product)) continue; + ImportedProduct.SapShortDescription = fields[3]; - //Establish EF Reference. If no PN/Opt found, then skip this custom description. - //CD.Product = GetProduct(db, CD.ProductNumber, CD.OptionNumber); //ResolveXY-functions return null, if no match is found in db. - CD.Product = productsInDb.Find(p => p.ProductNumber == CD.ProductNumber && p.OptionNumber == CD.OptionNumber); - if (CD.Product == null) - { - CDsWithoutEFReferences.Add(CD); - continue; - } + ImportedProduct.ListPrice = decimal.Parse(fields[4], new CultureInfo("de-de")); //parsing! compare with old value (either from CSV or from DB) -> price change? - //Establish EF Reference: If no Supplier-Account found, then skip this custom description (shouldn't happen), else set Supplier to Agilent (3PP-Supplier have been processed before and their products should not be part of this list). - if (CD.Supplier == null) - { - CDsWithoutEFReferences.Add(CD); - continue; - } - CD.Supplier = agilent; + //if (fields[5] != "") + // if (decimal.Parse(fields[5], new CultureInfo("de-de")) != ImportedProduct.ListPrice) + // listpriceHasChanged = true; - //prepare properties - CD.DataStatus = Status.Active.ToString(); - CD.DataVersionNumber = 1; - CD.DataModificationByUser = "CSVImporter"; - CD.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportCustomDescriptionsFromDocx)"; + ImportedProduct.BreakRangeFrom = fields[6] == "" ? 0 : Convert.ToInt32(fields[6]); + ImportedProduct.HasBreakPrices = ImportedProduct.BreakRangeFrom > 0; + ImportedProduct.BreakRangeTo = fields[7] is "" or "+" ? 0 : Convert.ToInt32(fields[7]); //erfasst sowohl Produkte ohne Break-Preise ("") als auch "+" bei Mengenangaben a la "100+" (= von 100 bis unendlich) - //add to final list of CDs, that will go into the db. - importedCDsWithEFReferences.Add(CD); + ImportedProduct.ProductLine.ProductLineCode = fields[9]; + + switch (fields[12]) + { + case "Active": + ImportedProduct.DataStatus = Status.Active.ToString(); + ImportedProduct.ProductStatus = Status.Active.ToString(); + break; + case "New Product" or "New": + ImportedProduct.DataStatus = Status.Active.ToString(); + ImportedProduct.ProductStatus = Status.New.ToString(); + break; + case "Price Changed": + ImportedProduct.DataStatus = Status.Active.ToString(); + ImportedProduct.ProductStatus = Status.PriceUpdated.ToString(); + break; + default: + ImportedProduct.DataStatus = Status.StatusUpdated.ToString(); + break; } - gremlinContext.CustomDescriptions.AddRange(importedCDsWithEFReferences); - _ = gremlinContext.SaveChanges(); - //Bestätigung senden - _ = MessageBox.Show($"Es wurden {importedCDsWithEFReferences.Count} eigene Beschreibungen erfolgreich der Datenbank hinzugefügt.{Environment.NewLine}Es wurden {thirdPartyProductsFromImportedCDs.Count} 3PP-Produkte neu angelegt."); + if (fields[14] != null) ImportedProduct.Weight = ParseWeight(fields[14]); + if (fields[13] == "G") ImportedProduct.Weight /= 1000; //Umrechnung g in kg + + if (fields[22] != "") ImportedProduct.IntroductionDate = DateTime.Parse(fields[22]); + + ImportedProduct.SapLongDescription = fields[25]; + + results.Add(ImportedProduct); } - return true; } + + return results; } - public static bool ImportCustomDescriptionsFromDocx(string filepath = "") - ///Importiert Eigene Beschreibungen aus Word-Dokument - ///Abkürzung: CD = CustomDescriptions + public static float ParseWeight(string input) { - if (filepath == "") filepath = FileIO.GetFilepathFromUser("Word-Dokument|*.doc; *.docx; *.docm"); //Pfad abfragen über Dtei-Öffnen-Dialog: - if (filepath == "") return false; //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch: + StringBuilder sb = new(); + if (input.StartsWith(".")) + { + input = sb.Append(0).Append(input).ToString(); + } - List importedCDs = new(2500); - WordprocessingDocument CDDoc = WordprocessingDocument.Open(filepath, false); //alternativ in using-Block packen, dann kann man nicht das Document.Close vergessen. - Table CDTable = CDDoc.MainDocumentPart.Document.Body.Elements
().ElementAt(0); //alternativ: Elements
.First() - if (CDTable != null) + try { - CDTable.Descendants().First().Remove(); //Überschriften löschen - foreach (TableRow aRow in CDTable.Descendants()) //einmal durch den XML-Baum wandern: WordprocessingDocument -> MainDocumentPart -> Document -> Body -> Table -> TableRow -> TableCell -> Paragraph -> Run -> Text + return float.Parse(input, new CultureInfo("en-us")); + } + catch (Exception ex) + { + ErrorHandler.ShowErrorInMessageBox(ex, "Unhandled Error in Function 'ParseWeight'"); + return 0; + } + + } + + public static bool InsertProducts(List products) + { + using (GremlinContext db = new()) + { + DateTime now; + now = DateTime.Now; + List productLines = db.ProductLines.ToList(); + foreach (Product product in products) { - CustomDescription importedCD = new(); - List stringsRead = new(); + //var query = db.ProductLines + // .Where(a => a.ProductLineAbbreviation == product.ProductLine.ProductLineAbbreviation) + // .First(); + //product.ProductLine = query; - foreach (TableCell aCell in aRow.Descendants()) - { - StringBuilder sb = new(); - StringBuilder note = new(); - foreach (Paragraph aParagraph in aCell.Descendants()) - { - foreach (Run aRun in aParagraph.Descendants()) - { - foreach (Text aText in aRun.Descendants()) - { - string temp = ""; - temp = aParagraph.Descendants() - .Where(r => r.RunProperties.Vanish != null) - .Aggregate("", (text, r) => text += r.InnerText); //nach hidden Text filtern, diesen in "note" speichern - _ = note.Append(temp); + //product.ProductLine = ResolveProductLine(db, product.ProductLine.ProductLineAbbreviation); + product.ProductLine = productLines.Find(x => x.ProductLineCode == product.ProductLine.ProductLineCode); - if (temp == "") //true, falls kein hidden Text => normalen Text verketten... - { - _ = sb.Append(aText.InnerText); - } - } - } - } - stringsRead.Add(sb.ToString()); //...und zellenweise abspeichern - if (note.ToString() != "") - { - importedCD.Note = note.ToString(); - } - } + product.DataValidFrom = now; + product.DataValidUntil = FarInTheFuture; + product.DataVersionNumber = 1; + product.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportProductsFromCSV)"; + product.DataCreationDate = now; + product.DataModificationDate = now; + product.DataModificationByUser = "CSVImporter"; + product.DataStatus = Status.Active.ToString(); + } - importedCD.ProductNumber = stringsRead.ElementAt(0); - importedCD.OptionNumber = stringsRead.ElementAt(1) == "" ? null : stringsRead.ElementAt(1); - importedCD.Supplier.AccountName = stringsRead.ElementAt(2) == "" || stringsRead.ElementAt(2) == "RB" ? "Agilent Technologies" : stringsRead.ElementAt(2); - importedCD.Heading = stringsRead.ElementAt(3); - importedCD.DescriptionText = stringsRead.ElementAt(4); - importedCD.CoverletterText = stringsRead.ElementAt(5); + db.Products.AddRange(products); + db.SaveChanges(); + //Bestätigung senden + MessageBox.Show($"Es wurden {products.Count} Produkte erfolgreich der Datenbank hinzugefügt."); + return true; + } + } - importedCDs.Add(importedCD); - if (importedCDs.Count >= 1200 && importedCDs.Count % 100 == 0) _ = MessageBox.Show(importedCDs.Count.ToString()); //DEBUGGING + public static void DisplayErrorDetails(Exception ex, string errorRaiser) + { + string errorMessage = $"Source: {ex.Source}{Environment.NewLine}" + + $"Message: {ex.Message}{Environment.NewLine}{Environment.NewLine}" + + $"Contact: {errorRaiser}"; + MessageBox.Show(errorMessage); + } - } + internal static string ExtractFileName(string filepath, bool withExtension = false) + { + string[] fields = filepath.Split(@"\"); + string[] filename = fields[fields.Length - 1].Split("."); + if (withExtension) + { + return fields[fields.Length - 1]; } - //MessageBox.Show(importedCDs.Count.ToString()); //DEBUGGING - CDDoc.Close(); + else return filename[0]; + } + private static string Normalize(string input) + { + return input.ToLower() + .Trim() + .Replace(" ", "") + .Replace("-", "") + .Replace("_", "") + .Replace(".", ""); + } - //Eingelesenen Custum Desciptions in DB schreiben: - //Check for 3PPs that not yet in the db. - //Step 1a: Add 3PP-Products from CD-List to list - //Step 1b: Add list to db. - //Step 2: Add CDs to products. + private static List FilterNewProducts(List products) + { + DateTime now = DateTime.Now; - using (GremlinContext gremlinContext = new()) + List results = new(5000); + foreach (Product product in products) { - //Step 1a - List thirdPartyProductsFromImportedCDs = new(); - foreach (CustomDescription CD in importedCDs) + if (product.ProductStatus == Status.New.ToString()) { - if (CD.Supplier.AccountName != "Agilent Technologies") - { - Product new3PPProduct = new(); - new3PPProduct.CustomDescription = CD; - new3PPProduct.HasBreakPrices = false; - new3PPProduct.ListPrice = 0; - new3PPProduct.ProductNumber = CD.ProductNumber; - new3PPProduct.OptionNumber = CD.OptionNumber; - new3PPProduct.ProductStatus = Status.Active.ToString(); - new3PPProduct.SapLongDescription = null; - new3PPProduct.SapShortDescription = null; - new3PPProduct.Weight = 0; - new3PPProduct.IntroductionDate = DateTime.Now.Date; - new3PPProduct.DataVersionNumber = 1; - new3PPProduct.DataVersionComment = "Created at import from Custom Descriptions."; - new3PPProduct.DataStatus = Status.Active.ToString(); - new3PPProduct.DataModificationByUser = "Custom Desciption Importer"; - new3PPProduct.BreakRangeFrom = 0; - new3PPProduct.BreakRangeTo = 0; - new3PPProduct.ProductLine = ResolveProductLine(gremlinContext, "3P"); - new3PPProduct.CustomDescription.Supplier = ResolveAccountByName(gremlinContext, new3PPProduct.CustomDescription.Supplier.AccountName); - - thirdPartyProductsFromImportedCDs.Add(new3PPProduct); - } - }; + product.DataCreationDate = now; + product.DataModificationDate = now; + product.DataValidFrom = now; + product.DataValidUntil = FarInTheFuture; + product.DataModificationByUser = "CPL Updater"; + product.DataVersionNumber = 1; + product.DataVersionComment = "Initial Import by CSV Importer/Updater"; + results.Add(product); + } + } + return results; + } - //Step 1b - gremlinContext.Products.AddRange(thirdPartyProductsFromImportedCDs); //Imports both the products and the associated custom descriptions! - _ = gremlinContext.SaveChanges(); + //private static List FilterProductsWithPriceChange(List products) + //{ + // List results = new(); + // foreach (var product in products) + // { + // if (product.ProductStatus == Status.PriceUpdated.ToString()) + // { + // results.Add(product); + // } + // } + // return results; + //} - // Produkt aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen. - //Step 2 - List importedCDsWithEFReferences = new(); - List CDsWithoutEFReferences = new(); //nur zur Kontrolle, wird nicht verwendet. - foreach (CustomDescription CD in importedCDs) - { - //Skip Desciptions, if it has been already imported above (as part from 3PP) - if (thirdPartyProductsFromImportedCDs.Contains(CD.Product)) continue; + public static async Task ImportLSAGContactListToolDataAsync() + { + return await Task.Run(() => ImportLSAGContactListToolData()); + } - //Establish EF Reference. If no PN/Opt found, then skip this custom description. - CD.Product = ResolveProduct(gremlinContext, CD.ProductNumber, CD.OptionNumber); //ResolveXY-functions return null, if no match is found in db. - if (CD.Product == null) - { - CDsWithoutEFReferences.Add(CD); - continue; - } + public static async Task ImportAccountsFromCsvAsync() + { + return await Task.Run(() => ImportAccountsFromCsv()); + } - //Establish EF Reference. If no Supplier-Account found, then skip this custom description. - CD.Supplier = ResolveAccountByName(gremlinContext, CD.Supplier.AccountName); //ResolveXY-functions return null, if no match is found in db. - if (CD.Supplier == null) - { - CDsWithoutEFReferences.Add(CD); - continue; - } + public static async Task ImportContactsFromCsvAsync() + { + return await Task.Run(() => ImportContactsFromCsv()); + } - //prepare properties - CD.DataStatus = Status.Active.ToString(); - CD.DataVersionNumber = 1; - CD.DataModificationByUser = "CSVImporter"; - CD.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportCustomDescriptionsFromDocx)"; + public static async Task ImportProductsFromCsvAsync() + { + return await Task.Run(() => ImportProductsFromCsv()); + } - //add to final list of CDs, that will go into the db. - importedCDsWithEFReferences.Add(CD); - } + public static async Task ImportCustomDescriptionsFromDocxAsync() + { + return await Task.Run(() => ImportCustomDescriptionsFromDocx()); + } - gremlinContext.CustomDescriptions.AddRange(importedCDsWithEFReferences); - _ = gremlinContext.SaveChanges(); - } - return true; + public static async Task ImportCustomDescriptionsFromCsvAsync() + { + return await Task.Run(() => ImportCustomDescriptionsFromCsv()); + } + public static async Task UpdateProductsFromCsvAsync() + { + return await Task.Run(() => UpdateProductsFromCsv()); } - public static Account ResolveAccountByName(GremlinContext context, string accountName) + public static Account ResolveAccountByName(GremlinContext context, string AccName) { try { - Account resolveAccount = context.Accounts - .Include(account => account.AccountType) - .Include(account => account.SubMarket) - .Where(account => account.AccountName == accountName) + Account query = context.Accounts + .Include(a => a.AccountType) + .Include(a => a.SubMarket) + .Where(a => a.AccountName == AccName) .First(); - return resolveAccount; + return query; } catch { @@ -1981,19 +1942,10 @@ namespace Gremlin.GremlinData.DBClasses } } - public static Account ResolveAccountById(GremlinContext context, string accountId) + public static Account ResolveAccountById(GremlinContext context, uint accountId) { - try - { - Account resolveAccount = context.Accounts - .Where(account => account.SAPAccountNumber == Convert.ToUInt32(accountId)) - .First(); - return resolveAccount; - } - catch - { - return null; - } + try { return context.Accounts.Where(account => account.SAPAccountNumber == accountId).First(); } + catch { return null; } } public static AccountType ResolveAccountType(GremlinContext context, string AccTypeCode) @@ -2001,7 +1953,7 @@ namespace Gremlin.GremlinData.DBClasses try { AccountType accountType = context.AccountTypes - .Where(acount => acount.AccountTypeCode == AccTypeCode) + .Where(account => account.AccountTypeCode == AccTypeCode) .First(); return accountType; } @@ -2015,10 +1967,10 @@ namespace Gremlin.GremlinData.DBClasses { try { - SubMarket accountSubMarket = context.SubMarkets - .Where(account => account.SubMarketCode == subMarketCode) + SubMarket query = context.SubMarkets + .Where(a => a.SubMarketCode == subMarketCode) .First(); - return accountSubMarket; + return query; } catch { @@ -2026,16 +1978,16 @@ namespace Gremlin.GremlinData.DBClasses } } - public static Product GetProduct(GremlinContext context, string productNumber, string optionNumber) + public static Product GetProduct(GremlinContext context, string pn, string option) { try { - Product getProduct = context.Products - .Include(product => product.ProductLine) - .Include(product => product.CustomDescription) - .Where(product => product.ProductNumber == productNumber && product.OptionNumber == optionNumber) + Product query = context.Products + .Include(p => p.ProductLine) + .Include(p => p.CustomDescription) + .Where(a => a.ProductNumber == pn && a.OptionNumber == option) .First(); - return getProduct; + return query; } catch { @@ -2043,14 +1995,14 @@ namespace Gremlin.GremlinData.DBClasses } } - public static Product ResolveProduct(GremlinContext context, string productNumber, string optionNumber) + public static Product ResolveProduct(GremlinContext context, string pn, string option) { try { - Product resolveProduct = context.Products - .Where(product => product.ProductNumber == productNumber && product.OptionNumber == optionNumber) + Product query = context.Products + .Where(a => a.ProductNumber == pn && a.OptionNumber == option) .First(); - return resolveProduct; + return query; } catch { @@ -2062,10 +2014,10 @@ namespace Gremlin.GremlinData.DBClasses { try { - ProductLine productLine = context.ProductLines - .Where(pL => pL.ProductLineAbbreviation == plCode) + ProductLine query = context.ProductLines + .Where(a => a.ProductLineCode == plCode) .First(); - return productLine; + return query; } catch { @@ -2079,112 +2031,6 @@ namespace Gremlin.GremlinData.DBClasses const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray()); } - - public static void InsertTestDataIntoDB() - { - using (GremlinContext db = new()) - { - DateTime now; - now = DateTime.Now; - - Random r = new(); - - Contact newContact = new() - { - AcademicTitle = RandomString(9), - FirstName = RandomString(5), - LastName = RandomString(20), - Gender = (byte)Gender.Male, - Department = RandomString(9), - DataStatus = Status.Active.ToString(), - DataModificationByUser = "Tester", - DataCreationDate = now, - DataModificationDate = now, - DataValidFrom = now, - DataValidUntil = FarInTheFuture, - DataVersionNumber = 1, - DataVersionComment = "Seeding data for testing.", - }; - - Account newAccount = new() - { - AccountName = RandomString(20), - Street = RandomString(9), - ZIP = Convert.ToUInt32(r.Next(11111, 99999)), - City = RandomString(9), - SAPAccountNumber = Convert.ToUInt32(r.Next(1111111, 99999999)), - EMail = RandomString(9) + "@" + RandomString(9) + "." + RandomString(3), - PhoneNumber = "+49 " + r.Next().ToString(), - DataStatus = Status.Active.ToString(), - DataModificationByUser = "Tester", - DataCreationDate = now, - DataModificationDate = now, - DataValidFrom = now, - DataValidUntil = FarInTheFuture, - DataVersionNumber = 1, - DataVersionComment = "Seeding data for testing.", - }; - - AccountType accountType = db.AccountTypes - .Where(a => a.AccountTypeCode == "FPC") - .FirstOrDefault(); - newAccount.AccountType = accountType; - - SubMarket subMarket = db.SubMarkets - .Where(a => a.SubMarketCode == "CEN") - .First(); - newAccount.SubMarket = subMarket; - newContact.Account = newAccount; - - try - { - _ = db.Add(newAccount); - _ = db.SaveChanges(); - _ = db.Add(newContact); - _ = db.SaveChanges(); - _ = MessageBox.Show($"Account {newAccount.AccountName} mit Contact {newContact.LastName} wurde angelegt."); - } - catch (Exception ex) - { - ErrorHandler.ShowErrorInMessageBox(ex); - } - } - } - - public static bool InsertProducts(List products) - { - using (GremlinContext db = new()) - { - DateTime now; - now = DateTime.Now; - List productLines = db.ProductLines.ToList(); - foreach (Product product in products) - { - //var query = db.ProductLines - // .Where(a => a.ProductLineAbbreviation == product.ProductLine.ProductLineAbbreviation) - // .First(); - //product.ProductLine = query; - - //product.ProductLine = ResolveProductLine(db, product.ProductLine.ProductLineAbbreviation); - product.ProductLine = productLines.Find(x => x.ProductLineAbbreviation == product.ProductLine.ProductLineAbbreviation); - - product.DataValidFrom = now; - product.DataValidUntil = FarInTheFuture; - product.DataVersionNumber = 1; - product.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportProductsFromCSV)"; - product.DataCreationDate = now; - product.DataModificationDate = now; - product.DataModificationByUser = "CSVImporter"; - product.DataStatus = Status.Active.ToString(); - product.DataModificationByUser = "CSVImporter"; - } - - db.Products.AddRange(products); - _ = db.SaveChanges(); - //Bestätigung senden - return true; - } - } } public class ProductEqualityComparer : IEqualityComparer