275 lines
16 KiB
C#
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);
|
|
}
|
|
} |