Gremlin/Gremlin_BlazorServer/Services/GenericImporter.cs

275 lines
16 KiB
C#

using Gremlin_BlazorServer.Data.EntityClasses;
namespace Gremlin_BlazorServer.Services;
public class GenericImporter {
public static async void ImportCsv<T>(string fileContent) where T : class, IMetadata, new() {
Console.WriteLine($"GENERIC IMPORTER: Importing {typeof(T)} from csv...");
List<string[]> splitLines = SplitLines<T>(fileContent);
Console.WriteLine($"Found {splitLines.Count} potential {typeof(T)} in csv.");
int result = await ParseLinesToResultSet<T>(splitLines);
if (result == -1) return; //no updates or new items
Console.WriteLine(result > 0 ? $"GENERIC IMPORTER: wrote {result} to db." : "GENERIC IMPORTER: Error by writing to db!");
}
private static List<string[]> SplitLines<T>(string fileContent) where T : class, IMetadata, new() {
IEnumerable<string> fileLines = fileContent.Split(Environment.NewLine.ToCharArray());
char seperator = '\t';
if (typeof(T) == typeof(CustomDescription)) seperator = '|';
List<string[]> fileList = (from clipboardLine in fileLines where clipboardLine != "" select clipboardLine.Split(seperator)).ToList();
return fileList;
}
private static void DrawTextProgressBar(int progress, int total)
{
//draw empty progress bar
Console.CursorLeft = 0;
Console.Write("["); //start
Console.CursorLeft = 64;
Console.Write("]"); //end
Console.CursorLeft = 1;
float onechunk = 62.0f / total;
//draw filled part
int position = 1;
for (int i = 0; i < onechunk * progress; i++)
{
Console.BackgroundColor = ConsoleColor.Green;
Console.CursorLeft = position++;
Console.Write(" ");
}
//draw unfilled part
for (int i = position; i <= 63 ; i++)
{
Console.BackgroundColor = ConsoleColor.Gray;
Console.CursorLeft = position++;
Console.Write(" ");
}
//draw totals
Console.CursorLeft = 66;
Console.BackgroundColor = ConsoleColor.Black;
int percent = (int)MathF.Round((float)progress / total * 100, 0);
Console.Write($"{progress} of {total} [{percent}%] "); //blanks at the end remove any excess
}
private static async Task<int> ParseLinesToResultSet<T>(IEnumerable<string[]> lineList) where T : class, IMetadata, new() {
Tuple<T?, T?>? resultSet = new(new(), new());
List<T> updatedItems = new();
List<T> newItems = new();
IList<T> allItems = await GenericController.GetAllAsync<T>() ?? new List<T>();
List<string[]> lines = lineList.Select(strings => strings.Select(x => x.Replace("\"", string.Empty)).ToArray()).ToList();
for (int i = 0; i < lines.Count; i++) {
if (typeof(T) == typeof(Account)) resultSet = ParseToAccount(lines[i], allItems as IList<Account>) as Tuple<T?, T?>;
if (typeof(T) == typeof(Contact)) resultSet = ParseToContact(lines[i], allItems as IList<Contact>) as Tuple<T?, T?>;
if (typeof(T) == typeof(Product)) resultSet = ParseToProduct(lines[i], allItems as IList<Product>) as Tuple<T?, T?>;
if (typeof(T) == typeof(CustomDescription)) resultSet = ParseToCustomDescription(lines[i], allItems as IList<CustomDescription>) as Tuple<T?, T?>;
if (resultSet is null) continue;
if (resultSet.Item1 is not null) updatedItems.Add(resultSet.Item1);
if (resultSet.Item2 is not null) newItems.Add(resultSet.Item2);
DrawTextProgressBar(i, lines.Count);
}
int success = 0;
Console.WriteLine();
Console.WriteLine($"Found {updatedItems.Count} updates and {newItems.Count} new items.");
if (updatedItems.Count > 0) success += GenericController.Update(updatedItems);
if (newItems.Count > 0) success += GenericController.Insert(newItems);
if (updatedItems.Count == 0 && newItems.Count == 0) return -1;
return success;
}
private static Tuple<Account?, Account?>? ParseToAccount(IReadOnlyList<string> line, IEnumerable<Account> allAccounts) {
// "ID" "Name" "Street" "City" "BP Role" "Postal Code" "Customer Type" "Market Indicator" "Phone" "E-Mail" "Market code"
Account? updatedAccount = null;
Account? newAccount = null;
if (line[0].Contains("ID")) return null; //HACK: skip first row if header
if (!uint.TryParse(line[0], out uint sapAccountNumber)) return null; //HACK: skip wrong SapAccountNumbers
if (!uint.TryParse(line[5], out uint zip)) return null; //HACK: skip wrong ZIPs
string accountTypeCode = line[6];
if (accountTypeCode is "" or null) accountTypeCode = "FPC"; // standard AccountType
string subMarketCode = line[7];
if (subMarketCode is "" or null) subMarketCode = "CCH"; // standard AccountType
Account readAccount = new(){
SapAccountNumber = sapAccountNumber,
AccountName = line[1], //System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(line[1].ToLower())
Street = line[2],
City = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(line[3].ToLower()), //line[3].ToUpper().First() + line[3][1..].ToLower(),
Zip = zip,
PhoneNumber = line[8],
EMail = line[9],
DataModificationByUser = "Gremlin Generic Importer",
AccountTypeCode = accountTypeCode,
SubMarketCode = subMarketCode
};
Account? existingAccount = allAccounts.FirstOrDefault(a => a.SapAccountNumber == sapAccountNumber || a.AccountName == readAccount.AccountName);
if (existingAccount is not null) {
if (existingAccount.Equals(readAccount)) return null;
existingAccount.DataModificationDate = DateTime.Now;
existingAccount.DataVersionNumber++;
existingAccount.DataModificationByUser = "Updated by Gremlin Generic Importer";
if (existingAccount.AccountName != readAccount.AccountName) existingAccount.AccountName = readAccount.AccountName;
if (existingAccount.City != readAccount.City) existingAccount.City = readAccount.City;
if (existingAccount.EMail != readAccount.EMail) existingAccount.EMail = readAccount.EMail;
if (existingAccount.PhoneNumber != readAccount.PhoneNumber) existingAccount.PhoneNumber = readAccount.PhoneNumber;
if (existingAccount.Street != readAccount.Street) existingAccount.Street = readAccount.Street;
if (existingAccount.Zip != readAccount.Zip) existingAccount.Zip = readAccount.Zip;
// Console.WriteLine($"Update in Contact {existingAccount.SapAccountNumber}:{existingAccount.AccountName}");
updatedAccount = existingAccount;
}
else {
// Console.WriteLine($"Account {readAccount.SapAccountNumber}:{readAccount.AccountName} ist neu!");
newAccount = readAccount;
}
return new(updatedAccount, newAccount);
}
private static Tuple<Contact?, Contact?>? ParseToContact(IReadOnlyList<string> line, IEnumerable<Contact> allContacts) {
//"Contact ID" "Account ID" "Last Name" "First Name" "Acct Name 1 and 2" "Street - Work Address" "Postal Code - Work Address" "City - Work Address" "E-Mail" "Phone" "E-Mail Opt"
Contact? updatedContact = null;
Contact? newContact = null;
if (line[0].Contains("ID")) return null; //HACK: skip first row if header
if (!uint.TryParse(line[0], out uint sapContactNumber)) return null; //HACK: skip wrong SapContactNumbers
if (!uint.TryParse(line[1], out uint sapAccountNumber)) return null; //HACK: skip wrong SapAccountNumbers
Account? account = GenericController.Get<Account>(a => a.SapAccountNumber.Equals(sapAccountNumber));
if (account is null)
// Console.WriteLine($"Account with SapAccountNumber {sapAccountNumber} is not existing!!");
return null; //HACK: skip empty Accounts
Contact readContact = new(){
SapContactNumber = sapContactNumber,
AccountId = account.AccountId,
LastName = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(line[2].ToLower()),
FirstName = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(line[3].ToLower()),
EMail = line[8].ToLower(),
EmailBounced = line[8].Contains("bounced"),
PhoneNumber = line[9],
OptInStatus = line[10] == "Opt In",
DataModificationByUser = "Gremlin Generic Importer",
Gender = 0
};
Contact? existingContact = allContacts.FirstOrDefault(a => a.SapContactNumber == sapContactNumber || (a.LastName == readContact.LastName && a.FirstName == readContact.FirstName));
if (existingContact is not null) {
if (existingContact.Equals(readContact)) return null;
existingContact.DataModificationDate = DateTime.Now;
existingContact.DataVersionNumber++;
existingContact.DataModificationByUser = "Updated by Gremlin Generic Importer";
existingContact.AccountId = readContact.AccountId;
existingContact.LastName = readContact.LastName;
existingContact.EMail = readContact.EMail;
existingContact.PhoneNumber = readContact.PhoneNumber;
existingContact.EmailBounced = readContact.EmailBounced;
existingContact.OptInStatus = readContact.OptInStatus;
// Console.WriteLine($"Update in Contact {existingContact.SapContactNumber}:{existingContact.FirstName} {existingContact.LastName}");
updatedContact = existingContact;
}
else {
// Console.WriteLine($"Contact {readContact.SapContactNumber}:{readContact.FirstName} {readContact.LastName} ist neu!");
newContact = readContact;
}
return new(updatedContact, newContact);
}
private static Tuple<Product?, Product?>? ParseToProduct(IReadOnlyList<string> line, IEnumerable<Product> allProducts) {
//Position Partnumber Option Description Current Month Price(EUR) Previous Month Price(EUR) Breaks Range From Breaks Range To Warranty Productline PH Code PH Description Status Air Packaged Unit Air Packaged Weight Country of Manufacturing ECCL M41 First Supplier code Harmonized Tarif Schedule Hazardous Goods Flag Order Instructions Introduction Date End of Production Date End of Support Date Long Description
Product? updatedProduct = null;
Product? newProduct = null;
if (line[0].Contains("Position")) return null; //HACK: skip first row if header
if (!decimal.TryParse(line[4], out decimal listPrice)) return null; //ListPrice not convertable
if (!int.TryParse(line[6], out int breakRangeFrom)) breakRangeFrom = 0; //no BreakRangeFrom
if (!int.TryParse(line[7], out int breakRangeTo)) breakRangeTo = 0; //no BreakRangeTo
if (!float.TryParse(line[13], out float weight)) weight = 0; //no Weight
Product readProduct = new(){
ProductNumber = line[1],
OptionNumber = line[2].Replace("'", ""),
SapShortDescription = line[3],
ListPrice = listPrice,
ProductLineCode = line[9],
SapLongDescription = line[25],
BreakRangeFrom = breakRangeFrom,
BreakRangeTo = breakRangeTo,
Weight = weight,
DataModificationByUser = "Gremlin Generic Importer",
};
Product? existingProduct = allProducts.FirstOrDefault(p => p.ProductNumber.Equals(readProduct.ProductNumber) && p.OptionNumber.Equals(readProduct.OptionNumber));
if (existingProduct is not null) {
if (existingProduct.Equals(readProduct)) return null;
existingProduct.DataModificationDate = DateTime.Now;
existingProduct.DataVersionNumber++;
existingProduct.DataModificationByUser = "Updated by Gremlin Generic Importer";
existingProduct.SapShortDescription = readProduct.SapShortDescription;
existingProduct.ListPrice = readProduct.ListPrice;
existingProduct.ProductLine = readProduct.ProductLine;
existingProduct.SapLongDescription = readProduct.SapLongDescription;
existingProduct.BreakRangeFrom = readProduct.BreakRangeFrom;
existingProduct.BreakRangeTo = readProduct.BreakRangeTo;
existingProduct.HasBreakPrices = existingProduct.BreakRangeFrom != 0 || existingProduct.BreakRangeTo != 0;
existingProduct.Weight = readProduct.Weight;
// Console.WriteLine($"Update in Product {existingProduct.ProductNumber}#{existingProduct.OptionNumber}:{existingProduct.SapShortDescription}");
updatedProduct = existingProduct;
}
else {
// Console.WriteLine($"Product {readProduct.ProductNumber}#{readProduct.OptionNumber}:{readProduct.SapShortDescription} ist neu!");
newProduct = readProduct;
}
return new(updatedProduct, newProduct);
}
private static Tuple<CustomDescription?, CustomDescription?>? ParseToCustomDescription(IReadOnlyList<string> line, IEnumerable<CustomDescription> allCustomDescriptions) {
//Produktnummer|Option|Non Agilent Produkt|Überschrift|Produktbeschreibung|Anschreiben|Rang|Originalbeschreibung|Veraltete_Originalbeschreibung
CustomDescription? updatedCustomDescription = null;
CustomDescription? newCustomDescription = null;
if (line[0].Contains("Produktnummer")) return null; //HACK: skip first row if header
CustomDescription readCustomDescription = new(){
ProductNumber = line[0],
OptionNumber = line[1],
Heading = line[3],
DescriptionText = line[4],
CoverletterText = line[5],
DataModificationByUser = "Gremlin Generic Importer",
};
if (readCustomDescription.ProductNumber == "") return null; //Skip empty lines
CustomDescription? existingCustomDescription = allCustomDescriptions.FirstOrDefault(p => p.ProductNumber.Equals(readCustomDescription.ProductNumber) && p.OptionNumber.Equals(readCustomDescription.OptionNumber));
if (existingCustomDescription is not null) {
if (existingCustomDescription.Equals(readCustomDescription)) return null;
existingCustomDescription.DataModificationDate = DateTime.Now;
existingCustomDescription.DataVersionNumber++;
existingCustomDescription.DataModificationByUser = "Updated by Gremlin Generic Importer";
existingCustomDescription.ProductNumber = readCustomDescription.ProductNumber;
existingCustomDescription.OptionNumber = readCustomDescription.OptionNumber;
existingCustomDescription.Heading = readCustomDescription.Heading;
existingCustomDescription.DescriptionText = readCustomDescription.DescriptionText;
existingCustomDescription.CoverletterText = readCustomDescription.CoverletterText;
existingCustomDescription.Notes = readCustomDescription.Notes;
// Console.WriteLine($"Update in CustomDescription {existingCustomDescription.ProductNumber}#{existingCustomDescription.OptionNumber}:{existingCustomDescription.Heading}");
updatedCustomDescription = existingCustomDescription;
}
else {
// Console.WriteLine($"Product {readCustomDescription.ProductNumber}#{readCustomDescription.OptionNumber}:{readCustomDescription.Heading} ist neu!");
newCustomDescription = readCustomDescription;
}
return new(updatedCustomDescription, newCustomDescription);
}
}