Gremlin/Gremlin/GremlinData/DBClasses/DbHelper.cs

1935 lines
92 KiB
C#

using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualBasic.FileIO;
using MySqlConnector;
using System;
using System.Collections.Generic;
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;
namespace Gremlin.GremlinData.DBClasses
{
internal static class DbHelper
{
private static readonly DateTime FarInTheFuture = DateTime.Parse("2050-12-31t00:00:00.000000z", CultureInfo.CurrentCulture);
private static readonly ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = Environment.ProcessorCount * 4 };
private static readonly Random random = new();
public static bool CheckDatabaseConnection(string connectionString)
{
bool isConnected = false;
MySqlConnection connection = null;
try
{
connection = new MySqlConnection(connectionString);
connection.Open();
isConnected = true;
}
catch (ArgumentException a_ex)
{
_ = MessageBox.Show(a_ex.Message, "ArgumentException", MessageBoxButton.OK, MessageBoxImage.Error);
}
catch (MySqlException ex)
{
_ = MessageBox.Show($"Message: {ex.Message}\nSource: {ex.Source}\nNumber: {ex.Number}", "Connection Failed!", MessageBoxButton.OK, MessageBoxImage.Error);
isConnected = false;
switch (ex.Number)
{
case 1042: // Unable to connect to any of the specified MySQL hosts (Check Server,Port)
break;
case 0: // Access denied (Check DB name,username,password)
break;
default:
break;
}
}
finally
{
if (connection.State == ConnectionState.Open) { connection.Close(); }
}
return isConnected;
}
public static void DbBuilder()
///Deletes existing database and builds it from scratch (code first).
{
using (GremlinContext db = new())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
}
}
public static void DbInitializer()
/// Initialzes the Db with enum data: product lines, account types, submarkets
{
//PRODUCT LINES
string[] ProductLineAbbrviations = { "XF", "LM", "XA", "RB", "GE",
"9Z", "9P", "9M", "9K", "9H",
"9F", "9E", "9C", "ZZ", "ZF",
"V1", "MB", "MA", "LI", "JW",
"CA", "BZ", "BC", "AZ", "AJ",
"AB", "AA", "8P", "89", "74",
"6P", "6G", "58", "29", "AM",
"RP", "CD", "PT", "XB", "RM",
"GS", "AT", "SR", "FS", "BD",
"UF", "3P" };
string[] ProductLineDescriptions = { "Cell Analysis", "Lab Management", "Flow Cell", "Remarketed Instruments", " Genomics",
"Drugs of Abuse", "Sample Prep/Resins", "Research Products", "Dissolution & Other", "Vacuum Products",
"AA/OES", "Drugs of Abuse", "Micro GC", "Common PL & Default", "Expedite Freight",
"Automation", "Particle Analysis", "Molecular Spectroscopy", "Software and Informatics", "GC Columns",
"Mid Range GC", "GC-MS", "LC Columns", "Gas Phase Analysis", "ICPMS",
"Bio-consumables", "Instrument Supplies", "Analytical Parts Sup", "LC-MS", "Support Services",
"Software and Professinal Services", "Other Local Products", "Competitive Supplies", "LC", "Amplicon Sequencing",
"Reagent Partnership", "Companion Diagnostic", "Pathology", "Biotek", "Raman Spectroscopy",
"Genomics Systems", "Parallel CE Instrument", "Bio Reagents", "Flexible Spending", "Nucleic Acid",
"Microfluidics", "3rd Party Product" };
int length = ProductLineAbbrviations.Length;
for (int i = 0; i < length; i++)
{
ProductLine PlToBeAdded = new()
{
ProductLineCode = ProductLineAbbrviations[i],
ProductLineDescription = ProductLineDescriptions[i],
DataStatus = Status.Active.ToString(),
DataModificationByUser = "DbInitializer"
};
using (GremlinContext db = new())
{
_ = db.ProductLines.Add(PlToBeAdded);
db.SaveChanges();
}
}
//ACCOUNT TYPES
string[] AccountTypes = { "FPC", "FPR", "FPT", "FPV", "FPN",
"FPP", "FPG", "FPX", "FPS", "FPA",
"FPO", "FPW", "NPU", "NPR", "NPG",
"NPH", "SUP" };
string[] AccountTypeDescription = { "Commercial", "Contract Manufacturing CRM & Contract Research CRO", "Contract Testing", "Instrument IDR Distributor", "Instrument National Distributor",
"Foreign Trade Org", "Genomics/CSD Distributor", "Regulated Distributor", "Transactional Reseller", "Value Added Reseller",
"OEM", "Occasional resellers", "University", "Research Institute", "Government",
"Hospital", "Supplier" };
length = AccountTypes.Length;
for (int i = 0; i < length; i++)
{
AccountType ATtoBeAdded = new()
{
AccountTypeCode = AccountTypes[i],
AccountTypeDescription = AccountTypeDescription[i],
DataStatus = Status.Active.ToString(),
};
MetaDataSetter.ForImport(ATtoBeAdded, "DbInitializer", "DbInitializer");
using (GremlinContext db = new())
{
db.AccountTypes.Add(ATtoBeAdded);
db.SaveChanges();
}
}
//SubMarkets
string[] SubMarketCodes = { "CCH", "CEN", "CFD", "CFR", "CMS",
"COT", "LPH", "LBT", "LGN", "LRS",
"DCT", "LOT", "VEN", "VAR", "SUP" };
string[] SubMarketDescriptions = { "Chemical & Energy", "Environmental", "Food", "Forensics", "Material Science",
"CA Other", "Pharmaceutical", "BioPharma", "Generics", "LS Research",
"Clinical Testing", "LS Other", "Analytical Instrument Vendor", "VAR-Partner", "Supplier" };
length = SubMarketCodes.Length;
for (int i = 0; i < length; i++)
{
SubMarket SMtoBeAdded = new()
{
SubMarketCode = SubMarketCodes[i],
SubMarketDescription = SubMarketDescriptions[i],
DataStatus = Status.Active.ToString(),
};
MetaDataSetter.ForImport(SMtoBeAdded, "DbInitializer", "DbInitializer");
using (GremlinContext db = new())
{
db.SubMarkets.Add(SMtoBeAdded);
db.SaveChanges();
}
}
List<Account> seedAccounts = new();
Account agilent = new()
{
AccountName = "Agilent Technologies",
AccountId = 1,
Street = "Hewlett-Packard-Str. 8",
ZIP = 76337,
City = "Waldbronn",
SAPAccountNumber = 00000001,
EMail = "CustomerCare_Germany@agilent.com",
PhoneNumber = "+49 800 6031000",
DataStatus = Status.Active.ToString(),
AccountType = new AccountType()
{
AccountTypeCode = "SUP"
},
SubMarket = new SubMarket()
{
SubMarketCode = "VEN"
}
};
seedAccounts.Add(agilent);
Account bios = new()
{
AccountName = "Bios Analytical Ltd.",
AccountId = 2,
Street = "7 South Fens Enterprise Park//Fenton Way//PE16 6WA Chatteris//Cambridgeshire//United Kingdom",
ZIP = 0,
City = "Chatteris, UNITED KINGDOM",
SAPAccountNumber = 00000002,
EMail = "",
PhoneNumber = "+44 1354 694377",
DataStatus = Status.Active.ToString(),
AccountType = new AccountType()
{
AccountTypeCode = "SUP"
},
SubMarket = new SubMarket()
{
SubMarketCode = "VAR"
}
};
seedAccounts.Add(bios);
Account huber = new()
{
AccountName = "Huber Kältemaschinenbau AG",
AccountId = 3,
Street = "Werner-von-Siemens-Straße 1",
ZIP = 77656,
City = "Offenburg",
SAPAccountNumber = 00000003,
EMail = "sales@huber-online.com",
PhoneNumber = "0781 96030",
DataStatus = Status.Active.ToString(),
AccountType = new AccountType()
{
AccountTypeCode = "SUP"
},
SubMarket = new SubMarket()
{
SubMarketCode = "SUP"
}
};
seedAccounts.Add(huber);
Account vanderheijden = new()
{
AccountName = "Van der Heijden Labortechnik GmbH",
AccountId = 4,
Street = "Tramsmeiers Berg 2",
ZIP = 32694,
City = "Dörentrup",
SAPAccountNumber = 00000004,
EMail = "info@vdh-online.com",
PhoneNumber = "05265 945520",
DataStatus = Status.Active.ToString(),
AccountType = new AccountType()
{
AccountTypeCode = "SUP"
},
SubMarket = new SubMarket()
{
SubMarketCode = "SUP"
}
};
seedAccounts.Add(vanderheijden);
Account ole = new()
{
AccountName = "OLE DICH Instrumentmakers ApS",
AccountId = 5,
Street = "18, Taarnfalkevej",
ZIP = 2650,
City = "Hvidovre, DENMARK",
SAPAccountNumber = 00000005,
EMail = "",
PhoneNumber = "+45 36 78 41 85",
DataStatus = Status.Active.ToString(),
AccountType = new AccountType()
{
AccountTypeCode = "SUP"
},
SubMarket = new SubMarket()
{
SubMarketCode = "SUP"
}
};
seedAccounts.Add(ole);
Account sprep = new()
{
AccountName = "S-prep GmbH",
AccountId = 6,
Street = "Im Amann 7",
ZIP = 88662,
City = "Überlingen",
SAPAccountNumber = 00000006,
EMail = "info@s-prep.com",
PhoneNumber = "07551 932696",
FaxNumber = "07551-932699",
DataStatus = Status.Active.ToString(),
AccountType = new AccountType()
{
AccountTypeCode = "SUP"
},
SubMarket = new SubMarket()
{
SubMarketCode = "SUP"
}
};
seedAccounts.Add(sprep);
Account hellma = new()
{
AccountName = "Hellma GmbH & Co. KG",
AccountId = 7,
Street = "Klosterrunsstraße 5",
ZIP = 79379,
City = "Müllheim",
SAPAccountNumber = 00000007,
EMail = "info.de@hellma.com",
PhoneNumber = "07631 1820",
DataStatus = Status.Active.ToString(),
AccountType = new AccountType()
{
AccountTypeCode = "SUP"
},
SubMarket = new SubMarket()
{
SubMarketCode = "SUP"
}
};
seedAccounts.Add(hellma);
Account Glasside = new()
{
AccountName = "Glasside Technologies",
AccountId = 8,
Street = "Albany House, 82 Avenue Road//PE19 1LH St Neots, Cambridgeshire//United Kingdom",
ZIP = 0,
City = "St Neots, Cambridgesgire, UNITED KINGDOM",
SAPAccountNumber = 00000008,
EMail = "",
PhoneNumber = "-/-",
DataStatus = Status.Active.ToString(),
AccountType = new AccountType()
{
AccountTypeCode = "SUP"
},
SubMarket = new SubMarket()
{
SubMarketCode = "SUP"
}
};
seedAccounts.Add(Glasside);
Account pike = new()
{
AccountName = "PIKE Technologies Inc.",
AccountId = 9,
Street = "6125 Cottonwood Drive//Madison, WI 53719//USA",
ZIP = 53719,
City = "Madison (WI), USA",
SAPAccountNumber = 00000009,
EMail = "info@piketech.com",
PhoneNumber = "+1 608 274 2721",
FaxNumber = "+1 608 274 0103",
DataStatus = Status.Active.ToString(),
AccountType = new AccountType()
{
AccountTypeCode = "SUP"
},
SubMarket = new SubMarket()
{
SubMarketCode = "SUP"
}
};
seedAccounts.Add(pike);
using (GremlinContext db = new())
{
foreach (Account account in seedAccounts)
{
// AccountType und SubMarket aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode);
account.SubMarket = ResolveSubmarket(db, account.SubMarket.SubMarketCode);
MetaDataSetter.ForImport(account, "DbInitializer", "Function DbHelper.DbInitializer");
}
db.Accounts.AddRange(seedAccounts);
_ = db.SaveChanges();
}
//RegisteredUser
List<RegisteredUser> seedRegisteredUser = new();
RegisteredUser userSascha = new();
userSascha.UserName = "woitsche";
userSascha.PasswordHash = "527646";
seedRegisteredUser.Add(userSascha);
RegisteredUser userSebastian = new();
userSebastian.UserName = "sewelsch";
userSebastian.PasswordHash = "123456";
seedRegisteredUser.Add(userSebastian);
using (GremlinContext db = new())
{
foreach (RegisteredUser userSetting in seedRegisteredUser)
{
userSetting.DataCreationDate = DateTime.Now;
userSetting.DataModificationByUser = "DB Initializer";
userSetting.DataModificationDate = DateTime.Now;
}
db.RegisteredUser.AddRange(seedRegisteredUser);
_ = db.SaveChanges();
}
//RUSetting
List<RUSettings> seedRUSettings = new();
using (RUSettings saschaName = new())
{
saschaName.RegisteredUserID = 1;
saschaName.SettingKey = "userName";
saschaName.SettingValue = "Dr. Sascha Woitschetzki";
seedRUSettings.Add(saschaName);
}
using (RUSettings saschaPhone = new())
{
saschaPhone.RegisteredUserID = 1;
saschaPhone.SettingKey = "userPhone";
saschaPhone.SettingValue = "+49 208 74129134";
seedRUSettings.Add(saschaPhone);
}
using (RUSettings saschaMobile = new())
{
saschaMobile.RegisteredUserID = 1;
saschaMobile.SettingKey = "userMobile";
saschaMobile.SettingValue = "+49 176 22285334";
seedRUSettings.Add(saschaMobile);
}
using (RUSettings saschaMail = new())
{
saschaMail.RegisteredUserID = 1;
saschaMail.SettingKey = "userMail";
saschaMail.SettingValue = "sascha.woitschetzki@non.agilent.com";
seedRUSettings.Add(saschaMail);
}
using (RUSettings saschaTerritoryID = new())
{
saschaTerritoryID.RegisteredUserID = 1;
saschaTerritoryID.SettingKey = "userTerritoryID";
saschaTerritoryID.SettingValue = "83PE89";
seedRUSettings.Add(saschaTerritoryID);
}
using (RUSettings saschaTexRand = new())
{
saschaTexRand.RegisteredUserID = 1;
saschaTexRand.SettingKey = "texRand";
saschaTexRand.SettingValue = "2";
seedRUSettings.Add(saschaTexRand);
}
using (GremlinContext db = new())
{
foreach (RUSettings userSetting in seedRUSettings)
{
userSetting.DataCreationDate = DateTime.Now;
userSetting.DataModificationByUser = "DB Initializer";
userSetting.DataModificationDate = DateTime.Now;
}
db.RUSettings.AddRange(seedRUSettings);
_ = db.SaveChanges();
}
}
public static void InsertTestDataIntoDB()
{
using (GremlinContext db = new())
{
DateTime 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),
};
MetaDataSetter.ForImport(newContact, "Tester", "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(),
};
MetaDataSetter.ForImport(newAccount, "Tester", "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 ImportContactsFromCsv(string filepath = "", string separator = ";", bool dataHasHeading = true)
{
//Pfad abfragen über Dtei-Öffnen-Dialog:
if (filepath == "") filepath = FileIO.GetFilepathFromUser();
//Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch:
if (filepath == "") return false;
List<Contact> ContactsReadFromFile = new(8000);
// ENCODING CHANGED TO UNICODE. 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();
while (!csvParser.EndOfData)
{
// Read current line fields, pointer moves to the next line.
Contact ImportedContact = new();
string[] fields = csvParser.ReadFields();
//No conversion
ImportedContact.AcademicTitle = fields[1];
ImportedContact.FirstName = fields[3];
ImportedContact.LastName = fields[4];
ImportedContact.EMail = fields[6];
ImportedContact.Department = fields[7];
ImportedContact.Room = fields[8];
ImportedContact.PhoneNumber = fields[9];
ImportedContact.Function = fields[10];
ImportedContact.MobileNumber = fields[13];
//Convert Gender
ImportedContact.Gender = fields[2] == "M" ? (byte)Gender.Male : fields[2] == "F" ? (byte)Gender.Female : (byte)Gender.Unknown;
//Convert OptIn Status
ImportedContact.OptInStatus = fields[5] switch
{
"Opt In" => true,
"Opt Out" => false,
_ => null,
};
//Convert "SAP Contact Number"
try
{
ImportedContact.SAPContactNumber = Convert.ToInt32(fields[11], CultureInfo.CurrentCulture);
}
catch (FormatException ex)
{
//errorhandling here: "Unable to parse input: " + field[0] + " as uint"
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[11];
if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
catch (OverflowException ex)
{
//errorhandling here: "Number cannot fit in an Uint."
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[11];
if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
//Convert "Account Created on"
int year = Convert.ToInt32(fields[12].Substring(0, 4), CultureInfo.CurrentCulture);
int month = Convert.ToInt32(fields[12].Substring(4, 2), CultureInfo.CurrentCulture);
int day = Convert.ToInt32(fields[12].Substring(6, 2), CultureInfo.CurrentCulture);
ImportedContact.SAPContactCreationDate = new DateTime(year, month, day);
//Convert "No Phone Calls"
if (fields[26] == "1") ImportedContact.NoPhoneCalls = true;
//Convert "No Hardcopy Mailing"
if (fields[27] == "1") ImportedContact.NoHardcopyMailing = true;
//SAPAccountID in Contact.Notes speichern, um den entsprechenden Account aus DB heraussuchen zu können:
ImportedContact.Notes = fields[0];
ContactsReadFromFile.Add(ImportedContact);
}
//Eingelesenen Account in DB schreiben:
using (GremlinContext db = new())
{
DateTime now = DateTime.Now;
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, uint.Parse(contact.Notes));
contact.Notes = "";
MetaDataSetter.ForImport(contact, "CSVImporter", "Initial import by CSV Importer (Function DbHelper.ImportContactsFromCSV)");
}
db.Contacts.AddRange(ContactsReadFromFile);
db.SaveChanges();
}
}
//Bestätigung senden
MessageBox.Show($"Es wurden {ContactsReadFromFile.Count} Contacts erfolgreich der Datenbank hinzugefügt.");
return true;
}
public static bool ImportAccountsFromCsv(string filepath = "", string separator = ";", bool dataHasHeading = true)
///Importiert Account Daten aus CSV (erstellt aus LSAG_Contact_List_Tool.xlsx)
///
/// - Encoding: Latin1
/// - fixe Spaltenzahl und -folge (Überschriften werden nicht untersucht/verwendet):
/// 1) SAPAccountNumber
/// 2) AccountName
/// 3) ZIP
/// 4) City
/// 5) Street
/// 6) AccountSubMarketCode
/// 7) AccountTypeCode
/// 8) PhoneNumber
/// 9) AccountsCreatedInSAP
///Kommentarzeichen: # (hardcoded, string array)
///Rückgabe 'false' bei Fehler oder User-Abbruch, ansonsten 'true'.
///
///Optionale Argumente:
/// 1. string filepath: Dateipfad zur einzulesenden Datei. Standardwert = "", öffnet OpenFile-Dialog.
/// 2. string separator: Trennzeichen, Standardwert = ";"
/// 3. bool dataHasheadings: true = CSV-Datei enthält Überschriften, false = CSV-Datei enthält keine Überschriften, Standardwert = true
{
//Pfad abfragen über Dtei-Öffnen-Dialog:
if (filepath == "") filepath = FileIO.GetFilepathFromUser();
//Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch:
if (filepath == "") return false;
List<Account> AccountsReadFromFile = new(1000);
//ENCODING CHANGED TO ISO-8859-1 AS EQUIVALENT (?) TO "LATIN1" (unavailable). TO BE TESTED!
//ich würde mir ja UTF8 als universelles Encoding wünschen
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();
while (!csvParser.EndOfData)
{
// Read current line fields, pointer moves to the next line.
Account ImportedAccount = new();
{
ImportedAccount.SubMarket = new();
ImportedAccount.AccountType = new();
ImportedAccount.Contacts = new List<Contact>();
}
string[] fields = csvParser.ReadFields();
//Konvertierung erforderlich:
try
{
ImportedAccount.SAPAccountNumber = Convert.ToUInt32(fields[0]);
}
catch (FormatException ex)
{
//errorhandling here: "Unable to parse input: " + field[0] + " as uint"
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[0];
if (errorRaiser == "") errorRaiser = "No Account Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
catch (OverflowException ex)
{
//errorhandling here: "Number cannot fit in an Uint."
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[0];
if (errorRaiser == "") errorRaiser = "No Account Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
try
{
ImportedAccount.ZIP = Convert.ToUInt32(fields[2]);
}
catch (FormatException ex)
{
//errorhandling here: "Unable to parse input: " + field[0] + " as uint"
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[0];
if (errorRaiser == "") errorRaiser = "No ZIP in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
catch (OverflowException ex)
{
//errorhandling here: "Number cannot fit in an Uint."
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[0];
if (errorRaiser == "") errorRaiser = "No ZIP in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
//Konvertierung für AccountCreatedinSAPOn Property:
//Test auf Inhalt
if (fields[8].Length != 0)
{
int year = Convert.ToInt32(fields[8].Substring(0, 4));
int month = Convert.ToInt32(fields[8].Substring(4, 2));
int day = Convert.ToInt32(fields[8].Substring(6, 2));
ImportedAccount.AccountCreatedInSAPOn = new DateTime(year, month, day);
}
//keine Konvertierung nötig:
ImportedAccount.AccountName = fields[1];
ImportedAccount.City = fields[3];
ImportedAccount.Street = fields[4];
ImportedAccount.SubMarket.SubMarketCode = fields[5];
ImportedAccount.SubMarket.DataStatus = Status.Active.ToString();
ImportedAccount.AccountType.AccountTypeCode = fields[6];
ImportedAccount.AccountType.DataStatus = Status.Active.ToString();
ImportedAccount.PhoneNumber = fields[7];
ImportedAccount.DataStatus = Status.Active.ToString();
AccountsReadFromFile.Add(ImportedAccount);
}
//Eingelesenen Account in DB schreiben:
using (GremlinContext db = new())
{
DateTime 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.
if (account.AccountType.AccountTypeCode != "")
{
AccountType accountType = db.AccountTypes
.Where(a => a.AccountTypeCode == account.AccountType.AccountTypeCode)
.First();
account.AccountType = accountType;
account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode);
}
else
{
//Default
account.AccountType = ResolveAccountType(db, "FPC");
}
if (account.SubMarket.SubMarketCode != "")
{
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(db, account.SubMarket.SubMarketCode);
}
else
{
//Default
account.SubMarket = ResolveSubmarket(db, "COT");
}
MetaDataSetter.ForImport(account, "CSVImporter", "Initial import by CSV Importer (Function DbHelper.ImportAccountsFromCSV)");
}
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 ImportLSAGContactListToolData(string filepath = "", string separator = ";")
///Importiert Accounts und Contacts aus CSV in die DB
{
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<Contact> ContactsReadFromFile = new(8000);
List<Account> AccountsReadFromFile = new(1000);
using (TextFieldParser csvParser = new(filepath, FileIO.GetEncoding(filepath))) //ISO-8859-1 entspricht Latin1
{
//Parser configuration:
csvParser.Delimiters = new string[] { separator };
csvParser.CommentTokens = new string[] { "#" };
csvParser.HasFieldsEnclosedInQuotes = true;
//dynamische Spaltenzuordnung in Dictonary speichern
Dictionary<string, int> columnNumberOf = new();
string[] fields = csvParser.ReadFields();
columnNumberOf = ColumnMapping(fields);
//Daten einlesen und Accounts und Kontakte in eigene List<Account> bzw List<Contact> extrahieren.
while (!csvParser.EndOfData)
{
bool DataHasError = false;
Account ImportedAccount = new();
Contact ImportedContact = new();
ImportedAccount.SubMarket = new();
ImportedAccount.AccountType = new();
ImportedContact.Account = new();
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.
//Account-Daten
// Konvertierung erforderlich:
try
{
ImportedAccount.SAPAccountNumber = Convert.ToUInt32(fields[columnNumberOf["SAPAccountNumber"]]);
}
catch (FormatException ex)
{
//errorhandling here: "Unable to parse input: " + field[0] + " as uint"
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
catch (OverflowException ex)
{
//errorhandling here: "Number cannot fit in an Uint."
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
try
{
ImportedAccount.ZIP = Convert.ToUInt32(fields[columnNumberOf["ZIP"]]);
}
catch (FormatException ex)
{
//errorhandling here: "Unable to parse input: " + field[0] + " as uint"
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
catch (OverflowException ex)
{
//errorhandling here: "Number cannot fit in an Uint."
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
// Konvertierung für AccountCreatedinSAPOn Property:
int year = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(0, 4));
int month = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(4, 2));
int day = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(6, 2));
ImportedAccount.AccountCreatedInSAPOn = new DateTime(year, month, day);
//Convert City von Großschreibung zu Normalschreibung
ImportedAccount.City = fields[columnNumberOf["City"]].First().ToString() + fields[columnNumberOf["City"]].Substring(1).ToLower();
// keine Konvertierung nötig:
ImportedAccount.AccountName = fields[columnNumberOf["AccountName"]];
ImportedAccount.Street = fields[columnNumberOf["Street"]];
ImportedAccount.SubMarket.SubMarketCode = fields[columnNumberOf["SubMarketCode"]];
ImportedAccount.SubMarket.DataStatus = Status.Active.ToString();
ImportedAccount.AccountType.AccountTypeCode = fields[columnNumberOf["AccountTypeCode"]];
ImportedAccount.AccountType.DataStatus = Status.Active.ToString();
ImportedAccount.PhoneNumber = fields[columnNumberOf["AccountPhoneNumber"]];
ImportedAccount.DataStatus = Status.Active.ToString();
//Validierungen:
if (ImportedAccount.AccountName == ""
|| ImportedAccount.City == ""
|| ImportedAccount.Street == ""
|| ImportedAccount.SubMarket.SubMarketCode == ""
|| ImportedAccount.AccountType.AccountTypeCode == ""
|| ImportedAccount.SAPAccountNumber == 0)
{
DataHasError = true;
}
//Validierten Account der Liste hinzufügen:
if (DataHasError == false) ImportedAccount.AddIfUniqueTo(AccountsReadFromFile);
//Contact-Daten
// Ohne Konvertierung
ImportedContact.AcademicTitle = fields[columnNumberOf["AcademicTitle"]];
ImportedContact.FirstName = fields[columnNumberOf["FirstName"]];
ImportedContact.LastName = fields[columnNumberOf["LastName"]];
ImportedContact.EMail = fields[columnNumberOf["EMail"]];
ImportedContact.Department = fields[columnNumberOf["Department"]];
ImportedContact.Room = fields[columnNumberOf["Room"]];
ImportedContact.PhoneNumber = fields[columnNumberOf["PhoneNumber"]];
ImportedContact.Function = fields[columnNumberOf["Function"]];
ImportedContact.MobileNumber = fields[columnNumberOf["MobileNumber"]];
//Convert Gender
ImportedContact.Gender = fields[columnNumberOf["Gender"]] == "M"
? (byte)Gender.Male
: fields[columnNumberOf["Gender"]] == "F" ? (byte)Gender.Female : (byte)Gender.Unknown;
//Convert OptIn Status
ImportedContact.OptInStatus = fields[columnNumberOf["OptInStatus"]] == "Opt In"
? true
: fields[columnNumberOf["OptInStatus"]] == "Opt Out" ? false : null;
//Convert "SAP Contact Number"
try
{
ImportedContact.SAPContactNumber = Convert.ToInt32(fields[columnNumberOf["SAPContactNumber"]]);
}
catch (FormatException ex)
{
//errorhandling here: "Unable to parse input: " + field[0] + " as uint"
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
catch (OverflowException ex)
{
//errorhandling here: "Number cannot fit in an Uint."
//Zeilennummer, Feld und Wert in Liste speichern, um am Ende gesammelt auszugeben
string errorRaiser = fields[columnNumberOf["SAPContactNumber"]];
if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
DisplayErrorDetails(ex, errorRaiser);
return false;
}
//Convert "Account Created on"
year = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(0, 4));
month = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(4, 2));
day = Convert.ToInt32(fields[columnNumberOf["AccountCreatedInSAPOn"]].Substring(6, 2));
ImportedContact.SAPContactCreationDate = new DateTime(year, month, day);
//Convert "No Phone Calls"
if (fields[columnNumberOf["NoPhoneCalls"]] == "1") ImportedContact.NoPhoneCalls = true;
//Convert "No Hardcopy Mailing"
if (fields[columnNumberOf["NoHardcopyMailing"]] == "1") ImportedContact.NoHardcopyMailing = true;
//SAPAccountID in Contact.Notes speichern, um den entsprechenden Account aus DB heraussuchen zu können:
ImportedContact.Notes = fields[columnNumberOf["SAPAccountNumber"]];
//Validierungen:
if (ImportedContact.EmailBounced || ImportedContact.LastName == "" || ImportedContact.SAPContactNumber == 0)
DataHasError = true;
if (ImportedContact.EMail.Length > 7 && ImportedContact.EMail.Substring(0, 8) == "bounced-")
DataHasError = true;
//Validierte Kontakte zu Liste hinzufügen:
if (DataHasError == false) ContactsReadFromFile.Add(ImportedContact);
}
//Eingelesenen Account in DB schreiben:
using (GremlinContext db = new())
{
DateTime 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 = db.AccountTypes
.Where(a => a.AccountTypeCode == account.AccountType.AccountTypeCode)
.First();
account.AccountType = accountType;
SubMarket subMarket = db.SubMarkets
.Where(a => a.SubMarketCode == account.SubMarket.SubMarketCode)
.First();
account.SubMarket = subMarket;
account.AccountType = ResolveAccountType(db, account.AccountType.AccountTypeCode);
account.SubMarket = ResolveSubmarket(db, account.SubMarket.SubMarketCode);
MetaDataSetter.ForImport(account, "CSVImporter", "Initial import by CSV Importer (Function DbHelper.ImportAccountsFromCSV)");
}
db.Accounts.AddRange(AccountsReadFromFile);
_ = db.SaveChanges();
}
//Eingelesenen Contacts in DB schreiben:
using (GremlinContext db = new())
{
DateTime now = DateTime.Now;
foreach (Contact contact in ContactsReadFromFile)
{
contact.Account = ResolveAccountById(db, uint.Parse(contact.Notes));
contact.Notes = "";
MetaDataSetter.ForImport(contact, "CSVImporter", "Initial import by CSV Importer (Function DbHelper.ImportContactsFromCSV)");
}
db.Contacts.AddRange(ContactsReadFromFile);
_ = db.SaveChanges();
}
};
return true;
}
public static bool ImportProductsFromCsv(string filepath = "", string separator = "|", bool dataHasHeading = true)
///Rückgabe 'false' bei Fehler oder User-Abbruch, ansonsten 'true'.
///
///Optionale Argumente:
/// 1. string filepath: Dateipfad zur einzulesenden Datei. Standardwert = "", öffnet OpenFile-Dialog.
/// 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 = FileIO.GetFilepathFromUser(); //Pfad abfragen über Dtei-Öffnen-Dialog.
if (filepath == "") return false; //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch.
List<Product> ProductsReadFromFile = new(ParseProductFile(filepath, separator, dataHasHeading));
return InsertProducts(ProductsReadFromFile);
}
public static bool ImportCustomDescriptionsFromCsv(string filepath = "", string separator = "|", bool dataHasHeading = true)
///Importiert Eigene Beschreibungen aus Word-Dokument
///Abkürzung: CD = CustomDescriptions
{
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<CustomDescription> 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();
while (!csvParser.EndOfData)
{
// Read current line fields, pointer moves to the next line.
string[] fields = csvParser.ReadFields();
//string productNumber = fields[0];
//string? optionNumber = fields[1] == "" ? null : fields[1];
CustomDescription ImportedCD = new()
{
ProductNumber = fields[0],
OptionNumber = fields[1] == "" ? null : fields[1],
Heading = fields[3],
DescriptionText = fields[4],
CoverletterText = fields[5],
Notes = fields[6],
};
ImportedCD.Products = new List<Product>();
ImportedCD.Supplier = new();
ImportedCD.Supplier.AccountName = fields[2] is "" or "RB" ? "Agilent Technologies" : fields[2];
MetaDataSetter.ForImport(ImportedCD, "Importer", "Initial Importer by CD-ImporterFomCsv");
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<Product> thirdPartyProductsFromImportedCDs = new();
foreach (CustomDescription CD in CDsReadFromFile)
{
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.BreakRangeFrom = 0;
new3PPProduct.BreakRangeTo = 0;
new3PPProduct.ProductLine = ResolveProductLine(db, "3P");
new3PPProduct.CustomDescription.Supplier = ResolveAccountByName(db, new3PPProduct.CustomDescription.Supplier.AccountName);
MetaDataSetter.ForImport(new3PPProduct, "Custom Desciption Importer", "Created at import from Custom Descriptions.");
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<CustomDescription> importedCDsWithEFReferences = new(100000);
List<CustomDescription> CDsWithoutEFReferences = new(1000); //nur zur Kontrolle, wird nicht verwendet.
List<Product> 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.Intersect(CD.Products).Any()) 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.Products = productsInDb.Where(product => product.ProductNumber == CD.ProductNumber && product.OptionNumber == CD.OptionNumber).ToList();
if (CD.Products == 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;
MetaDataSetter.ForImport(CD, "CSVImporter", "Initial import by CSV Importer (Function DbHelper.ImportCustomDescriptionsFromCsv)");
//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<CustomDescription> 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<Table>().ElementAt(0); //alternativ: Elements<Table>.First()
if (CDTable != null)
{
CDTable.Descendants<TableRow>().First().Remove(); //Überschriften löschen
foreach (TableRow aRow in CDTable.Descendants<TableRow>()) //einmal durch den XML-Baum wandern: WordprocessingDocument -> MainDocumentPart -> Document -> Body -> Table -> TableRow -> TableCell -> Paragraph -> Run -> Text
{
CustomDescription importedCD = new();
List<string> stringsRead = new();
foreach (TableCell aCell in aRow.Descendants<TableCell>())
{
StringBuilder sb = new();
StringBuilder note = new();
foreach (Paragraph aParagraph in aCell.Descendants<Paragraph>())
{
foreach (Run aRun in aParagraph.Descendants<Run>())
{
foreach (Text aText in aRun.Descendants<Text>())
{
string temp = "";
temp = aParagraph.Descendants<Run>()
.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...
{
_ = sb.Append(aText.InnerText);
}
}
}
}
stringsRead.Add(sb.ToString()); //...und zellenweise abspeichern
if (note.ToString() != "")
{
importedCD.Notes = note.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);
importedCDs.Add(importedCD);
//if (importedCDs.Count >= 1200 && importedCDs.Count % 100 == 0) MessageBox.Show(importedCDs.Count.ToString()); //DEBUGGING
}
}
//MessageBox.Show(importedCDs.Count.ToString()); //DEBUGGING
CDDoc.Close();
//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<Product> 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.BreakRangeFrom = 0;
new3PPProduct.BreakRangeTo = 0;
new3PPProduct.ProductLine = ResolveProductLine(db, "3P");
new3PPProduct.CustomDescription.Supplier = ResolveAccountByName(db, new3PPProduct.CustomDescription.Supplier.AccountName);
MetaDataSetter.ForImport(new3PPProduct, "Custom Desciption Importer", "Created at import from Custom Descriptions.");
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<CustomDescription> importedCDsWithEFReferences = new();
List<CustomDescription> 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.Intersect(CD.Products).Any()) continue;
//Establish EF Reference. If no PN/Opt found, then skip this custom description.
CD.Products.Add(ResolveProduct(db, CD.ProductNumber, CD.OptionNumber)); //ResolveXY-functions return null, if no match is found in db.
if (CD.Products == null)
{
CDsWithoutEFReferences.Add(CD);
continue;
}
//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;
}
MetaDataSetter.ForImport(CD, "DocxImporter", "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();
}
return 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);
if (filepath == "") return false; //Wenn keine Datei ausgewählt (Cancel geklickt), dann Abbruch.
//Aktiven Produkte aus DB laden
List<Product> ActiveProductsInDb = new(120000);
using (GremlinContext db = new())
{
//Aktive Produkte aus der DB einlesen
List<ProductLine> PLs = db.ProductLines.ToList();
ActiveProductsInDb = db.Products
.Where(p => p.DataStatus == Status.Active.ToString())
.Include(p => p.ProductLine)
.ToList();
//Neue CPL einlesen...
List<Product> ProductsReadFromFile = new(ParseProductFile(filepath, separator, dataHasHeading));
//...Aufteilung in neue und nicht-neue Produkte...
List<Product> NewProducts = new(FilterNewProducts(ProductsReadFromFile));
List<Product> NonNewProducts = new(ProductsReadFromFile.Except(NewProducts));
//...Letztere wiederum in obsolete Produkte...
ProductEqualityComparer ProEC = new();
List<Product> 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 = "CPL Updater";
}
//den Rest prozessieren
List<Product> UpdatedProducts = new(10000);
string dataVersionComment = "Data as per " + fileName;
////Counter für Debugging
//int x = 0;
//Multithreading
_ = Parallel.ForEach(NonNewProducts, parallelOptions, (product) =>
{
////Debugging-Counter
//x++;
Product ExistingProduct = ActiveProductsInDb
.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)
{
NewProducts.Add(product);
return;
}
//Wenn keine Änderung, dann nur DataVersionComment mit CPL-Dateinamen aktualisieren...
if (product.ListPrice == ExistingProduct.ListPrice
&& product.ProductStatus == ExistingProduct.ProductStatus
&& Normalize(product.SapLongDescription) == Normalize(ExistingProduct.SapLongDescription)
&& Normalize(product.SapShortDescription) == Normalize(ExistingProduct.SapShortDescription))
{
ExistingProduct.DataVersionComment = dataVersionComment;
}
//andernfalls die vorhandene Datensatz-Version archivieren und neue Version anlegen
else //alten Datensatz archivieren:
{
ExistingProduct.DataStatus = Status.Archived.ToString();
ExistingProduct.DataModificationByUser = "CPL Updater";
ExistingProduct.DataModificationDate = now;
ExistingProduct.DataValidUntil = now;
ExistingProduct.DataVersionComment = "Archived b/c of data update";
if (product.ListPrice != ExistingProduct.ListPrice)
{
ExistingProduct.ProductStatus = Status.PriceUpdated.ToString();
}
else if (Normalize(product.SapLongDescription) != Normalize(ExistingProduct.SapLongDescription)
|| Normalize(product.SapShortDescription) != Normalize(ExistingProduct.SapShortDescription))
{
ExistingProduct.ProductStatus = Status.DescriptionUpdated.ToString();
}
else if (product.ProductStatus == ExistingProduct.ProductStatus)
{
ExistingProduct.ProductStatus = Status.StatusUpdated.ToString();
}
//neuen Datensatz anlegen
Product UpdatedProduct = new()
{
ProductNumber = product.ProductNumber,
OptionNumber = product.OptionNumber,
BreakRangeFrom = product.BreakRangeFrom,
BreakRangeTo = product.BreakRangeTo,
HasBreakPrices = product.HasBreakPrices,
IntroductionDate = ExistingProduct.IntroductionDate,
ListPrice = product.ListPrice,
ProductStatus = Status.Active.ToString(),
SapLongDescription = product.SapLongDescription,
SapShortDescription = product.SapShortDescription,
Weight = product.Weight,
};
MetaDataSetter.ForImport(UpdatedProduct, "CPL Updater", "Product Update");
UpdatedProduct.ProductLine = product.ProductLine.ProductLineCode == ExistingProduct.ProductLine.ProductLineCode
? product.ProductLine
: ResolveProductLine(db, product.ProductLine.ProductLineCode);
NewProducts.Add(UpdatedProduct);
}
UpdatedProducts.Add(ExistingProduct);
});
_ = InsertProducts(NewProducts); //enthält auch ResolvePL()
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 Dictionary<string, int> ColumnMapping(string[] fields)
{
Dictionary<string, int> 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;
}
private static List<Product> 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<Product> 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();
//Kontrolle, ob Trennzeichen in Daten vorhanden ist:
if (fields.Length > 27) //27 ist der Normalfall
{
//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
{
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];
}
}
}
}
}
//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
ImportedProduct.ProductNumber = fields[1];
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];
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?
//if (fields[5] != "")
// if (decimal.Parse(fields[5], new CultureInfo("de-de")) != ImportedProduct.ListPrice)
// listpriceHasChanged = true;
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)
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;
}
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 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;
}
}
public static bool InsertProducts(List<Product> products)
{
using (GremlinContext db = new())
{
DateTime now = DateTime.Now;
List<ProductLine> productLines = db.ProductLines.ToList();
foreach (Product product in products)
{
product.ProductLine = productLines.Find(x => x.ProductLineCode == product.ProductLine.ProductLineCode);
MetaDataSetter.ForImport(product, "CSVImporter", "Initial import by CSV Importer (Function DbHelper.ImportProductsFromCSV)");
}
db.Products.AddRange(products);
db.SaveChanges();
//Bestätigung senden
MessageBox.Show($"Es wurden {products.Count} Produkte erfolgreich der Datenbank hinzugefügt.");
return true;
}
}
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];
}
else return filename[0];
}
private static string Normalize(string input)
{
return input.ToLower()
.Trim()
.Replace(" ", "")
.Replace("-", "")
.Replace("_", "")
.Replace(".", "");
}
private static List<Product> FilterNewProducts(List<Product> products)
{
DateTime now = DateTime.Now;
List<Product> results = new(5000);
foreach (Product product in products)
{
if (product.ProductStatus == Status.New.ToString())
{
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;
}
//private static List<Product> FilterProductsWithPriceChange(List<Product> products)
//{
// List<Product> results = new();
// foreach (var product in products)
// {
// if (product.ProductStatus == Status.PriceUpdated.ToString())
// {
// results.Add(product);
// }
// }
// return results;
//}
public static async Task<bool> ImportLSAGContactListToolDataAsync()
{
return await Task.Run(() => ImportLSAGContactListToolData());
}
public static async Task<bool> ImportAccountsFromCsvAsync()
{
return await Task.Run(() => ImportAccountsFromCsv());
}
public static async Task<bool> ImportContactsFromCsvAsync()
{
return await Task.Run(() => ImportContactsFromCsv());
}
public static async Task<bool> ImportProductsFromCsvAsync()
{
return await Task.Run(() => ImportProductsFromCsv());
}
public static async Task<bool> ImportCustomDescriptionsFromDocxAsync()
{
return await Task.Run(() => ImportCustomDescriptionsFromDocx());
}
public static async Task<bool> ImportCustomDescriptionsFromCsvAsync()
{
return await Task.Run(() => ImportCustomDescriptionsFromCsv());
}
public static async Task<bool> UpdateProductsFromCsvAsync()
{
return await Task.Run(() => UpdateProductsFromCsv());
}
public static Account ResolveAccountByName(GremlinContext context, string accountName)
{
try { return context.Accounts.Include(account => account.AccountType).Include(account => account.SubMarket).Where(account => account.AccountName == accountName).First(); }
catch { return null; }
}
public static Account ResolveAccountById(GremlinContext context, uint accountId)
{
try { return context.Accounts.Where(account => account.SAPAccountNumber == accountId).First(); }
catch { return null; }
}
public static Account ResolveAccountById(GremlinContext context, string accountId)
{
try { return context.Accounts.Where(account => account.SAPAccountNumber == Convert.ToUInt32(accountId)).First(); }
catch { return null; }
}
public static AccountType ResolveAccountType(GremlinContext context, string accountTypeCode)
{
try { return context.AccountTypes.Where(account => account.AccountTypeCode == accountTypeCode).First(); }
catch { return null; }
}
public static SubMarket ResolveSubmarket(GremlinContext context, string subMarketCode)
{
try { return context.SubMarkets.Where(submarket => submarket.SubMarketCode == subMarketCode).First(); }
catch { return null; }
}
public static Product GetProduct(GremlinContext context, string productNumber, string optionNumber)
{
try { return context.Products.Include(product => product.ProductLine).Include(product => product.CustomDescription).Where(product => product.ProductNumber == productNumber && product.OptionNumber == optionNumber).First(); }
catch { return null; }
}
public static Product ResolveProduct(GremlinContext context, string productNumber, string option)
{
try {return context.Products.Where(product => product.ProductNumber == productNumber && product.OptionNumber == option).First(); }
catch { return null; }
}
public static ProductLine ResolveProductLine(GremlinContext context, string productLineCode)
{
try { return context.ProductLines.Where(productline => productline.ProductLineCode == productLineCode).First(); }
catch { return null; }
}
public static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
return new string(Enumerable.Repeat(chars, length).Select(s => s[random.Next(s.Length)]).ToArray());
}
}
}