986 lines
43 KiB
C#
986 lines
43 KiB
C#
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
using Gremlin_BlazorServer.Data.EntityClasses;
|
|
using Gremlin_BlazorServer.Services;
|
|
using Gremlin_BlazorServer.Utilities.GUClasses;
|
|
using Microsoft.VisualBasic.FileIO;
|
|
using static Gremlin_BlazorServer.Data.EntityClasses.Enums;
|
|
|
|
namespace Gremlin_BlazorServer.Data.DBClasses;
|
|
|
|
public class GenericImporter {
|
|
//Private members
|
|
private static readonly DateTime farInTheFuture = DateTime.Parse("2050-12-31t00:00:00.000000z", CultureInfo.CurrentCulture);
|
|
|
|
private static Encoding encoding = Encoding.UTF8;
|
|
|
|
private static TextFieldParser csvParser = new(Filepath, encoding, true);
|
|
|
|
private static readonly GremlinDb gremlinDb = new();
|
|
|
|
//Public properties
|
|
public static string? Filepath { get; set; }
|
|
|
|
private static string[] Separators {
|
|
get => csvParser.Delimiters!;
|
|
set {
|
|
if (value.Length > 0) csvParser.SetDelimiters(value);
|
|
}
|
|
}
|
|
|
|
public static bool DataHasHeadings { get; set; }
|
|
|
|
private static Encoding Encoding {
|
|
set {
|
|
encoding = value;
|
|
ResetParser();
|
|
}
|
|
}
|
|
|
|
////Constructors
|
|
//public GenericImporter(string filepath)
|
|
//{
|
|
// Filepath = filepath;
|
|
// _dataHasHeadings = true;
|
|
// _encoding = FileIO.GetEncoding(Filepath);
|
|
// TextFieldParser _csvParser = new(Filepath, _encoding);
|
|
// _csvParser.SetDelimiters(Separators);
|
|
// _csvParser.HasFieldsEnclosedInQuotes = true;
|
|
//}
|
|
|
|
//public GenericImporter(string filepath, Encoding encoding)
|
|
//{
|
|
// Filepath = filepath;
|
|
// _dataHasHeadings = true;
|
|
// _encoding = encoding;
|
|
// TextFieldParser _csvParser = new(Filepath, _encoding);
|
|
// _csvParser.SetDelimiters(Separators);
|
|
// _csvParser.HasFieldsEnclosedInQuotes = true;
|
|
//}
|
|
|
|
//public GenericImporter(string filepath, string[] separators)
|
|
//{
|
|
// Filepath = filepath;
|
|
// _dataHasHeadings = true;
|
|
// _encoding = FileIO.GetEncoding(Filepath);
|
|
// TextFieldParser _csvParser = new(Filepath, _encoding);
|
|
// Separators = separators;
|
|
// _csvParser.HasFieldsEnclosedInQuotes = true;
|
|
//}
|
|
//public GenericImporter(string filepath, Encoding encoding, string[] separators)
|
|
//{
|
|
// Filepath = filepath;
|
|
// _encoding = encoding;
|
|
// _dataHasHeadings = true;
|
|
// TextFieldParser _csvParser = new(Filepath, _encoding);
|
|
// Separators = separators;
|
|
// _csvParser.HasFieldsEnclosedInQuotes = true;
|
|
//}
|
|
|
|
|
|
//Public methods
|
|
private static void SetFilepath() {
|
|
//Filepath = FileIO.GetFilepathFromUser();
|
|
}
|
|
|
|
private static void ResetParser() {
|
|
if (Filepath != "") {
|
|
TextFieldParser newParser = new(Filepath, encoding);
|
|
|
|
newParser.SetDelimiters(Separators);
|
|
newParser.HasFieldsEnclosedInQuotes = true;
|
|
csvParser = newParser;
|
|
}
|
|
}
|
|
|
|
public static bool Run(string filepath, string separator, string encoding) {
|
|
Filepath = filepath.Replace(@"\\", @"\");
|
|
try {
|
|
Encoding = Encoding.GetEncoding(encoding);
|
|
}
|
|
catch (Exception) {
|
|
Encoding = FileService.GetEncoding(Filepath);
|
|
}
|
|
|
|
Separators = new[] { separator };
|
|
|
|
return ImportFile();
|
|
}
|
|
|
|
public async Task<bool> RunAsync(string filepath, string separator, string encoding) {
|
|
if (filepath is "" or null) return false;
|
|
|
|
Filepath = filepath.Replace(@"\\", @"\");
|
|
|
|
try {
|
|
Encoding = Encoding.GetEncoding(encoding);
|
|
}
|
|
catch (Exception) {
|
|
Encoding = FileService.GetEncoding(Filepath);
|
|
}
|
|
|
|
Separators = new[] { separator };
|
|
|
|
return await Task.Run(ImportFile);
|
|
}
|
|
|
|
public static string GuessSeparator(string filepath) //, string encoding)
|
|
{
|
|
if (filepath is "" or null) return string.Empty;
|
|
|
|
string[] candidates = { "|", ",", ";" };
|
|
int numberOfCandidates = candidates.Length;
|
|
const int numberOfLinesToEvaluate = 100;
|
|
int[,] score = new int[numberOfLinesToEvaluate, numberOfCandidates];
|
|
|
|
Filepath = filepath.Replace(@"\\", @"\");
|
|
//if (csvParser == null)
|
|
//{
|
|
// try
|
|
// {
|
|
// Encoding = Encoding.GetEncoding(encoding);
|
|
// }
|
|
// catch (Exception)
|
|
// {
|
|
// Encoding = FileIO.GetEncoding(GenericImporter.filepath);
|
|
// }
|
|
//}
|
|
|
|
using (csvParser) {
|
|
for (int i = 0; i < numberOfLinesToEvaluate; i++)
|
|
if (!csvParser.EndOfData) {
|
|
string line = csvParser.ReadLine()!;
|
|
for (int j = 0; j < numberOfCandidates; j++) score[i, j] = line.Split(candidates[j]).Length;
|
|
}
|
|
}
|
|
|
|
List<(string, int, float)> scoreBoard = new(); //Item1 = Separator, Item2 = Score (Anzahl aufeinanderfolgender Zeilen mit gleicher Anzahl von Fields), Item3 = Count (durchschnittliche Anzahl von Fields in Zeile)
|
|
for (int j = 0; j < numberOfCandidates; j++) {
|
|
int x = 0;
|
|
float average = 0;
|
|
for (int i = 0; i < numberOfLinesToEvaluate - 1; i++) {
|
|
if (score[i, j] == score[i + 1, j] && score[i, j] > 1) x++;
|
|
|
|
average += score[i, j];
|
|
}
|
|
|
|
average += score[numberOfLinesToEvaluate - 1, j];
|
|
average /= numberOfLinesToEvaluate;
|
|
scoreBoard.Add((candidates[j], x, average));
|
|
}
|
|
|
|
ResetParser();
|
|
return scoreBoard.Find(f => f.Item2 == scoreBoard.Max(x => x.Item2) && f.Item3 == scoreBoard.Max(x => x.Item3)).Item1;
|
|
}
|
|
|
|
private static bool ImportFile()
|
|
//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 == "") SetFilepath();
|
|
|
|
if (Filepath == "") return false;
|
|
|
|
using (csvParser) {
|
|
//für geneerischen Code:
|
|
//int numberOfLines = File.ReadAllLines(filepath).Length;
|
|
//Assembly Gremlin = Assembly.GetExecutingAssembly();
|
|
|
|
|
|
//dynamische Spaltenzuordnung in Dictonary speichern
|
|
string[] fields = csvParser.ReadFields()!;
|
|
Dictionary<string, string> mappingDictionary = ReadMappingDictionaryFromFile();
|
|
Dictionary<string, int> mappingTable = MapDataHeading(fields, mappingDictionary);
|
|
|
|
//determine data type to be imported
|
|
DataIdentificator dataIdentificator = new(mappingTable);
|
|
List<string> detectedDataTypes = dataIdentificator.Identify();
|
|
|
|
//check for detectedDataTypes = empty (possible when DataIdentificator.Identify(MustMatchAllQualifer = true) and no dataset matches all qualifiers of a type)
|
|
if (detectedDataTypes.Count == 0) {
|
|
Console.WriteLine($"DataIdentificator.Identify() konnte die Datenart nicht bestimmen!{Environment.NewLine}Versuchen Sie den Import mit einer dezidierten Importerfunktion.");
|
|
return false;
|
|
}
|
|
|
|
return detectedDataTypes[0] switch {
|
|
"ProductLine" => ImportProductLine(csvParser, mappingTable),
|
|
"AccountType" => ImportAccountType(csvParser, mappingTable),
|
|
"SubMarket" => ImportSubMarket(csvParser, mappingTable),
|
|
"Account" => ImportAccounts(mappingTable),
|
|
"Contact" => ImportContacts(mappingTable),
|
|
"LSAG" => ImportLsag(mappingTable),
|
|
"Product" => ImportProducts(mappingTable),
|
|
"CustomDescription" => ImportCustomDescriptions(), // mappingTable);
|
|
_ => false
|
|
};
|
|
}
|
|
}
|
|
|
|
public static bool ImportFile(string filepath) {
|
|
Filepath = filepath;
|
|
return ImportFile();
|
|
}
|
|
|
|
private static bool ImportCustomDescriptions() //Dictionary<string, int> mappingTable)
|
|
{
|
|
List<CustomDescription> cDsReadFromFile = new(2500);
|
|
Encoding = Encoding.GetEncoding("UTF-8"); //Custom-Descriptions-CSV hat festes Encoding.
|
|
using (csvParser) {
|
|
// Skip the row with the column names:
|
|
_ = csvParser.ReadLine();
|
|
|
|
while (!csvParser.EndOfData) {
|
|
// Read current line fields, pointer moves to the next line.
|
|
string[] fields = csvParser.ReadFields()!;
|
|
CustomDescription importedCd = new() {
|
|
ProductNumber = fields[0],
|
|
OptionNumber = 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",
|
|
// Product = new Product(),
|
|
Supplier = new() { 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 (GremlinDb gremlinDb = new()) {
|
|
//Step 1a
|
|
List<Product> thirdPartyProductsFromImportedCDs = new();
|
|
foreach (CustomDescription cd in cDsReadFromFile)
|
|
if (cd.Supplier.AccountName != "Agilent Technologies") {
|
|
Product new3PpProduct = new() {
|
|
CustomDescription = cd,
|
|
HasBreakPrices = false,
|
|
ListPrice = 0,
|
|
ProductNumber = cd.ProductNumber,
|
|
OptionNumber = cd.OptionNumber,
|
|
ProductStatus = Status.Active.ToString(),
|
|
SapLongDescription = "",
|
|
SapShortDescription = "",
|
|
Weight = 0,
|
|
IntroductionDate = DateTime.Now.Date,
|
|
BreakRangeFrom = 0,
|
|
BreakRangeTo = 0,
|
|
ProductLine = DbHelper.ResolveProductLine(gremlinDb, "3P")
|
|
};
|
|
|
|
new3PpProduct.CustomDescription.Supplier = DbHelper.ResolveAccountByName(gremlinDb, new3PpProduct.CustomDescription.Supplier.AccountName);
|
|
|
|
_ = MetaDataSetter.ForImport(new3PpProduct, "GenericImporter-Method", "Created at import from Custom Descriptions.");
|
|
thirdPartyProductsFromImportedCDs.Add(new3PpProduct);
|
|
}
|
|
|
|
//Step 1b
|
|
|
|
gremlinDb.Products.AddRange(thirdPartyProductsFromImportedCDs); //Imports both the products and the associated custom descriptions!
|
|
|
|
_ = gremlinDb.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(100); //nur zur Kontrolle, wird nicht verwendet.
|
|
List<Product> productsInDb = gremlinDb.Products.ToList();
|
|
|
|
Account agilent = gremlinDb.Accounts.Single(a => a.AccountName == "Agilent Technologies");
|
|
|
|
foreach (CustomDescription cD in cDsReadFromFile) {
|
|
//Skip Desciptions, if it has been already imported above (as part from 3PP)
|
|
|
|
// if (thirdPartyProductsFromImportedCDs.Equals(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.Where(product => product.ProductNumber == cD.ProductNumber && product.OptionNumber == cD.OptionNumber).FirstOrDefault();
|
|
|
|
//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).
|
|
cD.Supplier = agilent;
|
|
|
|
//prepare properties
|
|
_ = MetaDataSetter.ForImport(cD, "GenericImporter-Method", "Initial import by CSV Importer (Function GenericImporter.ImportCustomDescriptions)");
|
|
|
|
//add to final list of CDs, that will go into the db.
|
|
importedCDsWithEfReferences.Add(cD);
|
|
}
|
|
|
|
|
|
gremlinDb.CustomDescriptions.AddRange(importedCDsWithEfReferences);
|
|
|
|
_ = gremlinDb.SaveChanges();
|
|
//Bestätigung senden
|
|
Console.WriteLine($"Es wurden {importedCDsWithEfReferences.Count} eigene Beschreibungen erfolgreich der Datenbank hinzugefügt.{Environment.NewLine}Es wurden {thirdPartyProductsFromImportedCDs.Count} 3PP-Produkte neu angelegt.");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private static bool ImportProducts(Dictionary<string, int> mappingTable) {
|
|
List<Product> productsReadFromFile = new(ParseProductFile(mappingTable));
|
|
return InsertProducts(productsReadFromFile);
|
|
}
|
|
|
|
private static bool InsertProducts(List<Product> products) {
|
|
using GremlinDb gremlinDb = new();
|
|
|
|
List<ProductLine> productLines = gremlinDb.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.ProductLineCode == product.ProductLine.ProductLineCode) ?? new ProductLine();
|
|
|
|
_ = MetaDataSetter.ForImport(product, "GenericImporter-Method");
|
|
}
|
|
|
|
|
|
gremlinDb.Products.AddRange(products);
|
|
|
|
_ = gremlinDb.SaveChanges();
|
|
//Bestätigung senden
|
|
Console.WriteLine($"Es wurden {products.Count} Produkte erfolgreich der Datenbank hinzugefügt.");
|
|
return true;
|
|
}
|
|
|
|
private static List<Product> ParseProductFile(Dictionary<string, int> columnNumberOf) {
|
|
List<Product> results = new(100000);
|
|
using (csvParser) {
|
|
while (!csvParser.EndOfData) {
|
|
// Read current line fields, pointer moves to the next line.
|
|
Product importedProduct = new() { 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[columnNumberOf["ProductNumber"]];
|
|
|
|
if (fields[columnNumberOf["OptionNumber"]].Length == 4) //Optionsnummer mit führendem Apostroph
|
|
importedProduct.OptionNumber = fields[columnNumberOf["OptionNumber"]].Substring(1); //schneidet erstes Zeichen/Apostroph weg
|
|
else if (fields[columnNumberOf["OptionNumber"]].Length == 3) //3-stellige Optionsnummer übernehmen; keine Aktion bei leerem Feld (keine Optionsnummer)
|
|
importedProduct.OptionNumber = fields[columnNumberOf["OptionNumber"]];
|
|
|
|
importedProduct.SapShortDescription = fields[columnNumberOf["SapShortDescription"]];
|
|
|
|
importedProduct.ListPrice = decimal.Parse(fields[columnNumberOf["ListPrice"]], new CultureInfo("de-de")); //parsing! compare with old value (either from CSV or from DB) -> price change?
|
|
|
|
//if (fields[columnNumberOf["ListPrice"]] != "")
|
|
// if (decimal.Parse(fields[columnNumberOf["ListPrice"]], new CultureInfo("de-de")) != ImportedProduct.ListPrice)
|
|
// listpriceHasChanged = true;
|
|
|
|
importedProduct.BreakRangeFrom = fields[columnNumberOf["BreakRangeFrom"]] == "" ? 0 : Convert.ToInt32(fields[columnNumberOf["BreakRangeFrom"]]);
|
|
importedProduct.HasBreakPrices = importedProduct.BreakRangeFrom > 0;
|
|
importedProduct.BreakRangeTo = fields[columnNumberOf["BreakRangeTo"]] is "" or "+" ? 0 : Convert.ToInt32(fields[columnNumberOf["BreakRangeTo"]]); //erfasst sowohl Produkte ohne Break-Preise ("") als auch "+" bei Mengenangaben a la "100+" (= von 100 bis unendlich)
|
|
|
|
importedProduct.ProductLine.ProductLineCode = fields[columnNumberOf["ProductLineCode"]];
|
|
|
|
switch (fields[columnNumberOf["ProductStatus"]]) {
|
|
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;
|
|
}
|
|
|
|
importedProduct.Weight = ParseWeight(fields[columnNumberOf["Weight"]]);
|
|
if (fields[columnNumberOf["WeightUnit"]] == "G") importedProduct.Weight /= 1000; //Umrechnung g in kg
|
|
|
|
if (fields[columnNumberOf["IntroductionDate"]] != "") importedProduct.IntroductionDate = DateTime.Parse(fields[columnNumberOf["IntroductionDate"]]);
|
|
|
|
importedProduct.SapLongDescription = fields[columnNumberOf["SapLongDescription"]];
|
|
results.Add(importedProduct);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
private 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) {
|
|
Debug.WriteLine(ex, "Unhandled Error in Function 'ParseWeight'");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
private static bool ImportLsag(Dictionary<string, int> mappingTable) {
|
|
bool result = ImportAccounts(mappingTable);
|
|
ResetParser();
|
|
_ = csvParser.ReadFields(); //Skip Heading
|
|
result = result && ImportContacts(mappingTable);
|
|
return result;
|
|
}
|
|
|
|
private static bool ImportContacts(Dictionary<string, int> columnNumberOf) {
|
|
List<Contact> contactsReadFromFile = new(8000);
|
|
using (csvParser) {
|
|
while (!csvParser.EndOfData) {
|
|
Contact importedContact = new();
|
|
string[] fields = csvParser.ReadFields()!;
|
|
|
|
//No conversion
|
|
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"]] switch {
|
|
"Opt In" => true,
|
|
"Opt Out" => false,
|
|
_ => false
|
|
};
|
|
|
|
//Convert "SAP Contact Number"
|
|
try {
|
|
importedContact.SapContactNumber = Convert.ToUInt32(fields[columnNumberOf["SAPContactNumber"]], 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[columnNumberOf["SAPContactNumber"]];
|
|
if (errorRaiser == "") errorRaiser = "No Contact Number in this row!";
|
|
|
|
DbHelper.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!";
|
|
|
|
DbHelper.DisplayErrorDetails(ex, errorRaiser);
|
|
return false;
|
|
}
|
|
|
|
//Convert "Account Created on"
|
|
int year = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(0, 4), CultureInfo.CurrentCulture);
|
|
int month = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(4, 2), CultureInfo.CurrentCulture);
|
|
int day = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(6, 2), CultureInfo.CurrentCulture);
|
|
importedContact.SapContactCreationDate = new(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"]];
|
|
contactsReadFromFile.Add(importedContact);
|
|
}
|
|
|
|
//Eingelesenen Account in DB schreiben:
|
|
using (GremlinDb gremlinDb = new()) {
|
|
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 = DbHelper.ResolveAccountById(gremlinDb, Convert.ToUInt32(contact.Notes));
|
|
contact.Notes = "";
|
|
_ = MetaDataSetter.ForImport(contact, "GenericImporter-Method");
|
|
}
|
|
|
|
|
|
gremlinDb.Contacts.AddRange(contactsReadFromFile);
|
|
|
|
_ = gremlinDb.SaveChanges();
|
|
}
|
|
}
|
|
|
|
//Bestätigung senden
|
|
Console.WriteLine($"Es wurden {contactsReadFromFile.Count} Contacts erfolgreich der Datenbank hinzugefügt.");
|
|
return true;
|
|
}
|
|
|
|
private static bool ImportAccounts(Dictionary<string, int> columnNumberOf) {
|
|
List<Account> accountsReadFromFile = new(1000);
|
|
|
|
while (!csvParser.EndOfData) {
|
|
bool dataHasError = false;
|
|
|
|
// 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[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["SAPAccountNumber"]];
|
|
if (errorRaiser == "") errorRaiser = "No Account Number in this row!";
|
|
|
|
DbHelper.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["SAPAccountNumber"]];
|
|
if (errorRaiser == "") errorRaiser = "No Account Number in this row!";
|
|
|
|
DbHelper.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["ZIP"]];
|
|
if (errorRaiser == "") errorRaiser = "No ZIP in this row!";
|
|
|
|
DbHelper.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["ZIP"]];
|
|
if (errorRaiser == "") errorRaiser = "No ZIP in this row!";
|
|
|
|
DbHelper.DisplayErrorDetails(ex, errorRaiser);
|
|
return false;
|
|
}
|
|
|
|
//Konvertierung für AccountCreatedinSAPOn Property:
|
|
//Test auf Inhalt bzw. Eintrag in der Mapping-Table (optionales Feld)
|
|
if (columnNumberOf.TryGetValue("AccountCreatedinSAPOn", out int columnNumber))
|
|
if (fields[columnNumberOf["AccountCreatedinSAPOn"]].Length != 0) {
|
|
int year = Convert.ToInt32(fields[columnNumber].Substring(0, 4));
|
|
int month = Convert.ToInt32(fields[columnNumber].Substring(4, 2));
|
|
int day = Convert.ToInt32(fields[columnNumber].Substring(6, 2));
|
|
importedAccount.AccountCreatedInSapOn = new(year, month, day);
|
|
}
|
|
|
|
//Convert City von Großschreibung zu Normalschreibung
|
|
importedAccount.City = fields[columnNumberOf["City"]].First() + 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["PhoneNumber"]];
|
|
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);
|
|
}
|
|
|
|
//Eingelesenen Account in DB schreiben:
|
|
|
|
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 = gremlinDb.AccountTypes.First(a => a.AccountTypeCode == account.AccountType.AccountTypeCode);
|
|
|
|
|
|
account.AccountType = accountType;
|
|
|
|
account.AccountType = DbHelper.ResolveAccountType(gremlinDb, account.AccountType.AccountTypeCode);
|
|
|
|
|
|
SubMarket subMarket = gremlinDb.SubMarkets.First(a => a.SubMarketCode == account.SubMarket.SubMarketCode);
|
|
|
|
|
|
account.SubMarket = subMarket;
|
|
|
|
account.SubMarket = DbHelper.ResolveSubmarket(gremlinDb, account.SubMarket.SubMarketCode);
|
|
|
|
|
|
_ = MetaDataSetter.ForImport(account, "GenericImporter-Method");
|
|
//account.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportAccountsFromCSV)";
|
|
}
|
|
|
|
|
|
gremlinDb.Accounts.AddRange(accountsReadFromFile);
|
|
|
|
_ = gremlinDb.SaveChanges();
|
|
|
|
//Bestätigung senden
|
|
Console.WriteLine($"Es wurden {accountsReadFromFile.Count} Accounts erfolgreich der Datenbank hinzugefügt.");
|
|
return true;
|
|
}
|
|
|
|
|
|
private static bool ImportProductLine(TextFieldParser csvParser, Dictionary<string, int> mappingTable) {
|
|
//foreach line in file:
|
|
//read seed data, parse/split, save to object with metadata
|
|
//add object to list
|
|
//add list to context, savechanges
|
|
|
|
List<ProductLine> productLinesReadFromFile = new(50);
|
|
using (csvParser) {
|
|
while (!csvParser.EndOfData) {
|
|
ProductLine importedProductLine = new();
|
|
string[] fields = csvParser.ReadFields()!;
|
|
importedProductLine.ProductLineCode = fields[mappingTable["ProductLineCode"]];
|
|
importedProductLine.ProductLineDescription = fields[mappingTable["ProductLineDescription"]];
|
|
productLinesReadFromFile.Add(importedProductLine);
|
|
_ = MetaDataSetter.ForImport(importedProductLine, "GenericImporter-Method");
|
|
}
|
|
}
|
|
|
|
|
|
gremlinDb.ProductLines.AddRange(productLinesReadFromFile);
|
|
|
|
_ = gremlinDb.SaveChanges();
|
|
return true;
|
|
}
|
|
|
|
private static bool ImportAccountType(TextFieldParser csvParser, Dictionary<string, int> mappingTable) {
|
|
//foreach line in file:
|
|
//read seed data, parse/split, save to object with metadata
|
|
//add object to list
|
|
//add list to context, savechanges
|
|
|
|
List<AccountType> accountTypesReadFromFile = new(20);
|
|
using (csvParser) {
|
|
while (!csvParser.EndOfData) {
|
|
AccountType importedAccountType = new();
|
|
string[] fields = csvParser.ReadFields()!;
|
|
importedAccountType.AccountTypeCode = fields[mappingTable["AccountTypeCode"]];
|
|
importedAccountType.AccountTypeDescription = fields[mappingTable["AccountTypeDescription"]];
|
|
accountTypesReadFromFile.Add(importedAccountType);
|
|
_ = MetaDataSetter.ForImport(importedAccountType, "GenericImporter-Method");
|
|
}
|
|
}
|
|
|
|
|
|
gremlinDb.AccountTypes.AddRange(accountTypesReadFromFile);
|
|
|
|
_ = gremlinDb.SaveChanges();
|
|
return true;
|
|
}
|
|
|
|
private static bool ImportSubMarket(TextFieldParser csvParser, Dictionary<string, int> mappingTable) {
|
|
//foreach line in file:
|
|
//read seed data, parse/split, save to object with metadata
|
|
//add object to list
|
|
//add list to context, savechanges
|
|
|
|
List<SubMarket> subMarketsReadFromFile = new(20);
|
|
using (csvParser) {
|
|
while (!csvParser.EndOfData) {
|
|
SubMarket importedSubMarket = new();
|
|
string[] fields = csvParser.ReadFields()!;
|
|
importedSubMarket.SubMarketCode = fields[mappingTable["SubMarketCode"]];
|
|
importedSubMarket.SubMarketDescription = fields[mappingTable["SubMarketDescription"]];
|
|
subMarketsReadFromFile.Add(importedSubMarket);
|
|
_ = MetaDataSetter.ForImport(importedSubMarket, "GenericImporter-Method");
|
|
}
|
|
}
|
|
|
|
|
|
gremlinDb.SubMarkets.AddRange(subMarketsReadFromFile);
|
|
|
|
_ = gremlinDb.SaveChanges();
|
|
return true;
|
|
}
|
|
|
|
//private static void SetProperty(object gremlinEntity, PropertyInfo propertyInfo, string value)
|
|
//{
|
|
// propertyInfo.SetValue(gremlinEntity, value);
|
|
//}
|
|
|
|
//private static Array GetNewGremlinTypeArray(Assembly assembly, string detectedDataType, int length)
|
|
//{
|
|
// Type GremlinType = GetNewGremlinType(assembly, detectedDataType).GetType();
|
|
// //Create an one-dimensional array with n+1 or n+2 elements (n = number of datasets, plus heading and/or emptyline at EOF):
|
|
// return Array.CreateInstance(GremlinType, length);
|
|
|
|
|
|
// //Das geht alles nicht:
|
|
// //List<GremlinType> test = new();
|
|
// //List<typeof(GremlinType)> test = new();
|
|
// //List<GremlinType.GetType()> test = new();
|
|
|
|
// ////Das geht:
|
|
// ////Create iList:
|
|
// //Type listType = typeof(List<>).MakeGenericType(GremlinType);
|
|
// //var list = (IList)Activator.CreateInstance(listType);
|
|
// //list.Add(new Account() { AccountName = "Test" });
|
|
// //list.Add(GremlinClassObject);
|
|
|
|
//}
|
|
|
|
//private static object GetNewGremlinType(Assembly Gremlin, string detectedDataType)
|
|
//{
|
|
// return Activator.CreateInstance(Gremlin.ToString(), "Gremlin." + detectedDataType).Unwrap();
|
|
//}
|
|
|
|
private static Dictionary<string, string> ReadMappingDictionaryFromFile() {
|
|
Dictionary<string, string> result = new();
|
|
string fileInput = FileService.ReadResource("MappingDictionary.txt");
|
|
string[] lines = fileInput.Split(Environment.NewLine);
|
|
foreach (string line in lines) {
|
|
string[] fields = line.Split("|");
|
|
result.Add(fields[0], fields[1]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static Dictionary<string, int> MapDataHeading(string[] headings, Dictionary<string, string> columnPropertyMapping) {
|
|
Dictionary<string, int> result = new();
|
|
for (int i = 0; i < headings.Length; i++) {
|
|
string heading = headings[i].ToLower(CultureInfo.CurrentCulture).Trim().Replace(" ", "").Replace("-", "").Replace("_", "").Replace(".", "").Replace(":", "");
|
|
if (columnPropertyMapping.TryGetValue(heading, out string? value)) result.Add(value, i);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//private static void BasimodosCodeDump()
|
|
//{
|
|
// //Unfertiger, generischer Code aus der ImportFile() Methode.
|
|
// //Aufbewahren für später, wenn Zeit ist, das ordentlich zu machen.
|
|
|
|
|
|
// //while (!csvParser.EndOfData)
|
|
// //{
|
|
// // //read
|
|
// // //new data instance
|
|
// // //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.
|
|
|
|
|
|
// // //TO DO: vor der foreach "Account" bevorzugt behandeln (Sonderfall bei LSAG: Liste deduplizieren, und als erstes importieren, da sonst keine Contacts importiert werden können).
|
|
|
|
|
|
// // foreach (string detectedDataType in detectedDataTypes)
|
|
// // {
|
|
// // //"Account" überspringen, da das vor der foreach schon verarbeitet worden ist.
|
|
// // if (detectedDataType == "Account") continue;
|
|
|
|
// // Array GremlinTypeArray = GetNewGremlinTypeArray(Gremlin, detectedDataType, numberOfLines);
|
|
// // Type GremlinType = GetNewGremlinType(Gremlin, detectedDataType).GetType();
|
|
// // PropertyInfo[] GremlinTypeProperties = GremlinType.GetProperties();
|
|
|
|
// // foreach (PropertyInfo property in GremlinTypeProperties)
|
|
// // {
|
|
// // if (mappingTable.TryGetValue(property.Name, out int columnNumber))
|
|
// // {
|
|
// // //convert:
|
|
// // GremlinTypeConverter.Convert(fields[columnNumber]);
|
|
// // }
|
|
// // }
|
|
// // }
|
|
|
|
// //}
|
|
|
|
// //using (GremlinContext db = new())
|
|
// //{
|
|
// // //add to context
|
|
// // //savechanges
|
|
// //}
|
|
//}
|
|
|
|
|
|
//#########################################################################################
|
|
//Kann gelöscht werden, sobald die generische Funktion zuverlässig funktioniert:
|
|
//#########################################################################################
|
|
//private static string RecognizeDataStatic(Dictionary<string, int> mappingDictionary)
|
|
//{
|
|
// //Logik zur Kategorisierung des Datensatzes:
|
|
// //Alle verpflichtenden Angaben zu einer Klasse vorhanen?
|
|
// //Alle vom Importer erwarteten Angaben vorhanden?
|
|
// //Bei den Enums zusätzlich noch proüfem, dass nur zwei Spalten vorhanden sind, sonst könnten LSAG-Daten falsch identifiziert werden.
|
|
// //
|
|
// //
|
|
|
|
// //Products
|
|
// if (//required by DB:
|
|
// mappingDictionary.ContainsKey("ProductNumber")
|
|
// && mappingDictionary.ContainsKey("ListPrice")
|
|
// //required by importer:
|
|
// && mappingDictionary.ContainsKey("Weight")
|
|
// //unique identifier:
|
|
// && mappingDictionary.ContainsKey("BreakRangeFrom")
|
|
// )
|
|
// {
|
|
// return "Product";
|
|
// }
|
|
|
|
// //LSAG Contact List Tool List
|
|
// if (//required by DB:
|
|
// mappingDictionary.ContainsKey("SAPAccountNumber")
|
|
// && mappingDictionary.ContainsKey("SAPContactNumber")
|
|
// && mappingDictionary.ContainsKey("AccountTypeCode")
|
|
// && mappingDictionary.ContainsKey("SubMarketCode")
|
|
// && mappingDictionary.ContainsKey("LastName")
|
|
// && mappingDictionary.ContainsKey("AccountName")
|
|
// //required by importer:
|
|
|
|
// //unique identifier:
|
|
// && mappingDictionary.ContainsKey("MA_ProductInterests")
|
|
// )
|
|
// {
|
|
// return "LSAG Contact List Tool List";
|
|
// }
|
|
|
|
// //Accounts
|
|
// if (//required by DB:
|
|
// mappingDictionary.ContainsKey("AccountName")
|
|
// && mappingDictionary.ContainsKey("Street")
|
|
// && mappingDictionary.ContainsKey("ZIP")
|
|
// && mappingDictionary.ContainsKey("City")
|
|
// && mappingDictionary.ContainsKey("PhoneNumber")
|
|
// && mappingDictionary.ContainsKey("SAPAccountNumber")
|
|
// && mappingDictionary.ContainsKey("AccountCreatedInSAPOn")
|
|
// && mappingDictionary.ContainsKey("AccountTypeCode")
|
|
// && mappingDictionary.ContainsKey("SubMarketCode")
|
|
// //required by importer:
|
|
// )
|
|
// {
|
|
// return "Account";
|
|
// }
|
|
|
|
// //Contacts
|
|
// if (//required by DB:
|
|
// mappingDictionary.ContainsKey("LastName")
|
|
// && mappingDictionary.ContainsKey("SAPContactNumber")
|
|
// //required by importer:
|
|
// && mappingDictionary.ContainsKey("FirstName")
|
|
// && mappingDictionary.ContainsKey("Gender")
|
|
// )
|
|
// {
|
|
// return "Contact";
|
|
// }
|
|
|
|
// //Custom Description
|
|
// if (//required by DB:
|
|
// mappingDictionary.ContainsKey("Heading")
|
|
// //required by importer:
|
|
// && mappingDictionary.ContainsKey("ProductNumber")
|
|
// && mappingDictionary.ContainsKey("OptionNumber")
|
|
// && mappingDictionary.ContainsKey("DescriptionText")
|
|
// //unique identifier:
|
|
// && mappingDictionary.ContainsKey("CoverletterText")
|
|
// )
|
|
// {
|
|
// return "CustomDescription";
|
|
// }
|
|
|
|
|
|
// //Pseudo-Enums AccountTypes, SubMarkets, ProductLines for DB-initializing
|
|
// if (mappingDictionary.Count == 2)
|
|
// {
|
|
// if (mappingDictionary.ContainsKey("AccountTypeCode")
|
|
// && mappingDictionary.ContainsKey("AccountTypeDescription")
|
|
// && mappingDictionary.Count == 2)
|
|
// {
|
|
// return "AccountType";
|
|
// }
|
|
|
|
// if (mappingDictionary.ContainsKey("SubMarketCode")
|
|
// && mappingDictionary.ContainsKey("SubMarketDescription")
|
|
// && mappingDictionary.Count == 2)
|
|
// {
|
|
// return "SubMarket";
|
|
// }
|
|
|
|
// if (mappingDictionary.ContainsKey("ProductLineCode")
|
|
// && mappingDictionary.ContainsKey("ProductLineDescription")
|
|
// && mappingDictionary.Count == 2)
|
|
// {
|
|
// return "ProductLine";
|
|
// }
|
|
// }
|
|
|
|
// return "No entity type unambigiously identified!";
|
|
|
|
//}
|
|
} |