diff --git a/src/bnhtrade.ComTypeLib/Account/Account.cs b/src/bnhtrade.ComTypeLib/Account/Account.cs index 491ac29..bf784bc 100644 --- a/src/bnhtrade.ComTypeLib/Account/Account.cs +++ b/src/bnhtrade.ComTypeLib/Account/Account.cs @@ -48,7 +48,7 @@ namespace bnhtrade.ComTypeLib public decimal CurrencyConvertToGbp(ConnectionCredential sqlConnCred, string currencyCode, [MarshalAs(UnmanagedType.Currency)] decimal amount, DateTime conversionDate) { - return new Core.Logic.Account.Currency().CurrencyConvertToGbp(currencyCode, amount, conversionDate); + return new Core.Logic.Account.CurrencyService().CurrencyConvertToGbp(currencyCode, amount, conversionDate); } public int CurrencyExchangeRateInsert(ConnectionCredential sqlConnCred, int exchangeRateSource, string currencyCode, diff --git a/src/bnhtrade.Core/Data/Amazon/Feeds/SampleFeeds.cs b/src/bnhtrade.Core/Data/Amazon/Feeds/SampleFeeds.cs index ec09606..c3f3de1 100644 --- a/src/bnhtrade.Core/Data/Amazon/Feeds/SampleFeeds.cs +++ b/src/bnhtrade.Core/Data/Amazon/Feeds/SampleFeeds.cs @@ -163,7 +163,7 @@ namespace bnhtrade.Core.Data.Amazon.Feeds GetFeedDetails(feedID); } - public void SubmitFeedPRICING(double PRICE, string SKU) + public void SubmitFeedPRICING(decimal price, string SKU) { ConstructFeedService createDocument = new ConstructFeedService(amazonConnection.GetCurrentSellerID, "1.02"); @@ -175,7 +175,7 @@ namespace bnhtrade.Core.Data.Amazon.Feeds StandardPrice = new StandardPrice() { currency = amazonConnection.GetCurrentMarketplace.CurrencyCode.ToString(), - Value = (PRICE).ToString("0.00") + Value = price } }); createDocument.AddPriceMessage(list); @@ -201,14 +201,14 @@ namespace bnhtrade.Core.Data.Amazon.Feeds StandardPrice = new StandardPrice { currency = currencyCode, - Value = price.ToString("0.00") + Value = price }, Sale = new Sale { SalePrice = new StandardPrice { currency = currencyCode, - Value = salePrice.ToString("0.00") + Value = salePrice }, StartDate = startDate.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fffK"), EndDate = endDate.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fffK") @@ -224,7 +224,7 @@ namespace bnhtrade.Core.Data.Amazon.Feeds } - public void SubmitFeedSale(double PRICE, string SKU) + public void SubmitFeedSale(decimal price, string SKU) { ConstructFeedService createDocument = new ConstructFeedService("A3J37AJU4O9RHK", "1.02"); @@ -236,7 +236,7 @@ namespace bnhtrade.Core.Data.Amazon.Feeds StandardPrice = new StandardPrice() { currency = amazonConnection.GetCurrentMarketplace.CurrencyCode.ToString(), - Value = (PRICE).ToString("0.00") + Value = price }, Sale = new Sale() { @@ -245,7 +245,7 @@ namespace bnhtrade.Core.Data.Amazon.Feeds SalePrice = new StandardPrice() { currency = amazonConnection.GetCurrentMarketplace.CurrencyCode.ToString(), - Value = (PRICE - 10).ToString("0.00") + Value = price } } }); diff --git a/src/bnhtrade.Core/Data/Amazon/Report/SettlementReport.cs b/src/bnhtrade.Core/Data/Amazon/Report/SettlementReport.cs index 5fc4edb..bbe7e9e 100644 --- a/src/bnhtrade.Core/Data/Amazon/Report/SettlementReport.cs +++ b/src/bnhtrade.Core/Data/Amazon/Report/SettlementReport.cs @@ -32,7 +32,7 @@ namespace bnhtrade.Core.Data.Amazon.Report reportIdList.Add(report.ReportId); } - UI.Console.WriteLine("{0} Settlement reports avaible on Amazon SP-API"); + UI.Console.WriteLine(reportIdList.Count().ToString() + " Settlement reports avaible on Amazon SP-API"); return reportIdList; } diff --git a/src/bnhtrade.Core/Data/Database/Account/CreateJournal.cs b/src/bnhtrade.Core/Data/Database/Account/CreateJournal.cs index a149dbf..bfbb086 100644 --- a/src/bnhtrade.Core/Data/Database/Account/CreateJournal.cs +++ b/src/bnhtrade.Core/Data/Database/Account/CreateJournal.cs @@ -162,7 +162,7 @@ namespace bnhtrade.Core.Data.Database.Account // currency conversion if (currencyCode != "GBP") { - amount = new Logic.Account.Currency().CurrencyConvertToGbp(currencyCode, amount, entryDate); + amount = new Logic.Account.CurrencyService().CurrencyConvertToGbp(currencyCode, amount, entryDate); } // ensure decimal is rounded diff --git a/src/bnhtrade.Core/Data/Database/Account/Currency.cs b/src/bnhtrade.Core/Data/Database/Account/Currency.cs deleted file mode 100644 index f2ed460..0000000 --- a/src/bnhtrade.Core/Data/Database/Account/Currency.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.Data.SqlClient; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Collections; -using FikaAmazonAPI.AmazonSpApiSDK.Models.FulfillmentOutbound; -using System.Data; - -namespace bnhtrade.Core.Data.Database.Account -{ - internal class Currency : Connection - { - /// - /// Returns excahnge rate, in decimal format, for a given currency and datetime - /// - /// currency code - /// dat and time - /// - public decimal? ReadExchangeRate(string currencyCode, DateTime date) - { - using (SqlConnection sqlConn = new SqlConnection(SqlConnectionString)) - { - sqlConn.Open(); - - using (SqlCommand cmd = new SqlCommand(@" - SELECT CurrencyUnitsPerGBP - FROM tblAccountExchangeRate - WHERE CurrencyCode=@currencyCode AND StartDate<=@conversionDate AND EndDate>@conversionDate - ", sqlConn)) - { - cmd.Parameters.AddWithValue("@currencyCode", currencyCode); - cmd.Parameters.AddWithValue("@conversionDate", date); - - object result = cmd.ExecuteScalar(); - if (result != null) - { - return Convert.ToDecimal(result); - } - else - { - return null; - } - } - } - } - - public List ReadExchangeRate(List currencyCodeList = null, DateTime date = default(DateTime)) - { - throw new NotImplementedException("Complete, but untested"); - - var returnList = new List(); - - string sql = @" - SELECT AccountEchangeRateID, ExchangeRateSource, CurrencyCode, CurrencyUnitsPerGBP, StartDate, EndDate - FROM tblAccountExchangeRate - WHERE 1=1 "; - - if (date != default(DateTime)) - { - sql = sql + " AND (@dateTime >= StartDate AND @dateTime < endDate) "; - } - - var sqlWhere = new Data.Database.SqlWhereBuilder(); - - // create string list - List stringList = new List(); - if (currencyCodeList != null) - { - stringList = currencyCodeList.ConvertAll(f => f.ToString()); - if (stringList.Any()) - { - sqlWhere.In("CurrencyCode", stringList, "AND"); - } - } - - sql = sql + sqlWhere.SqlWhereString; - - using (SqlConnection sqlConn = new SqlConnection(SqlConnectionString)) - { - sqlConn.Open(); - - using (SqlCommand cmd = new SqlCommand(sql)) - { - if (date != default(DateTime)) - { - cmd.Parameters.AddWithValue("@dateTime", date); - } - if (stringList.Any()) - { - sqlWhere.AddParametersToSqlCommand(cmd); - } - - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - int id = reader.GetInt32(0); - int exchangeRateSource = reader.GetInt32(1); - string currencyCodeString = reader.GetString(2); - decimal currencyUnitsPerGBP = reader.GetDecimal(3); - DateTime startDate = DateTime.SpecifyKind( reader.GetDateTime(4), DateTimeKind.Utc); - DateTime endDate = DateTime.SpecifyKind(reader.GetDateTime(5), DateTimeKind.Utc); - - - // convert string to enum - if (Enum.TryParse(currencyCodeString, out Model.Account.CurrencyCode currencyCode) == false) - { - throw new Exception("Failed converting database string to enum"); - } - - var item = new Model.Account.CurrencyExchangeRate( - currencyCode - , exchangeRateSource - , startDate - , endDate - ); - - returnList.Add(item); - } - } - } - } - return returnList; - } - - public List ReadExchangeRateLatest(List currencyCodeList = null) - { - var returnList = new List(); - - string sql = @" - SELECT t1.AccountExchangeRateID, t1.ExchangeRateSource, t1.CurrencyCode, t1.CurrencyUnitsPerGBP, t1.StartDate, t1.EndDate, - t2.maxdate - FROM tblAccountExchangeRate AS t1 - INNER JOIN - (SELECT max(StartDate) AS maxdate, - CurrencyCode - FROM tblAccountExchangeRate - GROUP BY CurrencyCode - "; - - // add any filters - var sqlWhere = new Data.Database.SqlWhereBuilder(); - var codeStringList = new List(); - if (currencyCodeList != null && currencyCodeList.Any() == true) - { - // convert to string list - foreach ( var currencyCode in currencyCodeList) - { - codeStringList.Add(currencyCode.ToString()); - } - - // add to where statement - sqlWhere.In("CurrencyCode", codeStringList, "HAVING"); - sql = sql + sqlWhere.SqlWhereString; - } - - sql = sql + @" ) AS t2 - ON t1.CurrencyCode = t2.CurrencyCode - AND t1.StartDate = t2.maxdate - ORDER BY t1.CurrencyCode;"; - - //query db - using (SqlConnection sqlConn = new SqlConnection(SqlConnectionString)) - { - sqlConn.Open(); - - using (SqlCommand cmd = new SqlCommand(sql, sqlConn)) - { - if (codeStringList.Any()) - { - sqlWhere.AddParametersToSqlCommand(cmd); - } - - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - int id = reader.GetInt32(0); - int exchangeRateSource = reader.GetInt32(1); - string currencyCodeString = reader.GetString(2); - decimal currencyUnitsPerGBP = reader.GetDecimal(3); - DateTime startDate = DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc); - DateTime endDate = DateTime.SpecifyKind(reader.GetDateTime(5), DateTimeKind.Utc); - - - // convert string to enum - if (Enum.TryParse(currencyCodeString, out Model.Account.CurrencyCode currencyCode) == false) - { - throw new Exception("Failed converting database string to enum"); - } - - var item = new Model.Account.CurrencyExchangeRate( - currencyCode - , exchangeRateSource - , startDate - , endDate - ); - - returnList.Add(item); - } - } - } - } - return returnList; - } - - public int InsertExchangeRate(int exchangeRateSource, Model.Account.CurrencyCode currencyCode, - decimal currencyUnitsPerGbp, DateTime periodStartUtc, DateTime periodEnd, bool checkOverride = false) - { - // checks - if (periodStartUtc.Kind != DateTimeKind.Utc || periodEnd.Kind != DateTimeKind.Utc) - { - throw new FormatException("Currency date time kind must be UTC"); - } - - currencyUnitsPerGbp = decimal.Round(currencyUnitsPerGbp, 4); - - // CHECKS - if (periodEnd <= periodStartUtc) - { - throw new Exception("Invalid date period."); - } - - if (checkOverride == false && (periodEnd - periodStartUtc).Days > 31) - { - throw new Exception("Date period is greater than 31 days."); - } - - // make the insert - DateTime? periodEndLast = null; - using (SqlConnection sqlConn = new SqlConnection(SqlConnectionString)) - { - sqlConn.Open(); - - int recordId = 0; - using (SqlCommand cmd = new SqlCommand(@" - INSERT INTO tblAccountExchangeRate (ExchangeRateSource, CurrencyCode, CurrencyUnitsPerGBP, StartDate, EndDate) - OUTPUT INSERTED.AccountExchangeRateID - VALUES (@exchangeRateSource, @currencyCode, @currencyUnitsPerGbp, @periodStart, @periodEnd); - ", sqlConn)) - { - cmd.Parameters.AddWithValue("@exchangeRateSource", exchangeRateSource); - cmd.Parameters.AddWithValue("@currencyCode", currencyCode.ToString()); - cmd.Parameters.AddWithValue("@currencyUnitsPerGbp", currencyUnitsPerGbp); - cmd.Parameters.AddWithValue("@periodStart", periodStartUtc); - cmd.Parameters.AddWithValue("@periodEnd", periodEnd); - - recordId = (int)cmd.ExecuteScalar(); - - if (recordId < 1) - { - throw new Exception("Error inserting record, did not retrive new record ID."); - } - } - - return recordId; - } - } - } -} diff --git a/src/bnhtrade.Core/Data/Database/Account/ReadAccountCode.cs b/src/bnhtrade.Core/Data/Database/Account/ReadAccountCode.cs index ba238a7..4d808f3 100644 --- a/src/bnhtrade.Core/Data/Database/Account/ReadAccountCode.cs +++ b/src/bnhtrade.Core/Data/Database/Account/ReadAccountCode.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; diff --git a/src/bnhtrade.Core/Data/Database/Account/ReadInvoiceLineItem.cs b/src/bnhtrade.Core/Data/Database/Account/ReadInvoiceLineItem.cs index 07482f0..9164335 100644 --- a/src/bnhtrade.Core/Data/Database/Account/ReadInvoiceLineItem.cs +++ b/src/bnhtrade.Core/Data/Database/Account/ReadInvoiceLineItem.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,63 +8,61 @@ using System.Transactions; namespace bnhtrade.Core.Data.Database.Account { - public class ReadInvoiceLineItem : Connection + internal class ReadInvoiceLineItem : Connection { public ReadInvoiceLineItem() { - Innit(); } - public Dictionary AccountCodeList { get; private set; } - - public Dictionary TaxCodeList { get; private set; } - - private void Innit() + /// + /// Reads all invoice line items from the database. + /// + /// dictionary where key=id, value=object + internal Dictionary All() { - AccountCodeList = new Dictionary(); - TaxCodeList = new Dictionary(); + return Execute(); } - public Model.Account.InvoiceLineItem ByItemCode(string itemCode) + /// + /// Read list of invoice line items by item code. + /// + /// List of item coeds to query db against + /// dictionary where key=id, value=object + /// + internal Dictionary ByItemCode(List itemCodes) { - if (string.IsNullOrWhiteSpace(itemCode)) { return null; } - - var result = ByItemCode(new List{ itemCode }); - - if (!result.Any()) - { - return null; - } - else - { - return result[itemCode]; + if (itemCodes == null || !itemCodes.Any()) + { + throw new ArgumentException("Item codes list cannot be null or empty."); } + + var sqlwhere = new SqlWhereBuilder(); + sqlwhere.In("ItemCode", itemCodes, "AND"); + return Execute(sqlwhere); } - public Dictionary ByItemCode(List itemCodeList) + private Dictionary Execute(SqlWhereBuilder sqlwhere = null) { - Innit(); - var resultList = new Dictionary(); - - if (itemCodeList == null || !itemCodeList.Any()) - { return resultList; } + var resultList = new Dictionary(); + var accountCodeIdList = new Dictionary(); // key=LineItemID, value=AccountChartOfID + var taxCodeIdList = new Dictionary(); // key=LineItemID, value=AccountTaxCodeID string sql = @" - SELECT tblAccountInvoiceLineItem.ItemCode - ,tblAccountInvoiceLineItem.ItemName - ,tblAccountInvoiceLineItem.ItemDescription - ,tblAccountInvoiceLineItem.IsNewReviewRequired - ,tblAccountInvoiceLineItem.InvoiceLineEntryEnable - ,tblAccountChartOf.AccountCode - ,tblAccountTaxCode.TaxCode - FROM tblAccountInvoiceLineItem - LEFT OUTER JOIN tblAccountTaxCode ON tblAccountInvoiceLineItem.AccountTaxCodeID_Default = tblAccountTaxCode.AccountTaxCodeID - LEFT OUTER JOIN tblAccountChartOf ON tblAccountInvoiceLineItem.AccountChartOfID_Default = tblAccountChartOf.AccountChartOfID - WHERE "; + SELECT [AccountInvoiceLineItemID] + ,[ItemName] + ,[ItemCode] + ,[ItemDescription] + ,[IsNewReviewRequired] + ,[InvoiceLineEntryEnable] + ,[AccountChartOfID_Default] + ,[AccountTaxCodeID_Default] + FROM [e2A].[dbo].[tblAccountInvoiceLineItem] + WHERE 1=1 "; - var whereBuilder = new SqlWhereBuilder(); - whereBuilder.In("tblAccountInvoiceLineItem.ItemCode", itemCodeList); - sql += whereBuilder.SqlWhereString; + if (sqlwhere != null) + { + sql += sqlwhere.SqlWhereString; + } using (SqlConnection conn = new SqlConnection(SqlConnectionString)) { @@ -72,41 +70,53 @@ namespace bnhtrade.Core.Data.Database.Account using (SqlCommand cmd = new SqlCommand(sql, conn)) { - foreach (var param in whereBuilder.ParameterList) + if (sqlwhere != null) { - cmd.Parameters.AddWithValue(param.Key, param.Value); + sqlwhere.AddParametersToSqlCommand(cmd); } using (SqlDataReader reader = cmd.ExecuteReader()) { - if (reader.HasRows) + while (reader.Read()) { - while (reader.Read()) + var lineItem = new Model.Account.InvoiceLineItem(); + + int lineItemId = reader.GetInt32(0); + lineItem.Name = reader.GetString(1); + lineItem.ItemCode = reader.GetString(2); + if (!reader.IsDBNull(3)) { lineItem.Description = reader.GetString(3); } + lineItem.IsNewReviewRequired = reader.GetBoolean(4); + lineItem.InvoiceLineEntryEnabled = reader.GetBoolean(5); + + if (!reader.IsDBNull(6)) { - var result = new Model.Account.InvoiceLineItem(); - - result.ItemCode = reader.GetString(0); - result.Name = reader.GetString(1); - if (!reader.IsDBNull(2)) { result.Description = reader.GetString(2); } - result.IsNewReviewRequired = reader.GetBoolean(3); - result.InvoiceLineEntryEnabled = reader.GetBoolean(4); - - if (!reader.IsDBNull(5)) - { - AccountCodeList.Add(result.ItemCode, reader.GetInt32(5)); - } - if (!reader.IsDBNull(6)) - { - TaxCodeList.Add(result.ItemCode, reader.GetString(6)); - } - - resultList.Add(result.ItemCode, result); + accountCodeIdList.Add(lineItemId, (uint)reader.GetInt32(6)); + } + if (!reader.IsDBNull(7)) + { + taxCodeIdList.Add(lineItemId, reader.GetInt32(7)); } - } - return resultList; + resultList.Add(lineItemId, lineItem); + } } } + + // get account codes and add to result list + var accountCodeDictionary = new Data.Database.Account.ReadAccountCode().ByAccountId(accountCodeIdList.Values.ToList()); + foreach (var accountCode in accountCodeIdList) + { + resultList[accountCode.Key].DefaultAccountCode = accountCodeDictionary[accountCode.Value]; + } + + // get tax codes + var taxCodeDictionary = new Data.Database.Account.ReadTaxCode().GetByTaxCodeId(taxCodeIdList.Values.ToList()); + foreach (var taxCode in taxCodeIdList) + { + resultList[taxCode.Key].DefaultTaxCode = taxCodeDictionary[taxCode.Value]; + } + + return resultList; } } } diff --git a/src/bnhtrade.Core/Data/Database/Account/ReadPurchaseInvoice.cs b/src/bnhtrade.Core/Data/Database/Account/ReadPurchaseInvoice.cs index f9442ff..97b608a 100644 --- a/src/bnhtrade.Core/Data/Database/Account/ReadPurchaseInvoice.cs +++ b/src/bnhtrade.Core/Data/Database/Account/ReadPurchaseInvoice.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,11 +8,11 @@ using static System.ComponentModel.Design.ObjectSelectorEditor; namespace bnhtrade.Core.Data.Database.Account { - public class ReadPurchaseInvoice : Connection + internal class ReadPurchaseInvoice : Connection { private bnhtrade.Core.Data.Database.SqlWhereBuilder sqlBuilder; - public List PurchaseInvoiceIdList { get; set; } + public IEnumerable PurchaseInvoiceIdList { get; set; } public ReadPurchaseInvoice() { diff --git a/src/bnhtrade.Core/Data/Database/Account/ReadTaxCode.cs b/src/bnhtrade.Core/Data/Database/Account/ReadTaxCode.cs index dd285c2..cd9f031 100644 --- a/src/bnhtrade.Core/Data/Database/Account/ReadTaxCode.cs +++ b/src/bnhtrade.Core/Data/Database/Account/ReadTaxCode.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; namespace bnhtrade.Core.Data.Database.Account { - public class ReadTaxCode : Connection + internal class ReadTaxCode : Connection { private Data.Database.SqlWhereBuilder whereBuilder = new SqlWhereBuilder(); @@ -15,14 +15,15 @@ namespace bnhtrade.Core.Data.Database.Account { } - private List Execute(string sqlWhere, Dictionary parameters) + private Dictionary Execute(string sqlWhere, Dictionary parameters) { - var resultList = new List(); + var resultList = new Dictionary(); //build sql query string sqlString = @" SELECT - TaxCode + AccountTaxCodeID + ,TaxCode ,TaxCodeName ,TaxCodeDescription ,TaxRatePercent @@ -52,19 +53,20 @@ namespace bnhtrade.Core.Data.Database.Account { while (reader.Read()) { - string taxCodeId = reader.GetString(0); - string name = reader.GetString(1); + int taxCodeId = reader.GetByte(0); + string taxCode = reader.GetString(1); + string name = reader.GetString(2); string description = null; - if (!reader.IsDBNull(2)) { description = reader.GetString(2); } - decimal rate = reader.GetDecimal(3); - bool isMargin = reader.GetBoolean(4); - bool isValidOnExpense = reader.GetBoolean(5); - bool isValidOnIncome = reader.GetBoolean(6); - bool isActive = reader.GetBoolean(7); - string taxType = reader.GetString(8); + if (!reader.IsDBNull(3)) { description = reader.GetString(3); } + decimal rate = reader.GetDecimal(4); + bool isMargin = reader.GetBoolean(5); + bool isValidOnExpense = reader.GetBoolean(6); + bool isValidOnIncome = reader.GetBoolean(7); + bool isActive = reader.GetBoolean(8); + string taxType = reader.GetString(9); var result = new Model.Account.TaxCodeInfo( - taxCodeId, + taxCode, name, description, rate, @@ -74,7 +76,7 @@ namespace bnhtrade.Core.Data.Database.Account taxType, isActive); - resultList.Add(result); + resultList.Add(taxCodeId, result); } } } @@ -97,12 +99,13 @@ namespace bnhtrade.Core.Data.Database.Account whereBuilder.Init(); whereBuilder.In("TaxCode", taxcodeList, "WHERE"); - return Execute(whereBuilder.SqlWhereString, whereBuilder.ParameterList); + var dic = Execute(whereBuilder.SqlWhereString, whereBuilder.ParameterList); + return dic.Values.ToList(); } - public List GetByTaxCodeId(List taxcodeIdList) + public Dictionary GetByTaxCodeId(List taxcodeIdList) { - var resultList = new List(); + var resultList = new Dictionary(); if (taxcodeIdList == null || !taxcodeIdList.Any()) { @@ -117,7 +120,12 @@ namespace bnhtrade.Core.Data.Database.Account return Execute(whereBuilder.SqlWhereString, whereBuilder.ParameterList); } - public List GetAllActive() + + /// + /// Gets all active Tax Code objects + /// + /// Dictionary where database record ID is the key + public Dictionary GetAllActive() { string sqlWhere = @" WHERE IsActive=@isActive;"; diff --git a/src/bnhtrade.Core/Data/Database/Connection.cs b/src/bnhtrade.Core/Data/Database/Connection.cs index 53f29d9..c97c0e8 100644 --- a/src/bnhtrade.Core/Data/Database/Connection.cs +++ b/src/bnhtrade.Core/Data/Database/Connection.cs @@ -11,11 +11,11 @@ namespace bnhtrade.Core.Data.Database public class Connection { //protected readonly string SqlConnectionString; - private Model.Credentials.bnhtradeDB dbCredentials; + private Model.Credentials.bnhtradeDB _dbCredentials; protected string SqlConnectionString { - get { return dbCredentials.ConnectionString; } + get { return _dbCredentials.ConnectionString; } } public Connection() @@ -44,7 +44,7 @@ namespace bnhtrade.Core.Data.Database } var dbCredentials = new bnhtrade.Core.Model.Credentials.bnhtradeDB(dataSource, userId, pass); - this.dbCredentials = dbCredentials; + this._dbCredentials = dbCredentials; } catch (Exception ex) { @@ -76,7 +76,7 @@ namespace bnhtrade.Core.Data.Database } var dbCredentials = new bnhtrade.Core.Model.Credentials.bnhtradeDB(dataSource, userId, pass); - this.dbCredentials = dbCredentials; + this._dbCredentials = dbCredentials; } catch(Exception ex) { @@ -91,7 +91,7 @@ namespace bnhtrade.Core.Data.Database { throw new Exception("DB credentials object is null"); } - this.dbCredentials = dbCredentials; + this._dbCredentials = dbCredentials; } } } diff --git a/src/bnhtrade.Core/Data/Database/Consistency/ImportAmazonSettlement.cs b/src/bnhtrade.Core/Data/Database/Consistency/ImportAmazonSettlement.cs deleted file mode 100644 index ece367b..0000000 --- a/src/bnhtrade.Core/Data/Database/Consistency/ImportAmazonSettlement.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace bnhtrade.Core.Data.Database.Consistency -{ - public class ImportAmazonSettlement : Connection - { - public ImportAmazonSettlement() - { - - } - - public List ErrorMessage { get; private set; } - - public bool ErrorMessageIsSet - { - get - { - if (ErrorMessage == null || !ErrorMessage.Any()) { return false; } - else { return true; } - } - } - - public bool PeriodDateGaps() - { - ErrorMessage = null; - - using (var sqlConn = new SqlConnection(SqlConnectionString)) - { - var log = new Logic.Log.LogEvent(); - sqlConn.Open(); - - // get list of marketplace names - var marketplaces = new List(); - using (SqlCommand cmd = new SqlCommand(@" - SELECT DISTINCT tblImportAmazonSettlementReport.ImportAmazonSettlementReportID - ,tblImportAmazonSettlementReport.[marketplace-name] - ,tblImportAmazonSettlementReport.[settlement-start-date] - ,tblImportAmazonSettlementReport.[settlement-end-date] - FROM tblImportAmazonSettlementReport - ORDER BY tblImportAmazonSettlementReport.[marketplace-name] - ,tblImportAmazonSettlementReport.[settlement-start-date] - ,tblImportAmazonSettlementReport.[settlement-end-date]; - ", sqlConn)) - { - using(var reader = cmd.ExecuteReader()) - { - if (!reader.HasRows) - { - ErrorMessage.Add("No data found. Is the table empty?"); - return false; - } - - int i = 0; - string marketplace = "some-random-string-7posadlnvl2oaweoh3amol5o5irv8nl2aoqefl"; - DateTime endDate = DateTime.SpecifyKind(default(DateTime), DateTimeKind.Utc); - while (reader.Read()) - { - if (reader.IsDBNull(1)) - { - log.LogError( - "Action required: Enter market place name for settlelment report id " + reader.GetInt32(0) + "." - , "Unable to process settlement data from one settlement report '" + reader.GetInt32(0) + - "'. Report header table requires a market place name, which is not supplied in original " + - "report from Amazon. This is useually inferred from settlement lines. " + - "However, in this case, it was not not possible. Manual edit/entry for database table required." - ); - return false; - } - - string newMarketPlace = reader.GetString(1); - DateTime startDate = DateTime.SpecifyKind( reader.GetDateTime(2), DateTimeKind.Utc); - - if (marketplace != newMarketPlace) - { - marketplace = newMarketPlace; - i = 0; - } - - if (i > 0 && startDate != endDate) - { - log.LogError("Inconsistancy in DB data found, break in settlement period dates.", - marketplace + " at ID=" + reader.GetInt32(0) + " start-date is not the same as the previous period end-date." - + Environment.NewLine + "If it's a missing settlement report and it's over 90 days ago, you may need to go to" - + " 'Seller Central' > 'Payments' > 'All Statements' and manually 'Request Report' for each missing settlement."); - return false; - } - - endDate = DateTime.SpecifyKind(reader.GetDateTime(3), DateTimeKind.Utc); - i++; - } - } - } - } - return true; - } - } -} \ No newline at end of file diff --git a/src/bnhtrade.Core/Data/Database/Export/CreateAmazonFeedSubmission.cs b/src/bnhtrade.Core/Data/Database/Export/AmazonFeedSubmissionInsert.cs similarity index 95% rename from src/bnhtrade.Core/Data/Database/Export/CreateAmazonFeedSubmission.cs rename to src/bnhtrade.Core/Data/Database/Export/AmazonFeedSubmissionInsert.cs index 4145fb3..8e12ffd 100644 --- a/src/bnhtrade.Core/Data/Database/Export/CreateAmazonFeedSubmission.cs +++ b/src/bnhtrade.Core/Data/Database/Export/AmazonFeedSubmissionInsert.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.IO; using System.Linq; using System.Text; @@ -8,9 +8,9 @@ using System.Threading.Tasks; namespace bnhtrade.Core.Data.Database.Export { - public class CreateAmazonFeedSubmission : Connection + internal class AmazonFeedSubmissionInsert : Connection { - public CreateAmazonFeedSubmission () + public AmazonFeedSubmissionInsert () { } diff --git a/src/bnhtrade.Core/Data/Database/Export/ReadAmazonFeedSubmission.cs b/src/bnhtrade.Core/Data/Database/Export/AmazonFeedSubmissionRead.cs similarity index 97% rename from src/bnhtrade.Core/Data/Database/Export/ReadAmazonFeedSubmission.cs rename to src/bnhtrade.Core/Data/Database/Export/AmazonFeedSubmissionRead.cs index d5768bf..0a122fd 100644 --- a/src/bnhtrade.Core/Data/Database/Export/ReadAmazonFeedSubmission.cs +++ b/src/bnhtrade.Core/Data/Database/Export/AmazonFeedSubmissionRead.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,9 +8,9 @@ using Dapper; namespace bnhtrade.Core.Data.Database.Export { - public class ReadAmazonFeedSubmission : Connection + internal class AmazonFeedSubmissionRead : Connection { - public ReadAmazonFeedSubmission() + public AmazonFeedSubmissionRead() { } diff --git a/src/bnhtrade.Core/Data/Database/Export/UpdateAmazonFeedSubmission.cs b/src/bnhtrade.Core/Data/Database/Export/AmazonFeedSubmissionUpdate.cs similarity index 97% rename from src/bnhtrade.Core/Data/Database/Export/UpdateAmazonFeedSubmission.cs rename to src/bnhtrade.Core/Data/Database/Export/AmazonFeedSubmissionUpdate.cs index acd001e..0f4ef07 100644 --- a/src/bnhtrade.Core/Data/Database/Export/UpdateAmazonFeedSubmission.cs +++ b/src/bnhtrade.Core/Data/Database/Export/AmazonFeedSubmissionUpdate.cs @@ -1,15 +1,15 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; namespace bnhtrade.Core.Data.Database.Export { - public class UpdateAmazonFeedSubmission : Connection + internal class AmazonFeedSubmissionUpdate : Connection { - public UpdateAmazonFeedSubmission() + public AmazonFeedSubmissionUpdate() { } diff --git a/src/bnhtrade.Core/Data/Database/Export/CreateSalesInvoice.cs b/src/bnhtrade.Core/Data/Database/Export/CreateSalesInvoice.cs deleted file mode 100644 index 38354b7..0000000 --- a/src/bnhtrade.Core/Data/Database/Export/CreateSalesInvoice.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Transactions; - -namespace bnhtrade.Core.Data.Database.Export -{ - public class CreateSalesInvoice : Connection - { - public CreateSalesInvoice() - { - - } - - public void Execute(List invoiceList) - { - using (TransactionScope scope = new TransactionScope()) - { - using (SqlConnection sqlConn = new SqlConnection(SqlConnectionString)) - { - sqlConn.Open(); - - // make the inserts - for (int i = 0; i < invoiceList.Count(); i++) - { - int invoiceId = 0; - using (SqlCommand cmd = new SqlCommand(@" - INSERT INTO tblExportAccountInvoice ( - ExportAccountInvoiceTypeID - ,Contact - ,InvoiceDate - ,InvoiceDueDate - ,InvoiceNumber - ,Reference - ,CurrencyCode - ,InvoiceAmount - ,IsComplete - ) - OUTPUT INSERTED.ExportAccountInvoiceID - VALUES ( - @invoiceTypeId - ,@contact - ,@invoiceDate - ,@invoiceDueDate - ,@invoiceNumber - ,@reference - ,@currencyCode - ,@invoiceAmount - ,@markComplete - ); - ", sqlConn)) - { - cmd.Parameters.AddWithValue("@invoiceTypeId", 2); - cmd.Parameters.AddWithValue("@contact", invoiceList[i].ContactName); - cmd.Parameters.AddWithValue("@invoiceDate", invoiceList[i].InvoiceDate); - cmd.Parameters.AddWithValue("@invoiceDueDate", invoiceList[i].InvoiceDueDate); - cmd.Parameters.AddWithValue("@reference", invoiceList[i].InvoiceReference); - cmd.Parameters.AddWithValue("@currencyCode", invoiceList[i].InvoiceCurrencyCode); - cmd.Parameters.AddWithValue("@markComplete", false); - cmd.Parameters.AddWithValue("@invoiceNumber", invoiceList[i].InvoiceNumber); - cmd.Parameters.AddWithValue("@invoiceAmount", invoiceList[i].InvoiceTotalAmount); - - invoiceId = (int)cmd.ExecuteScalar(); - } - - for (int j = 0; j < invoiceList[i].InvoiceLineList.Count(); j++) - { - // insert record - using (SqlCommand cmd = new SqlCommand(@" - INSERT INTO tblExportAccountInvoiceLine ( - ExportAccountInvoiceID - ,AccountInvoiceLineItemID - ,NetAmount - ,AccountChartOfID - ,TaxAmount - ,AccountTaxCodeID - ) - OUTPUT INSERTED.ExportAccountInvoiceLineID - VALUES ( - @invoiceId - ,( - SELECT AccountInvoiceLineItemID - FROM tblAccountInvoiceLineItem - WHERE ItemCode = @itemCode - ) - ,@netAmount - ,( - SELECT AccountChartOfID - FROM tblAccountChartOf - WHERE AccountCode = @accountCode - ) - ,@taxAmount - ,( - SELECT AccountTaxCodeID - FROM tblAccountTaxCode - WHERE TaxCode = @taxCode - ) - ); - ", sqlConn)) - { - cmd.Parameters.AddWithValue("@invoiceID", invoiceId); - cmd.Parameters.AddWithValue("@itemCode", invoiceList[i].InvoiceLineList[j].ItemCode); - cmd.Parameters.AddWithValue("@netAmount", invoiceList[i].InvoiceLineList[j].UnitAmount); - cmd.Parameters.AddWithValue("@accountCode", (int)invoiceList[i].InvoiceLineList[j].AccountCode.AccountCode); - cmd.Parameters.AddWithValue("@taxAmount", invoiceList[i].InvoiceLineList[j].TaxAmount); - cmd.Parameters.AddWithValue("@taxCode", invoiceList[i].InvoiceLineList[j].TaxCode.TaxCode); - - int lineId = (int)cmd.ExecuteScalar(); - } - } - } - } - scope.Complete(); - } - } - } -} \ No newline at end of file diff --git a/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementHeaderRead.cs b/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementHeaderRead.cs deleted file mode 100644 index 02f1f23..0000000 --- a/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementHeaderRead.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Data.SqlClient; - -namespace bnhtrade.Core.Data.Database.Import -{ - public class AmazonSettlementHeaderRead : Connection - { - private Dictionary dicTablePkBySettlementId = new Dictionary(); - private int? returnTop = null; - private List settlementIdList; - private List spapiReportId; - - protected bool FilterOutIsProcessed { get; set; } - - public bool DescendingOrder { get; set; } - - public int ReturnTop - { - get { return (int)returnTop; } - set - { - if (value > 0) - { returnTop = value; } - else - { returnTop = null; } - } - } - - public bool ReturnTopIsSet - { - get { return returnTop != null; } - } - - private List SettlementIdList - { - get { return settlementIdList; } - set - { - if (value.Any()) - { settlementIdList = value; } - } - - } - - private List SpapiReportIdList - { - get { return spapiReportId; } - set - { - if (value.Any()) - { spapiReportId = value; } - } - - } - - private bool SettlementIdListIsSet - { - get { return SettlementIdList != null; } - } - - private bool SpapiReportIdIsSet - { - get { return SpapiReportIdList != null; } - } - - public AmazonSettlementHeaderRead() - { - Innit(); - } - - private void Innit() - { - DescendingOrder = false; - FilterOutIsProcessed = false; - ReturnTop = 0; - settlementIdList = null; - spapiReportId = null; - } - - public List AllUnprocessed() - { - Innit(); - FilterOutIsProcessed = true; - return ReadHeaderList(); - } - - public Model.Import.AmazonSettlementHeader BySettlementId(string settlementId) - { - Innit(); - - // create settlement list - var idList = new List(); - idList.Add(settlementId); - var settlementList = BySettlementId(idList); - - // return answer - if (settlementList == null || !settlementList.Any()) - { return null; } - else - { return settlementList.First(); } - } - - public List BySettlementId(List settlementIdList) - { - Innit(); - - if (settlementIdList == null || !settlementIdList.Any()) - { return new List(); } - - SettlementIdList = settlementIdList; - - return ReadHeaderList(); - } - - public List BySpapiReportId(List spapiReportIdList) - { - Innit(); - - if (spapiReportIdList == null || !spapiReportIdList.Any()) - { return new List(); } - - SpapiReportIdList = spapiReportIdList; - - return ReadHeaderList(); - } - - private List ReadHeaderList() - { - var returnHeaderList = new List(); - - // build the sql string - string sqlString = "SELECT "; - - if (ReturnTopIsSet) - { - sqlString = sqlString + "TOP " + ReturnTop + " "; - } - - sqlString = sqlString + @" - ImportAmazonSettlementReportID - ,[marketplace-name] - ,[settlement-id] - ,[settlement-start-date] - ,[settlement-end-date] - ,[deposit-date] - ,[total-amount] - ,currency - ,IsProcessed - ,SpapiReportId - FROM tblImportAmazonSettlementReport - WHERE 1 = 1"; - - if (FilterOutIsProcessed) - { - sqlString = sqlString + @" - AND IsProcessed = 0"; - } - - // build dictionary of parameter and values for settlementid - var dicSettlementIdByParameterString = new Dictionary(); - if (SettlementIdListIsSet) - { - int count = 0; - foreach (string item in SettlementIdList) - { - if (!string.IsNullOrWhiteSpace(item)) - { - count = count + 1; - string parameterString = "@settlementId" + count; - dicSettlementIdByParameterString.Add(parameterString, item); - } - } - } - - if (dicSettlementIdByParameterString.Any()) - { - int count = 0; - foreach (var item in dicSettlementIdByParameterString) - { - count = count + 1; - if (count == 1) - { - sqlString = sqlString + @" - AND ( [settlement-id] = " + item.Key; - } - else - { - sqlString = sqlString + @" - OR [settlement-id] = " + item.Key; - } - } - sqlString = sqlString + " )"; - } - - // build dictionary of parameter and values for SP-API Report Id - var dicSpapiReportIdByParameterString = new Dictionary(); - if (SpapiReportIdIsSet) - { - int count = 0; - foreach (string item in SpapiReportIdList) - { - if (!string.IsNullOrWhiteSpace(item)) - { - count = count + 1; - string parameterString = "@SpapiReportId" + count; - dicSpapiReportIdByParameterString.Add(parameterString, item); - } - } - } - - if (dicSpapiReportIdByParameterString.Any()) - { - int count = 0; - foreach (var item in dicSpapiReportIdByParameterString) - { - count = count + 1; - if (count == 1) - { - sqlString = sqlString + @" - AND ( SpapiReportId = " + item.Key; - } - else - { - sqlString = sqlString + @" - OR SpapiReportId = " + item.Key; - } - } - sqlString = sqlString + " )"; - } - - - sqlString = sqlString + @" - ORDER BY [settlement-start-date] "; - if (DescendingOrder) { sqlString = sqlString + " DESC"; } - - using (SqlConnection conn = new SqlConnection(SqlConnectionString)) - { - conn.Open(); - - using (SqlCommand cmd = new SqlCommand(sqlString, conn)) - { - if (dicSettlementIdByParameterString.Any()) - { - foreach (var item in dicSettlementIdByParameterString) - { - cmd.Parameters.AddWithValue(item.Key, item.Value); - } - } - - if (dicSpapiReportIdByParameterString.Any()) - { - foreach (var item in dicSpapiReportIdByParameterString) - { - cmd.Parameters.AddWithValue(item.Key, item.Value); - } - } - - using (var reader = cmd.ExecuteReader()) - { - while (reader.Read()) - { - var header = new Model.Import.AmazonSettlementHeader(); - - int tablePk = reader.GetInt32(0); - if (!reader.IsDBNull(1)) { header.MarketPlaceName = reader.GetString(1); } - header.SettlementId = reader.GetString(2); - header.StartDate = DateTime.SpecifyKind(reader.GetDateTime(3), DateTimeKind.Utc); - header.EndDate = DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc); - header.DepositDate = DateTime.SpecifyKind(reader.GetDateTime(5), DateTimeKind.Utc); - header.TotalAmount = reader.GetDecimal(6); - header.CurrencyCode = reader.GetString(7); - header.IsProcessed = reader.GetBoolean(8); - if (!reader.IsDBNull(9)) { header.SpapiReportId = reader.GetString(9); } - - // update dictionary - if (!dicTablePkBySettlementId.ContainsKey(header.SettlementId)) - { - dicTablePkBySettlementId.Add(header.SettlementId, tablePk); - } - - // add header to list - returnHeaderList.Add(header); - } - } - } - } - return returnHeaderList; - } - } -} \ No newline at end of file diff --git a/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementInsert.cs b/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementInsert.cs deleted file mode 100644 index 778368b..0000000 --- a/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementInsert.cs +++ /dev/null @@ -1,330 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Transactions; - -namespace bnhtrade.Core.Data.Database.Import -{ - public class AmazonSettlementInsert : Connection - { - Logic.Log.LogEvent log = new Logic.Log.LogEvent(); - - public AmazonSettlementInsert () - { - - } - - /// - /// - /// - /// - /// The unique Amazon SP-API report id (not settlement id) - /// - public bool ByFlatFile(string filePath, string reportId) - { - using (TransactionScope scope = new TransactionScope()) - { - using (SqlConnection sqlConn = new SqlConnection(SqlConnectionString)) - { - sqlConn.Open(); - - int settlementReportId = 0; - string settlementRef = ""; - bool marketPlaceUpdated = false; - decimal settlementAmount = 0m; - int lineNumber = 2; - int lineSkip = 0; - - using (var reader = new StreamReader(filePath)) - { - //read file one line at a time and insert data into table if required - - // read header and retrive information - string headerRow = reader.ReadLine(); - string[] headers = headerRow.Split('\t'); - - int columnCount = headers.Length; - - int indexSettlementId = Array.IndexOf(headers, "settlement-id"); - int indexSettlementStartDate = Array.IndexOf(headers, "settlement-start-date"); - int indexSettlementEndDate = Array.IndexOf(headers, "settlement-end-date"); - int indexDepositDate = Array.IndexOf(headers, "deposit-date"); - int indexTotalAmount = Array.IndexOf(headers, "total-amount"); - int indexCurrency = Array.IndexOf(headers, "currency"); - int indexTransactionType = Array.IndexOf(headers, "transaction-type"); - int indexOrderId = Array.IndexOf(headers, "order-id"); - int indexMerchantOrderId = Array.IndexOf(headers, "merchant-order-id"); - int indexAdjustmentId = Array.IndexOf(headers, "adjustment-id"); - int indexShipmentId = Array.IndexOf(headers, "shipment-id"); - int indexMarketplaceName = Array.IndexOf(headers, "marketplace-name"); - int indexAmountType = Array.IndexOf(headers, "amount-type"); - int indexAmountDescription = Array.IndexOf(headers, "amount-description"); - int indexAmount = Array.IndexOf(headers, "amount"); - int indexFulfillmentId = Array.IndexOf(headers, "fulfillment-id"); - // int indexPostedDate = Array.IndexOf(headers, "posted-date"); - int indexPostedDateTime = Array.IndexOf(headers, "posted-date-time"); - int indexOrderItemCode = Array.IndexOf(headers, "order-item-code"); - int indexMerchantOrderItemId = Array.IndexOf(headers, "merchant-order-item-id"); - int indexMerchantAdjustmentItemId = Array.IndexOf(headers, "merchant-adjustment-item-id"); - int indexSku = Array.IndexOf(headers, "sku"); - int indexQuantityPurchased = Array.IndexOf(headers, "quantity-purchased"); - int indexPromotionId = Array.IndexOf(headers, "promotion-id"); - - string currency = ""; - - string fileRow; - while ((fileRow = reader.ReadLine()) != null) - { - Console.Write("\rParsing record: " + lineNumber); - //split line into array - string[] items = fileRow.Split('\t'); - if (items.Length != columnCount) - { - // skip line - lineSkip = lineSkip + 1; - log.LogWarning( - "Line #" + lineNumber + " skipped due to no enough element in row.", - filePath - ); - } - else if (lineNumber == 2) - { - // check if settlement has already been imported - using (SqlCommand sqlCommand = new SqlCommand( - "SELECT COUNT(*) FROM tblImportAmazonSettlementReport WHERE [settlement-id]=@settlementId;" - , sqlConn)) - { - sqlCommand.Parameters.AddWithValue("@settlementId", items[indexSettlementId]); - int recordCount = (int)sqlCommand.ExecuteScalar(); - if (recordCount > 0) - { - UpdateSpapiReportId(items[indexSettlementId], reportId); - log.LogInformation("Settlement report already imported, skipping..."); - scope.Complete(); - return true; - } - } - //set currencyId - //currencyId = GeneralQueries.GetCurrencyId(items[5]); - - //set currency - currency = items[indexCurrency]; - settlementAmount = decimal.Parse(items[indexTotalAmount].Replace(",", ".")); - - // insert - using (SqlCommand sqlCommand = new SqlCommand(@" - INSERT INTO tblImportAmazonSettlementReport ( - [settlement-id] - ,[settlement-start-date] - ,[settlement-end-date] - ,[deposit-date] - ,[total-amount] - ,[currency] - ,SpapiReportId - ) - OUTPUT INSERTED.ImportAmazonSettlementReportID - VALUES ( - @settlementId - ,@settlementStartDate - ,@settlementEndDate - ,@depositDate - ,@settlementotalAmounttId - ,@currency - ,@reportId - ); - ", sqlConn)) - { - // add parameters - if (indexSettlementId == -1 || items[indexSettlementId].Length == 0) { sqlCommand.Parameters.AddWithValue("@settlementId", DBNull.Value); } - else - { - settlementRef = items[indexSettlementId]; - sqlCommand.Parameters.AddWithValue("@settlementId", settlementRef); - } - - var parseDateTime = new Core.Logic.Utilities.DateTime(); - - if (indexSettlementStartDate == -1 || items[indexSettlementStartDate].Length == 0) { sqlCommand.Parameters.AddWithValue("@settlementStartDate", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@settlementStartDate", parseDateTime.ParseIsoDateTimeString(items[indexSettlementStartDate])); } - - if (indexSettlementEndDate == -1 || items[indexSettlementEndDate].Length == 0) { sqlCommand.Parameters.AddWithValue("@settlementEndDate", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@settlementEndDate", parseDateTime.ParseIsoDateTimeString(items[indexSettlementEndDate])); } - - if (indexDepositDate == -1 || items[indexDepositDate].Length == 0) { sqlCommand.Parameters.AddWithValue("@depositDate", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@depositDate", parseDateTime.ParseIsoDateTimeString(items[indexDepositDate])); } - - if (indexTotalAmount == -1 || items[indexTotalAmount].Length == 0) { sqlCommand.Parameters.AddWithValue("@totalAmount", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@settlementotalAmounttId", settlementAmount); } - - if (string.IsNullOrWhiteSpace(reportId)) { sqlCommand.Parameters.AddWithValue("@reportId", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@reportId", reportId); } - - sqlCommand.Parameters.AddWithValue("@currency", currency); - - //if (currencyId == -1) { sqlCommand.Parameters.AddWithValue("@currencyId", DBNull.Value); } - //else { sqlCommand.Parameters.AddWithValue("@currencyId", currencyId); } - - //execute and retrive id - settlementReportId = (int)sqlCommand.ExecuteScalar(); - - } - } - else - { - //update market place name in main table, if required - if (marketPlaceUpdated == false && settlementReportId > 0 && items[indexMarketplaceName].Length > 1) - { - using (SqlCommand sqlCommand = new SqlCommand(@" - UPDATE tblImportAmazonSettlementReport - SET [marketplace-name]=@MarketplaceName - WHERE ImportAmazonSettlementReportID=@ImportAmazonSettlementReportID - ", sqlConn)) - { - sqlCommand.Parameters.AddWithValue("@MarketplaceName", items[indexMarketplaceName]); - sqlCommand.Parameters.AddWithValue("@ImportAmazonSettlementReportID", settlementReportId); - - sqlCommand.ExecuteNonQuery(); - marketPlaceUpdated = true; - } - } - - //insert report items - using (SqlCommand sqlCommand = new SqlCommand( - "INSERT INTO tblImportAmazonSettlementReportLine ( " + - "ImportAmazonSettlementReportID, [transaction-type], [order-id], [merchant-order-id], [adjustment-id], [shipment-id], [marketplace-name], " + - "[amount-type], [amount-description], [currency], [amount], [fulfillment-id], [posted-date-time], [order-item-code], " + - "[merchant-order-item-id], [merchant-adjustment-item-id], [sku], [quantity-purchased], [promotion-id] ) " + - "VALUES ( " + - "@ImportAmazonSettlementReportID, @TransactionType, @orderRef, @merchantOrderRef, @AdjustmentRef, @ShipmentRef, @MarketplaceName, " + - "@AmountType, @AmountDescription, @currency, @Amount, @FulfillmentRef, @PostedDateTimeUTC, @OrderItemCode, " + - "@MerchantOrderItemRef, @MerchantAdjustmentItemRef, @SkuNumber, @QuantityPurchased, @PromotionRef );" - , sqlConn)) - { - // add parameters - sqlCommand.Parameters.AddWithValue("@ImportAmazonSettlementReportID", settlementReportId); - - sqlCommand.Parameters.AddWithValue("@currency", currency); - - if (indexTransactionType == -1 || items[indexTransactionType].Length == 0) { sqlCommand.Parameters.AddWithValue("@TransactionType", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@TransactionType", items[indexTransactionType]); } - - if (indexOrderId == -1 || items[indexOrderId].Length == 0) { sqlCommand.Parameters.AddWithValue("@orderRef", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@orderRef", items[indexOrderId]); } - - if (indexMerchantOrderId == -1 || items[indexMerchantOrderId].Length == 0) { sqlCommand.Parameters.AddWithValue("@merchantOrderRef", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@merchantOrderRef", items[indexMerchantOrderId]); } - - if (indexAdjustmentId == -1 || items[indexAdjustmentId].Length == 0) { sqlCommand.Parameters.AddWithValue("@AdjustmentRef", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@AdjustmentRef", items[indexAdjustmentId]); } - - if (indexShipmentId == -1 || items[indexShipmentId].Length == 0) { sqlCommand.Parameters.AddWithValue("@ShipmentRef", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@ShipmentRef", items[indexShipmentId]); } - - if (indexMarketplaceName == -1 || items[indexMarketplaceName].Length == 0) { sqlCommand.Parameters.AddWithValue("@MarketplaceName", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@MarketplaceName", items[indexMarketplaceName]); } - - if (indexAmountType == -1 || items[indexAmountType].Length == 0) { sqlCommand.Parameters.AddWithValue("@AmountType", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@AmountType", items[indexAmountType]); } - - if (indexAmountDescription == -1 || items[indexAmountDescription].Length == 0) { sqlCommand.Parameters.AddWithValue("@AmountDescription", DBNull.Value); } - else - { - string amountDescription = items[indexAmountDescription]; - if (amountDescription.Length > 100) { amountDescription = amountDescription.Substring(0, 100); } - sqlCommand.Parameters.AddWithValue("@AmountDescription", amountDescription); - } - - if (indexAmount == -1 || items[indexAmount].Length == 0) { sqlCommand.Parameters.AddWithValue("@Amount", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@Amount", decimal.Parse(items[indexAmount].Replace(",", "."))); } - - if (indexFulfillmentId == -1 || items[indexFulfillmentId].Length == 0) { sqlCommand.Parameters.AddWithValue("@FulfillmentRef", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@FulfillmentRef", items[indexFulfillmentId]); } - - if (indexPostedDateTime == -1 || items[indexPostedDateTime].Length == 0) { sqlCommand.Parameters.AddWithValue("@PostedDateTimeUTC", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@PostedDateTimeUTC", new Logic.Utilities.DateTime().ParseIsoDateTimeString(items[indexPostedDateTime])); } - - if (indexOrderItemCode == -1 || items[indexOrderItemCode].Length == 0) { sqlCommand.Parameters.AddWithValue("@OrderItemCode", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@OrderItemCode", long.Parse(items[indexOrderItemCode])); } - - if (indexMerchantOrderItemId == -1 || items[indexMerchantOrderItemId].Length == 0) { sqlCommand.Parameters.AddWithValue("@MerchantOrderItemRef", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@MerchantOrderItemRef", long.Parse(items[indexMerchantOrderItemId])); } - - if (indexMerchantAdjustmentItemId == -1 || items[indexMerchantAdjustmentItemId].Length == 0) { sqlCommand.Parameters.AddWithValue("@MerchantAdjustmentItemRef", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@MerchantAdjustmentItemRef", items[indexMerchantAdjustmentItemId]); } - - if (indexSku == -1 || items[indexSku].Length == 0) { sqlCommand.Parameters.AddWithValue("@SkuNumber", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@SkuNumber", items[indexSku]); } - - if (indexQuantityPurchased == -1 || items[indexQuantityPurchased].Length == 0) { sqlCommand.Parameters.AddWithValue("@QuantityPurchased", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@QuantityPurchased", int.Parse(items[indexQuantityPurchased])); } - - if (indexPromotionId == -1 || items[indexPromotionId].Length == 0) { sqlCommand.Parameters.AddWithValue("@PromotionRef", DBNull.Value); } - else { sqlCommand.Parameters.AddWithValue("@PromotionRef", items[indexPromotionId]); } - - sqlCommand.ExecuteNonQuery(); - } - } - lineNumber = lineNumber + 1; - } - } - - //final check - settlement amount matches sum of inserted settlement lines - using (SqlCommand sqlCommand = new SqlCommand(@" - SELECT Sum(tblImportAmazonSettlementReportLine.amount) AS SumOfAmount - FROM tblImportAmazonSettlementReportLine - WHERE ImportAmazonSettlementReportID=@ImportAmazonSettlementReportID; - ", sqlConn)) - { - decimal sumOfAmount = -1.12345m; - - sqlCommand.Parameters.AddWithValue("@ImportAmazonSettlementReportID", settlementReportId); - sumOfAmount = (decimal)sqlCommand.ExecuteScalar(); - - if (sumOfAmount != settlementAmount) - { - log.LogError("Error importing settlement id'" + settlementRef + "'. Sum of inserted settlement lines (" + sumOfAmount + - ") does not match settlement amount (" + settlementAmount + ")."); - return false; - } - } - scope.Complete(); - - Console.Write("\r"); - log.LogInformation((lineNumber - (2 + lineSkip)) + " total settlement items inserted"); - if (lineSkip > 0) - { - log.LogError(lineSkip + " total line(s) where skipped due to insufficent number of cells on row"); - } - } - } - return true; - } - - public void UpdateSpapiReportId (string settlementId, string spapiReportId) - { - using (SqlConnection conn = new SqlConnection(SqlConnectionString)) - { - conn.Open(); - - using (SqlCommand cmd = new SqlCommand(@" - UPDATE - tblImportAmazonSettlementReport - SET - SpapiReportId = @spapiReportId - WHERE - [settlement-id] = @settlementId - ", conn)) - { - cmd.Parameters.AddWithValue("@spapiReportId", spapiReportId); - cmd.Parameters.AddWithValue("@settlementId", settlementId); - - cmd.ExecuteNonQuery(); - } - } - } - } -} diff --git a/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementRead.cs b/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementRead.cs deleted file mode 100644 index 4e46a34..0000000 --- a/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementRead.cs +++ /dev/null @@ -1,462 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Data.SqlClient; - -namespace bnhtrade.Core.Data.Database.Import -{ - public class AmazonSettlementRead : Connection - { - private Data.Database.SqlWhereBuilder whereBuilder = new SqlWhereBuilder(); - - public List BySettlementIdList(List settlementIdList) - { - var settlementList = new List(); - - // build sql statement - - string sql = @" - SELECT tblImportAmazonSettlementReport.[settlement-id] - ,tblImportAmazonSettlementReportLine.[transaction-type] - ,tblImportAmazonSettlementReportLine.[order-id] - ,tblImportAmazonSettlementReportLine.[merchant-order-id] - ,tblImportAmazonSettlementReportLine.[adjustment-id] - ,tblImportAmazonSettlementReportLine.[shipment-id] - ,tblImportAmazonSettlementReportLine.[marketplace-name] - ,tblImportAmazonSettlementReportLine.[amount-type] - ,tblImportAmazonSettlementReportLine.[amount-description] - ,tblImportAmazonSettlementReportLine.amount - ,tblImportAmazonSettlementReportLine.currency - ,tblImportAmazonSettlementReportLine.[fulfillment-id] - ,tblImportAmazonSettlementReportLine.[posted-date-time] - ,tblImportAmazonSettlementReportLine.[order-item-code] - ,tblImportAmazonSettlementReportLine.[merchant-order-item-id] - ,tblImportAmazonSettlementReportLine.[merchant-adjustment-item-id] - ,tblImportAmazonSettlementReportLine.sku - ,tblImportAmazonSettlementReportLine.[quantity-purchased] - ,tblImportAmazonSettlementReportLine.[promotion-id] - ,tblImportAmazonSettlementReportLine.IsProcessed - ,tblImportAmazonSettlementReportLine.ExportAccountInvoiceLineID - FROM tblImportAmazonSettlementReport - INNER JOIN tblImportAmazonSettlementReportLine ON tblImportAmazonSettlementReport.ImportAmazonSettlementReportID = tblImportAmazonSettlementReportLine.ImportAmazonSettlementReportID - WHERE "; - - whereBuilder.Init(); - whereBuilder.In("tblImportAmazonSettlementReport.[settlement-id]", settlementIdList); - - sql += whereBuilder.SqlWhereString; - - sql += @" - ORDER BY tblImportAmazonSettlementReport.[settlement-id] - ,tblImportAmazonSettlementReportLine.[posted-date-time] "; - - // set variables - bool firstRecord = true; - string settlementId = ""; - var lineList = new List(); - var LineListDic = new Dictionary>(); - var headerList = new List(); - - using (SqlConnection conn = new SqlConnection(SqlConnectionString)) - { - conn.Open(); - - using (SqlCommand cmd = new SqlCommand(sql, conn)) - { - whereBuilder.AddParametersToSqlCommand(cmd); - - using (var reader = cmd.ExecuteReader()) - { - if (!reader.HasRows) - { - return settlementList; - } - - // get the header list - headerList = new Data.Database.Import.AmazonSettlementHeaderRead().BySettlementId(settlementIdList); - - // loop through table to build dictionary - while (reader.Read()) - { - if (reader.GetString(0) != settlementId) - { - if (firstRecord) - { - firstRecord = false; - settlementId = reader.GetString(0); - } - else - { - LineListDic.Add(settlementId, lineList); - settlementId = reader.GetString(0); - lineList = new List(); - } - } - - var line = new Model.Import.AmazonSettlement.SettlementLine(); - - line.TransactionType = reader.GetString(1); - if (!reader.IsDBNull(2)) { line.OrderId = reader.GetString(2); } - if (!reader.IsDBNull(3)) { line.MerchantOrderId = reader.GetString(3); } - if (!reader.IsDBNull(4)) { line.AdjustmentId = reader.GetString(4); } - if (!reader.IsDBNull(5)) { line.ShipmentId = reader.GetString(5); } - if (!reader.IsDBNull(6)) { line.MarketPlaceName = reader.GetString(6); } - line.AmountType = reader.GetString(7); - line.AmountDescription = reader.GetString(8); - line.Amount = reader.GetDecimal(9); - line.CurrenyCode = reader.GetString(10); - if (!reader.IsDBNull(11)) { line.FulfillmentId = reader.GetString(11); } - line.PostDateTime = DateTime.SpecifyKind(reader.GetDateTime(12), DateTimeKind.Utc); - if (!reader.IsDBNull(13)) { line.OrderItemCode = reader.GetString(13); } - if (!reader.IsDBNull(14)) { line.MerchantOrderItemId = reader.GetString(14); } - if (!reader.IsDBNull(15)) { line.MerchantAdjustmentItemId = reader.GetString(15); } - if (!reader.IsDBNull(16)) { line.Sku = reader.GetString(16); } - if (!reader.IsDBNull(17)) { line.QuantityPurchased = reader.GetInt32(17); } - if (!reader.IsDBNull(18)) { line.PromotionId = reader.GetString(18); } - line.IsProcessed = reader.GetBoolean(19); - if (!reader.IsDBNull(20)) { int exportAccountInvoiceLineId = reader.GetInt32(20); } - - lineList.Add(line); - } - LineListDic.Add(settlementId, lineList); - } - } - } - - //create return list - foreach(var item in headerList) - { - var settlementReport = new Model.Import.AmazonSettlement(item); - settlementReport.SettlementLineList = LineListDic[item.SettlementId]; - settlementList.Add(settlementReport); - } - - return settlementList; - } - - public void ByHeaderList() - { - - } - - public void BySettlementId() - { - - } - - - - private Dictionary dicTablePkBySettlementId = new Dictionary(); - private int? returnTop = null; - private List settlementIdList; - - private bool FilterOutIsProcessed { get; set; } - - public bool DescendingOrder { get; set; } - - public int ReturnTop - { - get { return (int)returnTop; } - set - { - if (value > 0) - { returnTop = value; } - else - { returnTop = null; } - } - } - - public bool ReturnTopIsSet - { - get { return returnTop != null; } - } - - private List SettlementIdList - { - get { return settlementIdList; } - set - { - if (value.Any()) - { settlementIdList = value; } - } - - } - - private bool SettlementIdListIsSet - { - get { return SettlementIdList != null; } - } - - public AmazonSettlementRead() - { - Innit(); - } - - private void Innit() - { - DescendingOrder = false; - FilterOutIsProcessed = false; - ReturnTop = 0; - settlementIdList = null; - } - - public List AllUnprocessed() - { - Innit(); - FilterOutIsProcessed = true; - return ExecuteDbQuery(); - } - - public Model.Import.AmazonSettlement BySettlementId(string settlementId) - { - Innit(); - - // create settlement list - var idList = new List(); - idList.Add(settlementId); - var settlementList = BySettlementId(idList); - - // return answer - if (settlementList == null || !settlementList.Any()) - { return null; } - else - { return settlementList.First(); } - } - - public List BySettlementId(List settlementIdList) - { - Innit(); - - if (settlementIdList == null || !settlementIdList.Any()) - { return null; } - - SettlementIdList = settlementIdList; - - return ExecuteDbQuery(); - } - - private List ExecuteDbQuery() - { - // get header info - var settlementList = ReadHeaderList(); - if (settlementList == null || !settlementList.Any()) - { - return null; - } - - // add lines to header - foreach (var item in settlementList) - { - if (!dicTablePkBySettlementId.ContainsKey(item.SettlementId)) - { - throw new Exception("This shouldnt' happen!"); - } - int tablePk = dicTablePkBySettlementId[item.SettlementId]; - - item.SettlementLineList = ReadLineList(tablePk); - } - - return settlementList; - } - - private List ReadHeaderList() - { - // build the sql string - string sqlString = "SELECT "; - - if (ReturnTopIsSet) - { - sqlString = sqlString + "TOP " + ReturnTop + " "; - } - - sqlString = sqlString + @" - ImportAmazonSettlementReportID - ,[marketplace-name] - ,[settlement-id] - ,[settlement-start-date] - ,[settlement-end-date] - ,[deposit-date] - ,[total-amount] - ,currency - ,IsProcessed - FROM tblImportAmazonSettlementReport - WHERE 1 = 1"; - - if (FilterOutIsProcessed) - { - sqlString = sqlString + @" - AND IsProcessed = 0"; - } - - // build dictionary of parameter and values - var dicSettlementIdByParameterString = new Dictionary(); - if (SettlementIdListIsSet) - { - int count = 0; - foreach (string item in SettlementIdList) - { - if (!string.IsNullOrWhiteSpace(item)) - { - count = count + 1; - string parameterString = "@settlementId" + count; - dicSettlementIdByParameterString.Add(parameterString, item); - } - } - } - - if (dicSettlementIdByParameterString.Any()) - { - int count = 0; - foreach (var item in dicSettlementIdByParameterString) - { - count = count + 1; - if (count == 1) - { - sqlString = sqlString + @" - AND ( [settlement-id] = " + item.Key; - } - else - { - sqlString = sqlString + @" - OR [settlement-id] = " + item.Key; - } - } - sqlString = sqlString + " )"; - } - - sqlString = sqlString + @" - ORDER BY [settlement-start-date] "; - if (DescendingOrder) { sqlString = sqlString + " DESC"; } - - using (SqlConnection conn = new SqlConnection(SqlConnectionString)) - { - conn.Open(); - - using (SqlCommand cmd = new SqlCommand(sqlString, conn)) - { - if (dicSettlementIdByParameterString.Any()) - { - foreach (var item in dicSettlementIdByParameterString) - { - cmd.Parameters.AddWithValue(item.Key, item.Value); - } - } - - using (var reader = cmd.ExecuteReader()) - { - if (!reader.HasRows) - { - return null; - } - - var headerList = new List(); - while (reader.Read()) - { - var header = new Model.Import.AmazonSettlement(); - - int tablePk = reader.GetInt32(0); - if (!reader.IsDBNull(1)) { header.MarketPlaceName = reader.GetString(1); } - header.SettlementId = reader.GetString(2); - header.StartDate = DateTime.SpecifyKind(reader.GetDateTime(3), DateTimeKind.Utc); - header.EndDate = DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc); - header.DepositDate = DateTime.SpecifyKind(reader.GetDateTime(5), DateTimeKind.Utc); - header.TotalAmount = reader.GetDecimal(6); - header.CurrencyCode = reader.GetString(7); - header.IsProcessed = reader.GetBoolean(8); - - // update dictionary - if (!dicTablePkBySettlementId.ContainsKey(header.SettlementId)) - { - dicTablePkBySettlementId.Add(header.SettlementId, tablePk); - } - - // add header to list - headerList.Add(header); - } - - return headerList; - } - } - } - } - - private List ReadLineList(int settlementPk) - { - using (SqlConnection conn = new SqlConnection(SqlConnectionString)) - { - conn.Open(); - - using (SqlCommand cmd = new SqlCommand(@" - SELECT ImportAmazonSettlementReportLineID - ,[transaction-type] - ,[order-id] - ,[merchant-order-id] - ,[adjustment-id] - ,[shipment-id] - ,[marketplace-name] - ,[amount-type] - ,[amount-description] - ,amount - ,currency - ,[fulfillment-id] - ,[posted-date-time] - ,[order-item-code] - ,[merchant-order-item-id] - ,[merchant-adjustment-item-id] - ,sku - ,[quantity-purchased] - ,[promotion-id] - ,IsProcessed - ,ExportAccountInvoiceLineID - FROM tblImportAmazonSettlementReportLine - WHERE ImportAmazonSettlementReportID = @settlementPk - ORDER BY [posted-date-time] - ", conn)) - { - cmd.Parameters.AddWithValue("@settlementPk", settlementPk); - - using (var reader = cmd.ExecuteReader()) - { - if (!reader.HasRows) - { - return null; - } - - var lineList = new List(); - while (reader.Read()) - { - var line = new Model.Import.AmazonSettlement.SettlementLine(); - - int tablePk = reader.GetInt32(0); - line.TransactionType = reader.GetString(1); - if (!reader.IsDBNull(2)) { line.OrderId = reader.GetString(2); } - if (!reader.IsDBNull(3)) { line.MerchantOrderId = reader.GetString(3); } - if (!reader.IsDBNull(4)) { line.AdjustmentId = reader.GetString(4); } - if (!reader.IsDBNull(5)) { line.ShipmentId = reader.GetString(5); } - if (!reader.IsDBNull(6)) { line.MarketPlaceName = reader.GetString(6); } - line.AmountType = reader.GetString(7); - line.AmountDescription = reader.GetString(8); - line.Amount = reader.GetDecimal(9); - line.CurrenyCode = reader.GetString(10); - if (!reader.IsDBNull(11)) { line.FulfillmentId = reader.GetString(11); } - line.PostDateTime = DateTime.SpecifyKind(reader.GetDateTime(12), DateTimeKind.Utc); - if (!reader.IsDBNull(13)) { line.OrderItemCode = reader.GetString(13); } - if (!reader.IsDBNull(14)) { line.MerchantOrderItemId = reader.GetString(14); } - if (!reader.IsDBNull(15)) { line.MerchantAdjustmentItemId = reader.GetString(15); } - if (!reader.IsDBNull(16)) { line.Sku = reader.GetString(16); } - if (!reader.IsDBNull(17)) { line.QuantityPurchased = reader.GetInt32(17); } - if (!reader.IsDBNull(18)) { line.PromotionId = reader.GetString(18); } - line.IsProcessed = reader.GetBoolean(19); - if (!reader.IsDBNull(20)) { int exportAccountInvoiceLineId = reader.GetInt32(20); } - - lineList.Add(line); - } - return lineList; - } - } - } - } - } -} \ No newline at end of file diff --git a/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementUpdate.cs b/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementUpdate.cs deleted file mode 100644 index 41a4c7b..0000000 --- a/src/bnhtrade.Core/Data/Database/Import/AmazonSettlementUpdate.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace bnhtrade.Core.Data.Database.Import -{ - public class AmazonSettlementUpdate : Connection - { - public AmazonSettlementUpdate() - { - - } - - public void SetIsProcessedTrue(List settlementIdList) - { - if (settlementIdList == null || !settlementIdList.Any()) - { - throw new Exception("Settlement ID list is empty."); - } - - string sqlString = @" - UPDATE tblImportAmazonSettlementReport - SET IsProcessed = 1 - WHERE (1=0)"; - - for (int i = 0; i < settlementIdList.Count(); i++) - { - sqlString += @" - OR ([settlement-id] = @settlementId" + i + ")"; - } - - using (SqlConnection conn = new SqlConnection(SqlConnectionString)) - { - conn.Open(); - - using (SqlCommand cmd = new SqlCommand(sqlString, conn)) - { - for (int i = 0; i < settlementIdList.Count(); i++) - { - cmd.Parameters.AddWithValue("@settlementId" + i, settlementIdList[i]); - } - - if (cmd.ExecuteNonQuery() == 0) - { - throw new Exception("Something went wrong updating settlement status."); - } - } - } - } - } -} diff --git a/src/bnhtrade.Core/Data/Database/Programmability/Sequence.cs b/src/bnhtrade.Core/Data/Database/Programmability/Sequence.cs deleted file mode 100644 index 58045a9..0000000 --- a/src/bnhtrade.Core/Data/Database/Programmability/Sequence.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace bnhtrade.Core.Data.Database.Programmability -{ - public class Sequence : Connection - { - public Sequence () - { - - } - public int GetNext(string sequenceName) - { - if (string.IsNullOrWhiteSpace(sequenceName)) - { - throw new Exception("Sequence name is null or whitespace."); - } - - using (SqlConnection conn = new SqlConnection(SqlConnectionString)) - { - conn.Open(); - - using (SqlCommand cmd = new SqlCommand(@" - SELECT NEXT VALUE FOR " + sequenceName - , conn)) - { - //cmd.Parameters.AddWithValue("@sequenceName", sequenceName); - // it wouldn't let me use parameters - - object obj = cmd.ExecuteScalar(); - - try - { - //string whaaaat = (string)obj; - return Convert.ToInt32(obj); - } - catch (Exception ex) - { - throw new Exception("Error returning next value in sequence: " + ex.Message); - } - } - } - } - } -} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/CurrencyRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/CurrencyRepository.cs new file mode 100644 index 0000000..efb7201 --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/CurrencyRepository.cs @@ -0,0 +1,267 @@ +using bnhtrade.Core.Data.Database.Repository.Interface; +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.Repository.Implementation +{ + internal class CurrencyRepository : _Base, ICurrencyRepository + { + public CurrencyRepository(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction) + { + } + + /// + /// Returns excahnge rate, in decimal format, for a given currency and datetime + /// + /// currency code + /// dat and time + /// + public decimal? ReadExchangeRate(Model.Account.CurrencyCode currencyCode, DateTime date) + { + string sql = @" + SELECT CurrencyUnitsPerGBP + FROM tblAccountExchangeRate + WHERE CurrencyCode=@currencyCode AND StartDate<=@conversionDate AND EndDate>@conversionDate + "; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@currencyCode", currencyCode.ToString()); + cmd.Parameters.AddWithValue("@conversionDate", date); + + object result = cmd.ExecuteScalar(); + if (result != null) + { + return Convert.ToDecimal(result); + } + else + { + return null; + } + } + } + + public List ReadExchangeRate(List currencyCodeList = null, DateTime date = default(DateTime)) + { + //throw new NotImplementedException("Complete, but untested"); + + var returnList = new List(); + + string sql = @" + SELECT AccountExchangeRateID, ExchangeRateSource, CurrencyCode, CurrencyUnitsPerGBP, StartDate, EndDate + FROM tblAccountExchangeRate + WHERE 1=1 "; + + if (date != default(DateTime)) + { + sql = sql + " AND (@dateTime >= StartDate AND @dateTime < endDate) "; + } + + var sqlWhere = new Data.Database.SqlWhereBuilder(); + + // create string list + List stringList = new List(); + if (currencyCodeList != null) + { + stringList = currencyCodeList.ConvertAll(f => f.ToString()); + if (stringList.Any()) + { + sqlWhere.In("CurrencyCode", stringList, "AND"); + } + } + + sql = sql + sqlWhere.SqlWhereString; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + if (date != default(DateTime)) + { + cmd.Parameters.AddWithValue("@dateTime", date); + } + if (stringList.Any()) + { + sqlWhere.AddParametersToSqlCommand(cmd); + } + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + int id = reader.GetInt32(0); + int exchangeRateSource = reader.GetInt32(1); + string currencyCodeString = reader.GetString(2); + decimal currencyUnitsPerGBP = reader.GetDecimal(3); + DateTime startDate = DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc); + DateTime endDate = DateTime.SpecifyKind(reader.GetDateTime(5), DateTimeKind.Utc); + + + // convert string to enum + if (Enum.TryParse(currencyCodeString, out Model.Account.CurrencyCode currencyCode) == false) + { + throw new Exception("Failed converting database string to enum"); + } + + var item = new Model.Account.CurrencyExchangeRate( + currencyCode + , exchangeRateSource + , currencyUnitsPerGBP + , startDate + , endDate + ); + + returnList.Add(item); + } + } + } + return returnList; + } + + public List ReadExchangeRateLatest(List currencyCodeList = null) + { + var returnList = new List(); + + string sql = @" + SELECT t1.AccountExchangeRateID, t1.ExchangeRateSource, t1.CurrencyCode, t1.CurrencyUnitsPerGBP, t1.StartDate, t1.EndDate, + t2.maxdate + FROM tblAccountExchangeRate AS t1 + INNER JOIN + (SELECT max(StartDate) AS maxdate, + CurrencyCode + FROM tblAccountExchangeRate + GROUP BY CurrencyCode + "; + + // add any filters + var sqlWhere = new Data.Database.SqlWhereBuilder(); + var codeStringList = new List(); + if (currencyCodeList != null && currencyCodeList.Any() == true) + { + // convert to string list + foreach (var currencyCode in currencyCodeList) + { + codeStringList.Add(currencyCode.ToString()); + } + + // add to where statement + sqlWhere.In("CurrencyCode", codeStringList, "HAVING"); + sql = sql + sqlWhere.SqlWhereString; + } + + sql = sql + @" ) AS t2 + ON t1.CurrencyCode = t2.CurrencyCode + AND t1.StartDate = t2.maxdate + ORDER BY t1.CurrencyCode;"; + + //query db + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + if (codeStringList.Any()) + { + sqlWhere.AddParametersToSqlCommand(cmd); + } + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + int id = reader.GetInt32(0); + int exchangeRateSource = reader.GetInt32(1); + string currencyCodeString = reader.GetString(2); + decimal currencyUnitsPerGBP = reader.GetDecimal(3); + DateTime startDate = DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc); + DateTime endDate = DateTime.SpecifyKind(reader.GetDateTime(5), DateTimeKind.Utc); + + + // convert string to enum + if (Enum.TryParse(currencyCodeString, out Model.Account.CurrencyCode currencyCode) == false) + { + throw new Exception("Failed converting database string to enum"); + } + + var item = new Model.Account.CurrencyExchangeRate( + currencyCode + , exchangeRateSource + , currencyUnitsPerGBP + , startDate + , endDate + ); + + returnList.Add(item); + } + } + } + return returnList; + } + + public int InsertExchangeRate(int exchangeRateSource, Model.Account.CurrencyCode currencyCode, + decimal currencyUnitsPerGbp, DateTime periodStartUtc, DateTime periodEnd, bool checkOverride = false) + { + // checks + if (periodStartUtc.Kind != DateTimeKind.Utc || periodEnd.Kind != DateTimeKind.Utc) + { + throw new FormatException("Currency date time kind must be UTC"); + } + + currencyUnitsPerGbp = decimal.Round(currencyUnitsPerGbp, 4); + + // CHECKS + if (periodEnd <= periodStartUtc) + { + throw new Exception("Invalid date period."); + } + + if (checkOverride == false && (periodEnd - periodStartUtc).Days > 31) + { + throw new Exception("Date period is greater than 31 days."); + } + + string sql = @" + INSERT INTO tblAccountExchangeRate (ExchangeRateSource, CurrencyCode, CurrencyUnitsPerGBP, StartDate, EndDate) + OUTPUT INSERTED.AccountExchangeRateID + VALUES (@exchangeRateSource, @currencyCode, @currencyUnitsPerGbp, @periodStart, @periodEnd); + "; + + // make the insert + DateTime? periodEndLast = null; + int recordId = 0; + + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@exchangeRateSource", exchangeRateSource); + cmd.Parameters.AddWithValue("@currencyCode", currencyCode.ToString()); + cmd.Parameters.AddWithValue("@currencyUnitsPerGbp", currencyUnitsPerGbp); + cmd.Parameters.AddWithValue("@periodStart", periodStartUtc); + cmd.Parameters.AddWithValue("@periodEnd", periodEnd); + + recordId = (int)cmd.ExecuteScalar(); + + if (recordId < 1) + { + throw new Exception("Error inserting record, did not retrive new record ID."); + } + } + + return recordId; + } + + } +} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/ExportInvoiceRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/ExportInvoiceRepository.cs new file mode 100644 index 0000000..b8846d0 --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/ExportInvoiceRepository.cs @@ -0,0 +1,451 @@ +using bnhtrade.Core.Data.Database.Repository.Interface; +using bnhtrade.Core.Logic.Validate; +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; + +namespace bnhtrade.Core.Data.Database.Repository.Implementation +{ + internal class ExportInvoiceRepository : _Base, IExportInvoiceRepository + { + public ExportInvoiceRepository(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction) + { + } + + // + // invoice Insert methods + // + + /// + /// Don't call this directly, use the logic layer instead (for validation and other checks). + /// + /// list of sales invoices to insert + /// dectionary, key= invoice id, value= new invoice + public Dictionary InsertSalesInvoices(IEnumerable invoiceList) + { + var result = InsertBaseExecute(invoiceList); + var returnList = new Dictionary(); + foreach (var item in result) + { + returnList.Add(item.Key, (Model.Account.SalesInvoice)item.Value); + } + return returnList; + } + + private Dictionary InsertBaseExecute(IEnumerable invoiceList) + { + if (invoiceList == null) + { + throw new ArgumentException("Invoice list is null"); + } + + var returnList = new Dictionary(); + int invoiceCount = invoiceList.Count(); + + // make the inserts + foreach (var invoice in invoiceList) + { + int? invoiceId = null; + string sql = @" + INSERT INTO tblExportAccountInvoice ( + ExportAccountInvoiceTypeID + ,Contact + ,InvoiceDate + ,InvoiceDueDate + ,InvoiceNumber + ,Reference + ,CurrencyCode + ,LineUnitAmountIsTaxExclusive + ,InvoiceAmount + ,IsComplete + ) + OUTPUT INSERTED.ExportAccountInvoiceID + VALUES ( + @invoiceTypeId + ,@contact + ,@invoiceDate + ,@invoiceDueDate + ,@invoiceNumber + ,@reference + ,@currencyCode + ,@lineUnitAmountIsTaxExclusive + ,@invoiceAmount + ,@markComplete + );"; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@invoiceTypeId", (int)invoice.InvoiceType); + cmd.Parameters.AddWithValue("@contact", invoice.ContactName); + cmd.Parameters.AddWithValue("@invoiceDate", invoice.InvoiceDate); + cmd.Parameters.AddWithValue("@invoiceDueDate", invoice.InvoiceDueDate); + cmd.Parameters.AddWithValue("@reference", invoice.InvoiceReference); + cmd.Parameters.AddWithValue("@currencyCode", invoice.InvoiceCurrencyCode.ToString()); + cmd.Parameters.AddWithValue("@lineUnitAmountIsTaxExclusive", invoice.InvoiceLineUnitAmountIsTaxExclusive); + cmd.Parameters.AddWithValue("@markComplete", false); + cmd.Parameters.AddWithValue("@invoiceNumber", invoice.InvoiceNumber); + cmd.Parameters.AddWithValue("@invoiceAmount", invoice.InvoiceTotalAmount); + + invoiceId = (int)cmd.ExecuteScalar(); + } + + foreach (var line in invoice.InvoiceLineList) + { + string lineSql = @" + INSERT INTO tblExportAccountInvoiceLine ( + ExportAccountInvoiceID + ,AccountInvoiceLineItemID + ,NetAmount + ,AccountChartOfID + ,TaxAmount + ,AccountTaxCodeID + ) + OUTPUT INSERTED.ExportAccountInvoiceLineID + VALUES ( + @invoiceId + ,( + SELECT AccountInvoiceLineItemID + FROM tblAccountInvoiceLineItem + WHERE ItemCode = @itemCode + ) + ,@netAmount + ,( + SELECT AccountChartOfID + FROM tblAccountChartOf + WHERE AccountCode = @accountCode + ) + ,@taxAmount + ,( + SELECT AccountTaxCodeID + FROM tblAccountTaxCode + WHERE TaxCode = @taxCode + ) + );"; + + // insert record + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) // Use _connection.CreateCommand() + { + cmd.CommandText = lineSql; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@invoiceID", invoiceId); + cmd.Parameters.AddWithValue("@itemCode", line.ItemCode); + cmd.Parameters.AddWithValue("@netAmount", line.UnitAmount); + cmd.Parameters.AddWithValue("@accountCode", (int)line.Account.AccountCode); + cmd.Parameters.AddWithValue("@taxAmount", line.TaxAmountTotal); + cmd.Parameters.AddWithValue("@taxCode", line.TaxCode.TaxCode); + + int lineId = (int)cmd.ExecuteScalar(); + } + } + returnList.Add(invoiceId.Value, invoice); + } + + if (returnList.Count != invoiceCount) + { + throw new Exception("Not all invoices were inserted into the export table. Expected: " + invoiceCount + ", Actual: " + returnList.Count); + } + else + { + return returnList; + } + } + + // + // invoice read methods + // + + /// + /// Returns list of invoice numbers that have not yet been exported (i.e. IsComplete=True) + /// + /// Sales or Purchase + /// Dictionary where key=ExportAccountInvoiceID, value=InvoiceNumber + public Dictionary GetNewInvoiceNumbers(Model.Account.InvoiceType invoiceType) + { + var returnList = new Dictionary(); + + string sql = @" + SELECT tblExportAccountInvoice.ExportAccountInvoiceID, [InvoiceNumber] + FROM tblExportAccountInvoice + WHERE (tblExportAccountInvoice.IsComplete = 0) + AND (tblExportAccountInvoice.ExportAccountInvoiceTypeID = @invoiceType);"; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@invoiceType", (int)invoiceType); + + using (SqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + returnList.Add(reader.GetInt32(0), reader.GetString(1)); + } + } + } + return returnList; + } + + /// + /// Get list of invoices by invoice id + /// + /// list of invoice ids + /// dictionary key=id, value=invoice-model + public Dictionary GetSalesInvoiceById(IEnumerable idList) + { + var returnList = new Dictionary(); + + if (idList.Any() == false) + { + return returnList; + } + + // get the account and tax code objects + var taxcode = new Data.Database.Account.ReadTaxCode().GetAllActive(); + var account = new Data.Database.Account.ReadAccountCode().All(); + + // build sql string + string sql = @" + SELECT tblExportAccountInvoice.ExportAccountInvoiceID + , tblExportAccountInvoice.ExportAccountInvoiceTypeID + , tblExportAccountInvoice.Contact + , tblExportAccountInvoice.InvoiceNumber + , tblExportAccountInvoice.InvoiceDate + , tblExportAccountInvoice.InvoiceDueDate + , tblExportAccountInvoice.Reference + , tblExportAccountInvoice.CurrencyCode + , tblExportAccountInvoice.InvoiceAmount + , tblExportAccountInvoice.IsComplete + , tblExportAccountInvoiceLine.ExportAccountInvoiceLineID + , tblAccountInvoiceLineItem.ItemName + , tblExportAccountInvoiceLine.AccountChartOfID + , tblExportAccountInvoiceLine.NetAmount + , tblExportAccountInvoiceLine.AccountTaxCodeID + , tblExportAccountInvoiceLine.TaxAmount + , tblExportAccountInvoice.LineUnitAmountIsTaxExclusive + FROM tblExportAccountInvoice + INNER JOIN + tblExportAccountInvoiceLine + ON tblExportAccountInvoice.ExportAccountInvoiceID = tblExportAccountInvoiceLine.ExportAccountInvoiceID + INNER JOIN + tblAccountInvoiceLineItem + ON tblExportAccountInvoiceLine.AccountInvoiceLineItemID = tblAccountInvoiceLineItem.AccountInvoiceLineItemID + "; + + var sqlWhere = new Data.Database.SqlWhereBuilder(); + sqlWhere.In("tblExportAccountInvoice.ExportAccountInvoiceID", idList, "WHERE"); + + sql = sql + sqlWhere.SqlWhereString + " ORDER BY tblExportAccountInvoice.ExportAccountInvoiceID, tblExportAccountInvoiceLine.ExportAccountInvoiceLineID; "; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + sqlWhere.AddParametersToSqlCommand(cmd); + + using (SqlDataReader reader = cmd.ExecuteReader()) + { + if (reader.HasRows == false) + { + return returnList; + } + + int? invoiceId = null; + int? previousInvoiceId = null; + Model.Account.SalesInvoice invoice = null; + + while (reader.Read()) + { + invoiceId = reader.GetInt32(0); + + // test for first/next invoice + if (invoice == null || previousInvoiceId.Value != invoiceId.Value) + { + // if next invoice, add previous to return list + if (previousInvoiceId.HasValue) + { + // invoice complete, add to return list + returnList.Add(previousInvoiceId.Value, invoice); + } + + // add header info to new invoice + invoice = new Model.Account.SalesInvoice(reader.GetBoolean(16)); + int invoiceType = reader.GetInt32(1); + invoice.ContactName = reader.GetString(2); + invoice.InvoiceNumber = reader.GetString(3); + invoice.InvoiceDate = reader.GetDateTime(4); + invoice.InvoiceDueDate = reader.GetDateTime(5); + if (!reader.IsDBNull(6)) { invoice.InvoiceReference = reader.GetString(6); } + invoice.InvoiceCurrencyCode = Enum.Parse(reader.GetString(7)); + if (!reader.IsDBNull(8)) { invoice.InvoiceTotalAmount = reader.GetDecimal(8); } + bool isComplete = reader.GetBoolean(9); + + // set credit note + if (invoice.InvoiceTotalAmount < 0) + { + invoice.IsCreditNote = true; + } + } + + // add line info + var invoiceLine = new Model.Account.SalesInvoice.InvoiceLine(invoice); + int lineId = reader.GetInt32(10); + invoiceLine.ItemCode = reader.GetString(11); + invoiceLine.Account = account[Convert.ToUInt32(reader.GetInt32(12))]; + invoiceLine.UnitAmount = reader.GetDecimal(13); + invoiceLine.Quantity = 1; + invoiceLine.TaxCode = taxcode[reader.GetByte(14)]; + invoiceLine.SetTaxAdjustmentByTaxTotal(reader.GetDecimal(15)); + + invoice.InvoiceLineList.Add(invoiceLine); + previousInvoiceId = invoiceId.Value; + } + + // complete final invoice and add to list + returnList.Add(invoiceId.Value, invoice); + } + } + return returnList; + } + + // + // invoice update methods + // + + /// + /// Update the 'IsComplete' field by invoice id + /// + /// key=ExportAccountInvoiceTypeID, value=IsComplete + /// Number of row effected + public int SetInvoiceIsCompleteValue(Dictionary updateDictionary) + { + int returnCount = 0; + + foreach (var item in updateDictionary) + { + string sql = @" + UPDATE + tblExportAccountInvoice + SET + IsComplete = @isComplete + WHERE + ExportAccountInvoiceID = @id + "; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@id", item.Key); + cmd.Parameters.AddWithValue("@isComplete", item.Value); + + returnCount = returnCount + cmd.ExecuteNonQuery(); + } + } + return returnCount; + } + + // + // Invoice Delete Methods + // + + public int DeleteInvoiceLine(int invoiceLineId) + { + // only being able to delete one line at a time is a design/safety decision + // otherwise, delete whole invoice + if (invoiceLineId <= 0) + { + throw new ArgumentException("InvoiceLineId must be greater than zero."); + } + Core.Data.Database.SqlWhereBuilder sqlWhereBuilder = new Core.Data.Database.SqlWhereBuilder(); + sqlWhereBuilder.In("ExportAccountInvoiceLineID", new List { invoiceLineId }); + return DeleteInvoiceExecuteInvoiceLine(sqlWhereBuilder); + } + + public int DeleteInvoice(IEnumerable invoiceIdList) + { + if (invoiceIdList == null || invoiceIdList.Count() == 0) + { + throw new ArgumentException("InvoiceIdList is empty, nothing to delete."); + } + + int invoiceToDelete = invoiceIdList.Distinct().Count(); + int invoiceEffected = 0; + + // delete lines first + Core.Data.Database.SqlWhereBuilder sqlWhereBuilder = new Core.Data.Database.SqlWhereBuilder(); + sqlWhereBuilder.In("ExportAccountInvoiceID", invoiceIdList); + int lineCount = DeleteInvoiceExecuteInvoiceLine(sqlWhereBuilder); + + // then delete invoice + sqlWhereBuilder = new Core.Data.Database.SqlWhereBuilder(); + sqlWhereBuilder.In("ExportAccountInvoiceID", invoiceIdList); + invoiceEffected = DeleteInvoiceExecuteInvoice(sqlWhereBuilder); + + if (invoiceEffected == invoiceToDelete) + { + return invoiceEffected; + } + else + { + throw new Exception("Error: " + + invoiceToDelete + " number invoices requested for deletion, " + + invoiceEffected + " number invoices effected. Changes rolled back."); + } + } + + /// + /// Helper method Only intended for use with ExecuteInvoice() or ExecuteInvoiceLine() + /// + /// the full sql statement + /// instance of the sql where builder helper + /// Number of row effected (deleted) + private int DeleteInvoiceExecuteHelper(string sql, Core.Data.Database.SqlWhereBuilder sqlWhereBuilder) + { + // Only intended for ExecuteInvoice() or ExecuteInvoiceLine() + + // important checks + if (sqlWhereBuilder.IsSetSqlWhereString == false) + { + throw new ArgumentException("SqlWhereBuilder is not set, set the SqlWhereBuilder before calling this method."); + } + if (sqlWhereBuilder.ParameterListCount == 0) + { + throw new Exception("Parameter list count is zero, this could delete all records in table, operation cancelled"); + } + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; // Assign the shared transaction + sqlWhereBuilder.AddParametersToSqlCommand(cmd); + + return cmd.ExecuteNonQuery(); + } + } + + private int DeleteInvoiceExecuteInvoice(Core.Data.Database.SqlWhereBuilder sqlWhereBuilder) + { + string sql = "DELETE FROM tblExportAccountInvoice WHERE " + sqlWhereBuilder.SqlWhereString; + return DeleteInvoiceExecuteHelper(sql, sqlWhereBuilder); + } + + private int DeleteInvoiceExecuteInvoiceLine(Core.Data.Database.SqlWhereBuilder sqlWhereBuilder) + { + string sql = "DELETE FROM tblExportAccountInvoiceLine WHERE " + sqlWhereBuilder.SqlWhereString; + return DeleteInvoiceExecuteHelper(sql, sqlWhereBuilder); + } + } +} \ No newline at end of file diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/ImportAmazonRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/ImportAmazonRepository.cs new file mode 100644 index 0000000..e122a63 --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/ImportAmazonRepository.cs @@ -0,0 +1,713 @@ +using bnhtrade.Core.Data.Database._BoilerPlate; +using bnhtrade.Core.Data.Database.Repository.Interface; +using bnhtrade.Core.Model.Amazon; +using Microsoft.Data.SqlClient; +using Microsoft.VisualBasic.Logging; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; + +namespace bnhtrade.Core.Data.Database.Repository.Implementation +{ + internal class ImportAmazonRepository : _Base, IImportAmazonRepository + { + Logic.Log.LogEvent _log = new Logic.Log.LogEvent(); + + public ImportAmazonRepository(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction) + { + } + + // + // Read Amazon Settlement Header Information + // + + public List ReadAmazonSettlementHeaderInfoBySettlementId(List settlementIdList) + { + if (settlementIdList == null || !settlementIdList.Any()) + { return new List(); } + + return ReadAmazonSettlementHeaders(settlementIdList); + } + + public List ReadAmazonSettlementHeaderInfoBySpapiReportId(List spapiReportIdList) + { + if (spapiReportIdList == null || !spapiReportIdList.Any()) + { return new List(); } + + return ReadAmazonSettlementHeaders(null, spapiReportIdList); + } + + private List ReadAmazonSettlementHeaders( + List settlementIdList = null, List spapiReportIdList = null, bool filterOutIsProcessed = false, + bool DescendingOrder = false, int? returnTop = null) + { + var returnHeaderList = new List(); + Dictionary dicTablePkBySettlementId = new Dictionary(); + + + // build the sql string + string sqlString = "SELECT "; + + if (returnTop != null) + { + sqlString = sqlString + "TOP " + returnTop.Value + " "; + } + + sqlString = sqlString + @" + ImportAmazonSettlementReportID + ,[marketplace-name] + ,[settlement-id] + ,[settlement-start-date] + ,[settlement-end-date] + ,[deposit-date] + ,[total-amount] + ,currency + ,IsProcessed + ,SpapiReportId + FROM tblImportAmazonSettlementReport + WHERE 1 = 1"; + + if (filterOutIsProcessed) + { + sqlString = sqlString + @" + AND IsProcessed = 0"; + } + + // build dictionary of parameter and values for settlementid + var dicSettlementIdByParameterString = new Dictionary(); + if (settlementIdList != null) + { + int count = 0; + foreach (string item in settlementIdList) + { + if (!string.IsNullOrWhiteSpace(item)) + { + count = count + 1; + string parameterString = "@settlementId" + count; + dicSettlementIdByParameterString.Add(parameterString, item); + } + } + } + + if (dicSettlementIdByParameterString.Any()) + { + int count = 0; + foreach (var item in dicSettlementIdByParameterString) + { + count = count + 1; + if (count == 1) + { + sqlString = sqlString + @" + AND ( [settlement-id] = " + item.Key; + } + else + { + sqlString = sqlString + @" + OR [settlement-id] = " + item.Key; + } + } + sqlString = sqlString + " )"; + } + + // build dictionary of parameter and values for SP-API Report Id + var dicSpapiReportIdByParameterString = new Dictionary(); + if (spapiReportIdList != null && spapiReportIdList.Any()) + { + int count = 0; + foreach (string item in spapiReportIdList) + { + if (!string.IsNullOrWhiteSpace(item)) + { + count = count + 1; + string parameterString = "@SpapiReportId" + count; + dicSpapiReportIdByParameterString.Add(parameterString, item); + } + } + } + + if (dicSpapiReportIdByParameterString.Any()) + { + int count = 0; + foreach (var item in dicSpapiReportIdByParameterString) + { + count = count + 1; + if (count == 1) + { + sqlString = sqlString + @" + AND ( SpapiReportId = " + item.Key; + } + else + { + sqlString = sqlString + @" + OR SpapiReportId = " + item.Key; + } + } + sqlString = sqlString + " )"; + } + + + sqlString = sqlString + @" + ORDER BY [settlement-start-date] "; + if (DescendingOrder) { sqlString = sqlString + " DESC"; } + + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sqlString; + cmd.Transaction = _transaction as SqlTransaction; + + if (dicSettlementIdByParameterString.Any()) + { + foreach (var item in dicSettlementIdByParameterString) + { + cmd.Parameters.AddWithValue(item.Key, item.Value); + } + } + + if (dicSpapiReportIdByParameterString.Any()) + { + foreach (var item in dicSpapiReportIdByParameterString) + { + cmd.Parameters.AddWithValue(item.Key, item.Value); + } + } + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + var header = new Model.Import.AmazonSettlementHeader(); + + int tablePk = reader.GetInt32(0); + if (!reader.IsDBNull(1)) { header.MarketPlace = MarketPlaceEnumExtensions.FromMarketplaceUrl(reader.GetString(1)); } + header.SettlementId = reader.GetString(2); + header.StartDate = DateTime.SpecifyKind(reader.GetDateTime(3), DateTimeKind.Utc); + header.EndDate = DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc); + header.DepositDate = DateTime.SpecifyKind(reader.GetDateTime(5), DateTimeKind.Utc); + header.TotalAmount = reader.GetDecimal(6); + header.CurrencyCode = reader.GetString(7); + header.IsProcessed = reader.GetBoolean(8); + if (!reader.IsDBNull(9)) { header.SpapiReportId = reader.GetString(9); } + + // update dictionary + if (!dicTablePkBySettlementId.ContainsKey(header.SettlementId)) + { + dicTablePkBySettlementId.Add(header.SettlementId, tablePk); + } + + // add header to list + returnHeaderList.Add(header); + } + } + } + return returnHeaderList; + } + + // + // Read Amazon Settlement Report + // + + /// + /// Gets Amazon settlement report information from the database. + /// + /// Dictionary where key=id and value=settlement + public Dictionary ReadAmazonSettlements( + List settlementIdList = null, List marketPlaceNameList = null, bool? isProcessed = null, + bool descendingOrder = false, int? returnTop = null ) + { + var returnList = new Dictionary(); + var whereBuilder = new Data.Database.SqlWhereBuilder(); + + // build the sql string + string sqlString = "SELECT "; + + if (returnTop != null) + { + sqlString = sqlString + "TOP " + returnTop.Value + " "; + } + + sqlString = sqlString + @" + tblImportAmazonSettlementReport.ImportAmazonSettlementReportID, + tblImportAmazonSettlementReport.[marketplace-name], + tblImportAmazonSettlementReport.[settlement-id], + tblImportAmazonSettlementReport.[settlement-start-date], + tblImportAmazonSettlementReport.[settlement-end-date], + tblImportAmazonSettlementReport.[deposit-date], + tblImportAmazonSettlementReport.[total-amount], + tblImportAmazonSettlementReport.currency, + tblImportAmazonSettlementReport.IsProcessed, + tblImportAmazonSettlementReport.SpapiReportId, + tblImportAmazonSettlementReportLine.ImportAmazonSettlementReportLineID, + tblImportAmazonSettlementReportLine.[transaction-type], + tblImportAmazonSettlementReportLine.[order-id], + tblImportAmazonSettlementReportLine.[merchant-order-id], + tblImportAmazonSettlementReportLine.[adjustment-id], + tblImportAmazonSettlementReportLine.[shipment-id], + tblImportAmazonSettlementReportLine.[marketplace-name] AS Expr1, + tblImportAmazonSettlementReportLine.[amount-type], + tblImportAmazonSettlementReportLine.[amount-description], + tblImportAmazonSettlementReportLine.amount, + tblImportAmazonSettlementReportLine.currency, + tblImportAmazonSettlementReportLine.[fulfillment-id], + tblImportAmazonSettlementReportLine.[posted-date-time], + tblImportAmazonSettlementReportLine.[order-item-code], + tblImportAmazonSettlementReportLine.[merchant-order-item-id], + tblImportAmazonSettlementReportLine.[merchant-adjustment-item-id], + tblImportAmazonSettlementReportLine.sku, + tblImportAmazonSettlementReportLine.[quantity-purchased], + tblImportAmazonSettlementReportLine.[promotion-id], + tblImportAmazonSettlementReportLine.IsProcessed, + tblImportAmazonSettlementReportLine.ExportAccountInvoiceLineID + FROM tblImportAmazonSettlementReport + INNER JOIN + tblImportAmazonSettlementReportLine + ON tblImportAmazonSettlementReport.ImportAmazonSettlementReportID = tblImportAmazonSettlementReportLine.ImportAmazonSettlementReportID + WHERE 1 = 1 "; + + if (isProcessed != null) + { + if (isProcessed.Value == true) + { + sqlString = sqlString + @" + AND tblImportAmazonSettlementReport.IsProcessed = 1"; + } + else + { + sqlString = sqlString + @" + AND tblImportAmazonSettlementReport.IsProcessed = 0"; + } + } + + if (settlementIdList != null && settlementIdList.Any() == true) + { + whereBuilder.In("tblImportAmazonSettlementReport.ImportAmazonSettlementReportID", settlementIdList, "AND"); + } + + if (marketPlaceNameList != null && marketPlaceNameList.Any() == true) + { + whereBuilder.In("tblImportAmazonSettlementReport.[marketplace-name]", marketPlaceNameList, "AND"); + } + + sqlString = sqlString + whereBuilder.SqlWhereString; + + // add the order by clause(s) + sqlString = sqlString + @" + ORDER BY tblImportAmazonSettlementReport.[marketplace-name] ASC, tblImportAmazonSettlementReport.[settlement-start-date] "; + if (descendingOrder) { sqlString = sqlString + " DESC"; } + else { sqlString = sqlString + " ASC"; } + sqlString = sqlString + ", tblImportAmazonSettlementReport.ImportAmazonSettlementReportID ASC, tblImportAmazonSettlementReportLine.[posted-date-time] ASC, " + + "tblImportAmazonSettlementReportLine.ImportAmazonSettlementReportLineID ASC;"; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sqlString; + cmd.Transaction = _transaction as SqlTransaction; + + whereBuilder.AddParametersToSqlCommand(cmd); + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + int tableId = reader.GetInt32(0); + + if (returnList.ContainsKey(tableId) == false) + { + var settlement = new Model.Import.AmazonSettlement(); + + if (!reader.IsDBNull(1)) { settlement.MarketPlace = MarketPlaceEnumExtensions.FromMarketplaceUrl(reader.GetString(1)); } + settlement.SettlementId = reader.GetString(2); + settlement.StartDate = DateTime.SpecifyKind(reader.GetDateTime(3), DateTimeKind.Utc); + settlement.EndDate = DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc); + settlement.DepositDate = DateTime.SpecifyKind(reader.GetDateTime(5), DateTimeKind.Utc); + settlement.TotalAmount = reader.GetDecimal(6); + settlement.CurrencyCode = reader.GetString(7); + settlement.IsProcessed = reader.GetBoolean(8); + if (!reader.IsDBNull(9)) { settlement.SpapiReportId = reader.GetString(9); } + + returnList.Add(tableId, settlement); + } + + // add lines to settlement + var line = new Model.Import.AmazonSettlement.SettlementLine(); + + line.TransactionType = reader.GetString(11); + if (!reader.IsDBNull(12)) { line.OrderId = reader.GetString(12); } + if (!reader.IsDBNull(13)) { line.MerchantOrderId = reader.GetString(13); } + if (!reader.IsDBNull(14)) { line.AdjustmentId = reader.GetString(14); } + if (!reader.IsDBNull(15)) { line.ShipmentId = reader.GetString(15); } + if (!reader.IsDBNull(16)) { line.MarketPlaceName = reader.GetString(16); } + line.AmountType = reader.GetString(17); + line.AmountDescription = reader.GetString(18); + line.Amount = reader.GetDecimal(19); + line.CurrenyCode = reader.GetString(20); + if (!reader.IsDBNull(21)) { line.FulfillmentId = reader.GetString(21); } + line.PostDateTime = DateTime.SpecifyKind(reader.GetDateTime(22), DateTimeKind.Utc); + if (!reader.IsDBNull(23)) { line.OrderItemCode = reader.GetString(23); } + if (!reader.IsDBNull(24)) { line.MerchantOrderItemId = reader.GetString(24); } + if (!reader.IsDBNull(25)) { line.MerchantAdjustmentItemId = reader.GetString(25); } + if (!reader.IsDBNull(26)) { line.Sku = reader.GetString(26); } + if (!reader.IsDBNull(27)) { line.QuantityPurchased = reader.GetInt32(27); } + if (!reader.IsDBNull(28)) { line.PromotionId = reader.GetString(28); } + line.IsProcessed = reader.GetBoolean(29); + if (!reader.IsDBNull(30)) { line.ExportAccountInvoiceLineId = reader.GetInt32(30); } + + returnList[tableId].SettlementLineList.Add(line); + } + return returnList; + } + } + } + + // + // Update Amazon Settlement Report + // + + /// + /// Set the IsProcessed flag to true or false for the specified settlement Ids. + /// + /// List of settlement id (not table id) + /// value to set the isProcessed to + /// Number of rows effected + public int SetAmazonSettlementIsProcessed(List settlementIdList, bool isProcessed) + { + if (settlementIdList == null || !settlementIdList.Any()) + { + throw new Exception("Settlement ID list is empty."); + } + + string sqlString = @" + UPDATE tblImportAmazonSettlementReport "; + + sqlString = sqlString + @" + SET IsProcessed = " + (isProcessed ? "1" : "0"); + + var whereBuilder = new Data.Database.SqlWhereBuilder(); + whereBuilder.In("tblImportAmazonSettlementReport.[settlement-id]", settlementIdList, "WHERE"); + + sqlString = sqlString + whereBuilder.SqlWhereString; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sqlString; + cmd.Transaction = _transaction as SqlTransaction; + + whereBuilder.AddParametersToSqlCommand(cmd); + + return cmd.ExecuteNonQuery(); + } + } + + /// + /// Takes a Settlement Report flat file from Amazon and imports it into the database. + /// + /// path to the Amazon report flat file + /// The unique Amazon SP-API report id (not settlement id) + /// + public bool CreateAmazonSettlements(string filePath, string reportId) + { + int settlementReportId = 0; + string settlementRef = ""; + bool marketPlaceUpdated = false; + decimal settlementAmount = 0m; + int lineNumber = 2; + int lineSkip = 0; + + using (var reader = new StreamReader(filePath)) + { + //read file one line at a time and insert data into table if required + + // read header and retrive information + string headerRow = reader.ReadLine(); + string[] headers = headerRow.Split('\t'); + + int columnCount = headers.Length; + + int indexSettlementId = Array.IndexOf(headers, "settlement-id"); + int indexSettlementStartDate = Array.IndexOf(headers, "settlement-start-date"); + int indexSettlementEndDate = Array.IndexOf(headers, "settlement-end-date"); + int indexDepositDate = Array.IndexOf(headers, "deposit-date"); + int indexTotalAmount = Array.IndexOf(headers, "total-amount"); + int indexCurrency = Array.IndexOf(headers, "currency"); + int indexTransactionType = Array.IndexOf(headers, "transaction-type"); + int indexOrderId = Array.IndexOf(headers, "order-id"); + int indexMerchantOrderId = Array.IndexOf(headers, "merchant-order-id"); + int indexAdjustmentId = Array.IndexOf(headers, "adjustment-id"); + int indexShipmentId = Array.IndexOf(headers, "shipment-id"); + int indexMarketplaceName = Array.IndexOf(headers, "marketplace-name"); + int indexAmountType = Array.IndexOf(headers, "amount-type"); + int indexAmountDescription = Array.IndexOf(headers, "amount-description"); + int indexAmount = Array.IndexOf(headers, "amount"); + int indexFulfillmentId = Array.IndexOf(headers, "fulfillment-id"); + // int indexPostedDate = Array.IndexOf(headers, "posted-date"); + int indexPostedDateTime = Array.IndexOf(headers, "posted-date-time"); + int indexOrderItemCode = Array.IndexOf(headers, "order-item-code"); + int indexMerchantOrderItemId = Array.IndexOf(headers, "merchant-order-item-id"); + int indexMerchantAdjustmentItemId = Array.IndexOf(headers, "merchant-adjustment-item-id"); + int indexSku = Array.IndexOf(headers, "sku"); + int indexQuantityPurchased = Array.IndexOf(headers, "quantity-purchased"); + int indexPromotionId = Array.IndexOf(headers, "promotion-id"); + + string currency = ""; + + string fileRow; + while ((fileRow = reader.ReadLine()) != null) + { + Console.Write("\rParsing record: " + lineNumber); + //split line into array + string[] items = fileRow.Split('\t'); + if (items.Length != columnCount) + { + // skip line + lineSkip = lineSkip + 1; + _log.LogWarning( + "Line #" + lineNumber + " skipped due to no enough element in row.", + filePath + ); + } + else if (lineNumber == 2) + { + // check if settlement has already been imported + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = "SELECT COUNT(*) FROM tblImportAmazonSettlementReport WHERE [settlement-id]=@settlementId;"; + cmd.Transaction = _transaction as SqlTransaction; + cmd.Parameters.AddWithValue("@settlementId", items[indexSettlementId]); + + int recordCount = (int)cmd.ExecuteScalar(); + if (recordCount > 0) + { + SetSpapiReportId(items[indexSettlementId], reportId); + _log.LogInformation("Settlement report already imported, skipping..."); + return true; + } + } + //set currencyId + //currencyId = GeneralQueries.GetCurrencyId(items[5]); + + //set currency + currency = items[indexCurrency]; + settlementAmount = decimal.Parse(items[indexTotalAmount].Replace(",", ".")); + + // insert + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" + INSERT INTO tblImportAmazonSettlementReport ( + [settlement-id] + ,[settlement-start-date] + ,[settlement-end-date] + ,[deposit-date] + ,[total-amount] + ,[currency] + ,SpapiReportId + ) + OUTPUT INSERTED.ImportAmazonSettlementReportID + VALUES ( + @settlementId + ,@settlementStartDate + ,@settlementEndDate + ,@depositDate + ,@settlementotalAmounttId + ,@currency + ,@reportId + );"; + + // add parameters + if (indexSettlementId == -1 || items[indexSettlementId].Length == 0) { cmd.Parameters.AddWithValue("@settlementId", DBNull.Value); } + else + { + settlementRef = items[indexSettlementId]; + cmd.Parameters.AddWithValue("@settlementId", settlementRef); + } + + var parseDateTime = new Core.Logic.Utilities.DateTime(); + + if (indexSettlementStartDate == -1 || items[indexSettlementStartDate].Length == 0) { cmd.Parameters.AddWithValue("@settlementStartDate", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@settlementStartDate", parseDateTime.ParseIsoDateTimeString(items[indexSettlementStartDate])); } + + if (indexSettlementEndDate == -1 || items[indexSettlementEndDate].Length == 0) { cmd.Parameters.AddWithValue("@settlementEndDate", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@settlementEndDate", parseDateTime.ParseIsoDateTimeString(items[indexSettlementEndDate])); } + + if (indexDepositDate == -1 || items[indexDepositDate].Length == 0) { cmd.Parameters.AddWithValue("@depositDate", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@depositDate", parseDateTime.ParseIsoDateTimeString(items[indexDepositDate])); } + + if (indexTotalAmount == -1 || items[indexTotalAmount].Length == 0) { cmd.Parameters.AddWithValue("@totalAmount", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@settlementotalAmounttId", settlementAmount); } + + if (string.IsNullOrWhiteSpace(reportId)) { cmd.Parameters.AddWithValue("@reportId", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@reportId", reportId); } + + cmd.Parameters.AddWithValue("@currency", currency); + + //if (currencyId == -1) { sqlCommand.Parameters.AddWithValue("@currencyId", DBNull.Value); } + //else { sqlCommand.Parameters.AddWithValue("@currencyId", currencyId); } + + //execute and retrive id + settlementReportId = (int)cmd.ExecuteScalar(); + + } + } + else + { + //update market place name in main table, if required + if (marketPlaceUpdated == false && settlementReportId > 0 && items[indexMarketplaceName].Length > 1) + { + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" + UPDATE tblImportAmazonSettlementReport + SET [marketplace-name]=@MarketplaceName + WHERE ImportAmazonSettlementReportID=@ImportAmazonSettlementReportID;"; + cmd.Parameters.AddWithValue("@MarketplaceName", items[indexMarketplaceName]); + cmd.Parameters.AddWithValue("@ImportAmazonSettlementReportID", settlementReportId); + + cmd.ExecuteNonQuery(); + marketPlaceUpdated = true; + } + } + + //insert report items + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = + "INSERT INTO tblImportAmazonSettlementReportLine ( " + + "ImportAmazonSettlementReportID, [transaction-type], [order-id], [merchant-order-id], [adjustment-id], [shipment-id], [marketplace-name], " + + "[amount-type], [amount-description], [currency], [amount], [fulfillment-id], [posted-date-time], [order-item-code], " + + "[merchant-order-item-id], [merchant-adjustment-item-id], [sku], [quantity-purchased], [promotion-id] ) " + + "VALUES ( " + + "@ImportAmazonSettlementReportID, @TransactionType, @orderRef, @merchantOrderRef, @AdjustmentRef, @ShipmentRef, @MarketplaceName, " + + "@AmountType, @AmountDescription, @currency, @Amount, @FulfillmentRef, @PostedDateTimeUTC, @OrderItemCode, " + + "@MerchantOrderItemRef, @MerchantAdjustmentItemRef, @SkuNumber, @QuantityPurchased, @PromotionRef );"; + + // add parameters + cmd.Parameters.AddWithValue("@ImportAmazonSettlementReportID", settlementReportId); + + cmd.Parameters.AddWithValue("@currency", currency); + + if (indexTransactionType == -1 || items[indexTransactionType].Length == 0) { cmd.Parameters.AddWithValue("@TransactionType", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@TransactionType", items[indexTransactionType]); } + + if (indexOrderId == -1 || items[indexOrderId].Length == 0) { cmd.Parameters.AddWithValue("@orderRef", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@orderRef", items[indexOrderId]); } + + if (indexMerchantOrderId == -1 || items[indexMerchantOrderId].Length == 0) { cmd.Parameters.AddWithValue("@merchantOrderRef", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@merchantOrderRef", items[indexMerchantOrderId]); } + + if (indexAdjustmentId == -1 || items[indexAdjustmentId].Length == 0) { cmd.Parameters.AddWithValue("@AdjustmentRef", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@AdjustmentRef", items[indexAdjustmentId]); } + + if (indexShipmentId == -1 || items[indexShipmentId].Length == 0) { cmd.Parameters.AddWithValue("@ShipmentRef", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@ShipmentRef", items[indexShipmentId]); } + + if (indexMarketplaceName == -1 || items[indexMarketplaceName].Length == 0) { cmd.Parameters.AddWithValue("@MarketplaceName", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@MarketplaceName", items[indexMarketplaceName]); } + + if (indexAmountType == -1 || items[indexAmountType].Length == 0) { cmd.Parameters.AddWithValue("@AmountType", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@AmountType", items[indexAmountType]); } + + if (indexAmountDescription == -1 || items[indexAmountDescription].Length == 0) { cmd.Parameters.AddWithValue("@AmountDescription", DBNull.Value); } + else + { + string amountDescription = items[indexAmountDescription]; + if (amountDescription.Length > 100) { amountDescription = amountDescription.Substring(0, 100); } + cmd.Parameters.AddWithValue("@AmountDescription", amountDescription); + } + + if (indexAmount == -1 || items[indexAmount].Length == 0) { cmd.Parameters.AddWithValue("@Amount", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@Amount", decimal.Parse(items[indexAmount].Replace(",", "."))); } + + if (indexFulfillmentId == -1 || items[indexFulfillmentId].Length == 0) { cmd.Parameters.AddWithValue("@FulfillmentRef", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@FulfillmentRef", items[indexFulfillmentId]); } + + if (indexPostedDateTime == -1 || items[indexPostedDateTime].Length == 0) { cmd.Parameters.AddWithValue("@PostedDateTimeUTC", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@PostedDateTimeUTC", new Logic.Utilities.DateTime().ParseIsoDateTimeString(items[indexPostedDateTime])); } + + if (indexOrderItemCode == -1 || items[indexOrderItemCode].Length == 0) { cmd.Parameters.AddWithValue("@OrderItemCode", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@OrderItemCode", long.Parse(items[indexOrderItemCode])); } + + if (indexMerchantOrderItemId == -1 || items[indexMerchantOrderItemId].Length == 0) { cmd.Parameters.AddWithValue("@MerchantOrderItemRef", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@MerchantOrderItemRef", long.Parse(items[indexMerchantOrderItemId])); } + + if (indexMerchantAdjustmentItemId == -1 || items[indexMerchantAdjustmentItemId].Length == 0) { cmd.Parameters.AddWithValue("@MerchantAdjustmentItemRef", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@MerchantAdjustmentItemRef", items[indexMerchantAdjustmentItemId]); } + + if (indexSku == -1 || items[indexSku].Length == 0) { cmd.Parameters.AddWithValue("@SkuNumber", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@SkuNumber", items[indexSku]); } + + if (indexQuantityPurchased == -1 || items[indexQuantityPurchased].Length == 0) { cmd.Parameters.AddWithValue("@QuantityPurchased", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@QuantityPurchased", int.Parse(items[indexQuantityPurchased])); } + + if (indexPromotionId == -1 || items[indexPromotionId].Length == 0) { cmd.Parameters.AddWithValue("@PromotionRef", DBNull.Value); } + else { cmd.Parameters.AddWithValue("@PromotionRef", items[indexPromotionId]); } + + cmd.ExecuteNonQuery(); + } + } + lineNumber = lineNumber + 1; + } + } + + //final check - settlement amount matches sum of inserted settlement lines + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" + SELECT Sum(tblImportAmazonSettlementReportLine.amount) AS SumOfAmount + FROM tblImportAmazonSettlementReportLine + WHERE ImportAmazonSettlementReportID=@ImportAmazonSettlementReportID;"; + + decimal sumOfAmount = -1.12345m; + + cmd.Parameters.AddWithValue("@ImportAmazonSettlementReportID", settlementReportId); + sumOfAmount = (decimal)cmd.ExecuteScalar(); + + if (sumOfAmount != settlementAmount) + { + _log.LogError("Error importing settlement id'" + settlementRef + "'. Sum of inserted settlement lines (" + sumOfAmount + + ") does not match settlement amount (" + settlementAmount + ")."); + return false; + } + } + + Console.Write("\r"); + _log.LogInformation((lineNumber - (2 + lineSkip)) + " total settlement items inserted"); + if (lineSkip > 0) + { + _log.LogError(lineSkip + " total line(s) where skipped due to insufficent number of cells on row"); + } + return true; + } + + public int SetSpapiReportId(string settlementId, string spapiReportId) + { + string sqlString = @" + UPDATE + tblImportAmazonSettlementReport + SET + SpapiReportId = @spapiReportId + WHERE + [settlement-id] = @settlementId;"; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sqlString; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@spapiReportId", spapiReportId); + cmd.Parameters.AddWithValue("@settlementId", settlementId); + + return cmd.ExecuteNonQuery(); + } + } + } +} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/SequenceGenerator.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/SequenceGenerator.cs new file mode 100644 index 0000000..51ede37 --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/SequenceGenerator.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Data; +using Microsoft.Data.SqlClient; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using bnhtrade.Core.Data.Database.Repository.Interface; + +namespace bnhtrade.Core.Data.Database.Repository.Implementation +{ + internal class SequenceGenerator : _Base, ISequenceGenerator + { + public SequenceGenerator (IDbConnection connection, IDbTransaction transaction) : base(connection, transaction) + { + } + + public int GetNext(string sequenceName) + { + if (string.IsNullOrWhiteSpace(sequenceName)) + { + throw new Exception("Sequence name is null or whitespace."); + } + + string sql = $"SELECT NEXT VALUE FOR {sequenceName};"; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + object obj = cmd.ExecuteScalar(); + + try + { + return Convert.ToInt32(obj); + } + catch (Exception ex) + { + // Provide more context in the exception + throw new InvalidOperationException($"Error retrieving next value for sequence '{sequenceName}'. " + + $"Raw value: '{obj}'. Inner exception: {ex.Message}", ex); + } + } + } + } +} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/_Base.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/_Base.cs new file mode 100644 index 0000000..5c86041 --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/_Base.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.Repository.Implementation +{ + abstract class _Base + { + protected readonly IDbConnection _connection; + protected readonly IDbTransaction _transaction; + + public _Base(IDbConnection connection, IDbTransaction transaction) + { + _connection = connection ?? throw new ArgumentNullException(nameof(connection)); + _transaction = transaction; // Transaction can be null if not needed for read-only ops + // But for NEXT VALUE FOR, it implies a modification/dependency within a transaction + } + } +} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/_BoilerPlate.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/_BoilerPlate.cs new file mode 100644 index 0000000..c7f4974 --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/_BoilerPlate.cs @@ -0,0 +1,131 @@ +using bnhtrade.Core.Data.Database._BoilerPlate; +using bnhtrade.Core.Data.Database.Repository.Interface; +using FikaAmazonAPI.AmazonSpApiSDK.Models.FulfillmentOutbound; +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; + +namespace bnhtrade.Core.Data.Database.Repository.Implementation +{ + internal class _BoilerPlate : _Base, _IBoilerPlate + { + public _BoilerPlate(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction) + { + } + + public Dictionary SelectByList(List selectList) + { + var returnList = new Dictionary(); + + // check input list for items + if (selectList == null) + { + throw new ArgumentNullException("Method argument cannot be null"); + } + + // build SQL string + string sql = @" + SELECT + column01 + ,column02 + FROM + tblTable + "; + + var sqlwhere = new Data.Database.SqlWhereBuilder(); + sqlwhere.In("column03", selectList, "WHERE"); + + sql = sql + sqlwhere.SqlWhereString; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + sqlwhere.AddParametersToSqlCommand(cmd); + + using (SqlDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + int id = reader.GetInt32(0); + string data = reader.GetString(1); + + returnList.Add(id, data); + } + } + } + return returnList; + } + + public int Insert() + { + string sql = @" + INSERT INTO tblTable ( + column01 + ,column02 + ,column03 + ,column04 + ) + OUTPUT INSERTED.TablePk + VALUES ( + @value01 + ,@value02 + ,@value03 + ,@value04 + ) + "; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@value01", "xxxxxxxx"); + cmd.Parameters.AddWithValue("@value02", "xxxxxxxx"); + cmd.Parameters.AddWithValue("@value03", "xxxxxxxx"); + cmd.Parameters.AddWithValue("@value04", "xxxxxxxx"); + + int newId = (int)cmd.ExecuteScalar(); + + return newId; + } + } + + public int Update() + { + string sql = @" + UPDATE + tblTable + SET + column01 = @value01 + column02 = @value02 + column03 = @value03 + column04 = @value04 + WHERE + column05 = @value05 + "; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@value01", "xxxxxxxx"); + cmd.Parameters.AddWithValue("@value02", "xxxxxxxx"); + cmd.Parameters.AddWithValue("@value03", "xxxxxxxx"); + cmd.Parameters.AddWithValue("@value04", "xxxxxxxx"); + cmd.Parameters.AddWithValue("@value04", "xxxxxxxx"); + + int recordsEffected = cmd.ExecuteNonQuery(); + + return recordsEffected; + } + } + } +} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/ICurrencyRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/ICurrencyRepository.cs new file mode 100644 index 0000000..f4ac03a --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/ICurrencyRepository.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.Repository.Interface +{ + internal interface ICurrencyRepository + { + public decimal? ReadExchangeRate(Model.Account.CurrencyCode currencyCode, DateTime date); + public List ReadExchangeRate(List currencyCodeList = null, DateTime date = default(DateTime)); + public List ReadExchangeRateLatest(List currencyCodeList = null); + public int InsertExchangeRate(int exchangeRateSource, Model.Account.CurrencyCode currencyCode, + decimal currencyUnitsPerGbp, DateTime periodStartUtc, DateTime periodEnd, bool checkOverride = false); + } +} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IExportInvoiceRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IExportInvoiceRepository.cs new file mode 100644 index 0000000..c5e7004 --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IExportInvoiceRepository.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.Repository.Interface +{ + internal interface IExportInvoiceRepository + { + internal Dictionary InsertSalesInvoices(IEnumerable invoiceList); + internal Dictionary GetNewInvoiceNumbers(Model.Account.InvoiceType invoiceType); + internal Dictionary GetSalesInvoiceById(IEnumerable idList); + internal int SetInvoiceIsCompleteValue(Dictionary updateDictionary); + internal int DeleteInvoiceLine(int invoiceLineId); + internal int DeleteInvoice(IEnumerable invoiceIdList); + } +} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IImportAmazonRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IImportAmazonRepository.cs new file mode 100644 index 0000000..21cabda --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IImportAmazonRepository.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.Repository.Interface +{ + internal interface IImportAmazonRepository + { + public List ReadAmazonSettlementHeaderInfoBySettlementId(List settlementIdList); + + public List ReadAmazonSettlementHeaderInfoBySpapiReportId(List spapiReportIdList); + + public Dictionary ReadAmazonSettlements( + List settlementIdList = null, List marketPlaceNameList = null, bool? isProcessed = null, + bool descendingOrder = false, int? returnTop = null); + + public int SetAmazonSettlementIsProcessed(List settlementIdList, bool isProcessed); + + public bool CreateAmazonSettlements(string filePath, string reportId); + + public int SetSpapiReportId(string settlementId, string spapiReportId); + } +} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/ISequenceGenerator.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/ISequenceGenerator.cs new file mode 100644 index 0000000..15200fa --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/ISequenceGenerator.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Data; + +namespace bnhtrade.Core.Data.Database.Repository.Interface +{ + internal interface ISequenceGenerator + { + int GetNext(string sequenceName); + } +} \ No newline at end of file diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/_IBoilerPlate.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/_IBoilerPlate.cs new file mode 100644 index 0000000..626da22 --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/_IBoilerPlate.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.Repository.Interface +{ + internal interface _IBoilerPlate + { + internal Dictionary SelectByList(List selectList); + internal int Insert(); + internal int Update(); + } +} diff --git a/src/bnhtrade.Core/Data/Database/SKU/InsertSku.cs b/src/bnhtrade.Core/Data/Database/SKU/InsertSku.cs index 68cb69a..7578e9d 100644 --- a/src/bnhtrade.Core/Data/Database/SKU/InsertSku.cs +++ b/src/bnhtrade.Core/Data/Database/SKU/InsertSku.cs @@ -1,7 +1,7 @@ using FikaAmazonAPI.AmazonSpApiSDK.Models.ProductPricing; using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -22,11 +22,11 @@ namespace bnhtrade.Core.Data.Database.Sku var dbTax = new Data.Database.Account.ReadTaxCode(); var taxCodeList = dbTax.GetByTaxCodeId(new List { accountTaxCodeId }); - if (!taxCodeList.Any()) + if (taxCodeList.ContainsKey(accountTaxCodeId) == false) { throw new Exception("AccountTaxCodeID=" + accountTaxCodeId + " doesn't exist!"); } - else if (taxCodeList[0].IsValidOnIncome == false) + else if (taxCodeList[accountTaxCodeId].IsValidOnIncome == false) { throw new Exception("AccountTaxCodeID=" + accountTaxCodeId + " is not a valid type for an SKU."); } diff --git a/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs b/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs index 73687f1..7f7159d 100644 --- a/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs +++ b/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs @@ -5,6 +5,7 @@ using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.IdentityModel.Tokens; namespace bnhtrade.Core.Data.Database { @@ -12,9 +13,12 @@ namespace bnhtrade.Core.Data.Database /// Step 1: Call the methods for each where clause you want to create. This can be done multiple times to create an sql where string. Pay attention /// to the prefixes that you'll require between each where clause, as each time a method is called the sql statement will be appended to the previous sql /// string. + /// /// Step 2: Appened the created sql string to your sql statement, NB the WHERE statemet is not included by default. + /// /// Step 3: Once you've created your sql command object, add the parameters to it using the method contained within this class. - /// STep 4: exceute your sql commend. + /// + /// Step 4: exceute your sql commend. /// public class SqlWhereBuilder { @@ -44,6 +48,21 @@ namespace bnhtrade.Core.Data.Database public Dictionary ParameterList { get; private set; } + public int ParameterListCount + { + get + { + if (ParameterList == null || ParameterList.Any() == false) + { + return 0; + } + else + { + return ParameterList.Count(); + } + } + } + /// /// Initialises the class /// @@ -83,7 +102,7 @@ namespace bnhtrade.Core.Data.Database /// Name of the column to used to for the condition statement /// List of phrases to test in condition statement /// Optional prefix that gets added to the sql string result - public void LikeAnd(string columnReference, List phraseList, string wherePrefix = null) + public void LikeAnd(string columnReference, IEnumerable phraseList, string wherePrefix = null) { Like(columnReference, phraseList, true, wherePrefix); } @@ -94,12 +113,12 @@ namespace bnhtrade.Core.Data.Database /// Name of the column to used to for the condition statement /// List of phrases to test in condition statement /// Optional prefix that gets added to the sql string result - public void LikeOr(string columnReference, List phraseList, string wherePrefix = null) + public void LikeOr(string columnReference, IEnumerable phraseList, string wherePrefix = null) { Like(columnReference, phraseList, false, wherePrefix); } - private void Like(string columnReference, List phraseList, bool isAnd, string wherePrefix = null) + private void Like(string columnReference, IEnumerable phraseList, bool isAnd, string wherePrefix = null) { if (phraseList == null || !phraseList.Any()) { @@ -107,7 +126,7 @@ namespace bnhtrade.Core.Data.Database } // ensure no values are repeated - var distinctList = phraseList.ToList(); + var distinctList = phraseList.Distinct().ToList(); // clean the list for (int i = 0; i < distinctList.Count; i++) @@ -166,7 +185,7 @@ namespace bnhtrade.Core.Data.Database /// Name of the column to used to for the condition statement /// List of values to test in condition statement /// Optional prefix that gets added to the sql string result - public void In(string columnReference, List orValueList, string wherePrefix = null) + public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) { if (orValueList == null || !orValueList.Any()) { @@ -208,19 +227,19 @@ namespace bnhtrade.Core.Data.Database /// Name of the column to used to for the condition statement /// List of values to test in condition statement /// Optional prefix that gets added to the sql string result - public void In(string columnReference, List orValueList, string wherePrefix = null) + public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) { - var stringList = new List(); + var objectList = new List(); - if (orValueList != null || !orValueList.Any()) + if (orValueList != null && orValueList.Any()) { - foreach (int value in orValueList) + foreach (string value in orValueList) { - stringList.Add(value.ToString()); + objectList.Add(value.ToString()); } } - In(columnReference, stringList, wherePrefix); + In(columnReference, objectList, wherePrefix); } /// @@ -229,19 +248,55 @@ namespace bnhtrade.Core.Data.Database /// Name of the column to used to for the condition statement /// List of values to test in condition statement /// Optional prefix that gets added to the sql string result - public void In(string columnReference, List orValueList, string wherePrefix = null) + public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) { - var stringList = new List(); + var objectList = new List(); - if (orValueList != null || !orValueList.Any()) + if (orValueList != null && orValueList.Any()) { - foreach (uint value in orValueList) + foreach (var value in orValueList) { - stringList.Add(value.ToString()); + objectList.Add(value.ToString()); } } - In(columnReference, stringList, wherePrefix); + In(columnReference, objectList, wherePrefix); + } + + /// + /// Append an 'In' statement and parameter list to the class properties + /// + /// Name of the column to used to for the condition statement + /// List of values to test in condition statement + /// Optional prefix that gets added to the sql string result + public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) + { + var objectList = new List(); + + if (orValueList != null && orValueList.Any()) + { + foreach (var value in orValueList) + { + objectList.Add(value.ToString()); + } + } + + In(columnReference, objectList, wherePrefix); + } + + public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) + { + var objectList = new List(); + + if (orValueList != null && orValueList.Any()) + { + foreach(var value in orValueList) + { + objectList.Add(value.ToString()); + } + } + + In(columnReference, objectList, wherePrefix); } } } diff --git a/src/bnhtrade.Core/Data/Database/UnitOfWork/Connection.cs b/src/bnhtrade.Core/Data/Database/UnitOfWork/Connection.cs new file mode 100644 index 0000000..6a178f4 --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/UnitOfWork/Connection.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Data.SqlClient; +using System.Configuration; + +namespace bnhtrade.Core.Data.Database.UnitOfWork +{ + internal class Connection + { + //protected readonly string SqlConnectionString; + private Model.Credentials.bnhtradeDB _dbCredentials; + + protected string SqlConnectionString + { + get { return _dbCredentials.ConnectionString; } + } + + public Connection() + { + var config = new Config().GetConfiguration(); + + // attempt to retrive credentials from app.local.config + try + { + string dataSource = config.AppSettings.Settings["DbDataSource"].Value; + string userId = config.AppSettings.Settings["DbUserId"].Value; + string pass = config.AppSettings.Settings["DbUserPassword"].Value; + + // check + if (string.IsNullOrEmpty(dataSource)) + { + throw new ArgumentException("Could not retrive 'DbDataSource' from config file"); + } + else if (string.IsNullOrEmpty(userId)) + { + throw new ArgumentException("Could not retrive 'DbUserId' from config file"); + } + else if (string.IsNullOrEmpty(pass)) + { + throw new ArgumentException("Could not retrive 'DbUserPassword' from config file"); + } + + var dbCredentials = new bnhtrade.Core.Model.Credentials.bnhtradeDB(dataSource, userId, pass); + this._dbCredentials = dbCredentials; + } + catch (Exception ex) + { + throw new Exception("Unable to retirve DB credentials: " + ex.Message); + } + } + } +} diff --git a/src/bnhtrade.Core/Data/Database/UnitOfWork/IUnitOfWork.cs b/src/bnhtrade.Core/Data/Database/UnitOfWork/IUnitOfWork.cs new file mode 100644 index 0000000..58f6e7c --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/UnitOfWork/IUnitOfWork.cs @@ -0,0 +1,23 @@ +using bnhtrade.Core.Data.Database.Repository.Interface; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.UnitOfWork +{ + internal interface IUnitOfWork : IDisposable + { + // Properties for repositories, add here for each repository + public ICurrencyRepository CurrencyRepository { get; } + IExportInvoiceRepository ExportInvoiceRepository { get; } + IImportAmazonRepository ImportAmazonRepository { get; } + ISequenceGenerator SequenceGenerator { get; } + + // Methods to manage the transaction + void Commit(); + + void Rollback(); + } +} diff --git a/src/bnhtrade.Core/Data/Database/UnitOfWork/UnitOfWork.cs b/src/bnhtrade.Core/Data/Database/UnitOfWork/UnitOfWork.cs new file mode 100644 index 0000000..9e4b82b --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/UnitOfWork/UnitOfWork.cs @@ -0,0 +1,136 @@ +using bnhtrade.Core.Data.Database.Repository.Implementation; +using bnhtrade.Core.Data.Database.Repository.Interface; +using bnhtrade.Core.Data.Database.UnitOfWork; +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.UnitOfWork +{ + internal class UnitOfWork : Connection, IUnitOfWork + { + private IDbConnection _connection; + private IDbTransaction _transaction; + private bool _disposed; + + // Private field for lazy loading, add here for each repository + private ICurrencyRepository _currencyRepository; + private IExportInvoiceRepository _exportInvoiceRepository; + private IImportAmazonRepository _importAmazonRepository; + private ISequenceGenerator _sequenceGenerator; + + public UnitOfWork() + { + _connection = new SqlConnection(this.SqlConnectionString); + _connection.Open(); + _transaction = _connection.BeginTransaction(); + } + + // Properties for repositories, add here for each repository + public ICurrencyRepository CurrencyRepository + { + get + { + if (_currencyRepository == null) + { + _currencyRepository = new CurrencyRepository(_connection, _transaction); + } + return _currencyRepository; + } + } + + public IExportInvoiceRepository ExportInvoiceRepository + { + get + { + if (_exportInvoiceRepository == null) + { + _exportInvoiceRepository = new ExportInvoiceRepository(_connection, _transaction); + } + return _exportInvoiceRepository; + } + } + + public IImportAmazonRepository ImportAmazonRepository + { + get + { + if (_importAmazonRepository == null) + { + _importAmazonRepository = new ImportAmazonRepository(_connection, _transaction); + } + return _importAmazonRepository; + } + } + + public ISequenceGenerator SequenceGenerator + { + get + { + if (_sequenceGenerator == null) + { + _sequenceGenerator = new SequenceGenerator(_connection, _transaction); + } + return _sequenceGenerator; + } + } + + public void Commit() + { + try + { + _transaction.Commit(); + } + catch + { + _transaction.Rollback(); + throw; + } + finally + { + Dispose(); + } + } + + public void Rollback() + { + _transaction.Rollback(); + Dispose(); + } + + // IDisposable implementation (ensure proper disposal of connection and transaction) + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + // Dispose the transaction first + if (_transaction != null) + { + _transaction.Dispose(); + _transaction = null; + } + // Then close and dispose the connection + if (_connection != null) + { + _connection.Close(); + _connection.Dispose(); + _connection = null; + } + } + _disposed = true; + } + } + } +} \ No newline at end of file diff --git a/src/bnhtrade.Core/Test/_BoilerPlate/ClassFromSql.cs b/src/bnhtrade.Core/Data/Database/_BoilerPlate/ClassFromSql.cs similarity index 98% rename from src/bnhtrade.Core/Test/_BoilerPlate/ClassFromSql.cs rename to src/bnhtrade.Core/Data/Database/_BoilerPlate/ClassFromSql.cs index 1cd339e..d34a783 100644 --- a/src/bnhtrade.Core/Test/_BoilerPlate/ClassFromSql.cs +++ b/src/bnhtrade.Core/Data/Database/_BoilerPlate/ClassFromSql.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace bnhtrade.Core.Test._BoilerPlate +namespace bnhtrade.Core.Data.Database._BoilerPlate { class ClassFromSql { diff --git a/src/bnhtrade.Core/Test/_BoilerPlate/Sql.cs b/src/bnhtrade.Core/Data/Database/_BoilerPlate/Sql.cs similarity index 80% rename from src/bnhtrade.Core/Test/_BoilerPlate/Sql.cs rename to src/bnhtrade.Core/Data/Database/_BoilerPlate/Sql.cs index cdf2d36..ad81aba 100644 --- a/src/bnhtrade.Core/Test/_BoilerPlate/Sql.cs +++ b/src/bnhtrade.Core/Data/Database/_BoilerPlate/Sql.cs @@ -1,34 +1,39 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Transactions; -namespace bnhtrade.Core.Test._BoilerPlate +namespace bnhtrade.Core.Data.Database._BoilerPlate { - public class Sql + internal class Sql { - public void Select() + private string SqlConnectionString = ""; + + internal List Select() { - using (SqlConnection conn = new SqlConnection()) + var returnList = new List(); + + string sql = @" + SELECT + column01 + ,column02 + ,column03 + ,column04 + FROM tblTable + WHERE + column01 = @value01 + OR column01 = @value02 + OR column01 = @value03 + OR column01 = @value04; "; + + using (SqlConnection conn = new SqlConnection(SqlConnectionString)) { conn.Open(); - using (SqlCommand cmd = new SqlCommand(@" - SELECT - column01 - ,column02 - ,column03 - ,column04 - FROM tblTable - WHERE - column01 = @value01 - OR column01 = @value02 - OR column01 = @value03 - OR column01 = @value04 - ", conn)) + using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@value01", "xxxxxxxx"); cmd.Parameters.AddWithValue("@value02", "xxxxxxxx"); @@ -37,22 +42,17 @@ namespace bnhtrade.Core.Test._BoilerPlate using (SqlDataReader reader = cmd.ExecuteReader()) { - if (!reader.HasRows) + while (reader.Read()) { - // do something - } - else - { - while (reader.Read()) - { - // do some thing with the data - } + returnList.Add(""); } } } } + return returnList; } - public string SelectByList(List stringList) + + internal string SelectByList(List stringList) { // check input list for items if (stringList == null || !stringList.Any()) @@ -98,7 +98,7 @@ namespace bnhtrade.Core.Test._BoilerPlate // execute query and build result list var skuTaxCodeList = new List>(); - using (SqlConnection conn = new SqlConnection()) + using (SqlConnection conn = new SqlConnection(SqlConnectionString)) { conn.Open(); @@ -125,11 +125,12 @@ namespace bnhtrade.Core.Test._BoilerPlate } return "Complete"; // return object } - public void Insert() + + internal void Insert() { using (TransactionScope scope = new TransactionScope()) { - using (SqlConnection conn = new SqlConnection()) + using (SqlConnection conn = new SqlConnection(SqlConnectionString)) { conn.Open(); @@ -160,11 +161,12 @@ namespace bnhtrade.Core.Test._BoilerPlate scope.Complete(); } } - public void Update() + + internal void Update() { using (TransactionScope scope = new TransactionScope()) { - using (SqlConnection conn = new SqlConnection()) + using (SqlConnection conn = new SqlConnection(SqlConnectionString)) { conn.Open(); diff --git a/src/bnhtrade.Core/Test/_BoilerPlate/sql_Read.cs b/src/bnhtrade.Core/Data/Database/_BoilerPlate/sql_Read.cs similarity index 95% rename from src/bnhtrade.Core/Test/_BoilerPlate/sql_Read.cs rename to src/bnhtrade.Core/Data/Database/_BoilerPlate/sql_Read.cs index 7121986..8db189a 100644 --- a/src/bnhtrade.Core/Test/_BoilerPlate/sql_Read.cs +++ b/src/bnhtrade.Core/Data/Database/_BoilerPlate/sql_Read.cs @@ -1,16 +1,16 @@ using bnhtrade.Core.Data.Database; using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace bnhtrade.Core.Test._BoilerPlate +namespace bnhtrade.Core.Data.Database._BoilerPlate { internal class xxxxxxxYourClassNameHerexxxxxxxx : Connection { - private bnhtrade.Core.Data.Database.SqlWhereBuilder sqlBuilder; + private SqlWhereBuilder sqlBuilder; /// /// Results filter diff --git a/src/bnhtrade.Core/Data/Xero/SalesInvoice.cs b/src/bnhtrade.Core/Data/Xero/SalesInvoice.cs new file mode 100644 index 0000000..8fb05ec --- /dev/null +++ b/src/bnhtrade.Core/Data/Xero/SalesInvoice.cs @@ -0,0 +1,172 @@ +using bnhtrade.Core.Test.Amazon.SP_API; +using CsvHelper; +using CsvHelper.Configuration.Attributes; +using Microsoft.VisualBasic.Logging; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace bnhtrade.Core.Data.Xero +{ + internal class SalesInvoice + { + private class CsvLine + { + [Required()] + [Name("*ContactName")] + public string ContactName { get; set; } + + public string EmailAddress { get; set; } + + public string POAddressLine1 { get; set; } + + public string POAddressLine2 { get; set; } + + public string POAddressLine3 { get; set; } + + public string POAddressLine4 { get; set; } + + public string POCity { get; set; } + + public string PORegion { get; set; } + + public string POPostalCode { get; set; } + + public string POCountry { get; set; } + + [Required()] + [Name("*InvoiceNumber")] + public string InvoiceNumber { get; set; } + public string Reference { get; set; } + + [Required()] + [Name("*InvoiceDate")] + public string InvoiceDate { get; set; } + + [Required()] + [Name("*DueDate")] + public string DueDate { get; set; } + + public string Total { get; set; } + + public string InventoryItemCode { get; set; } + + [Required()] + [Name("*Description")] + public string Description { get; set; } + + [Required()] + [Name("*Quantity")] + public string Quantity { get; set; } + + [Required()] + [Name("*UnitAmount")] + public string UnitAmount { get; set; } + + public string Discount { get; set; } + + [Required()] + [Name("*AccountCode")] + public string AccountCode { get; set; } + + [Required()] + [Name("*TaxType")] + public string TaxType { get; set; } + + public string TaxAmount { get; set; } + + public string TrackingName1 { get; set; } + + public string TrackingOption1 { get; set; } + + public string TrackingName2 { get; set; } + + public string TrackingOption2 { get; set; } + + public string Currency { get; set; } + + public string BrandingTheme { get; set; } + } + + internal void ExportToCsv(List salesInvoiceList, int startingInvoiceNumber, string savePath, bool unitAmountIsTaxExclusive = true) + { + int invoiceNumberCount = startingInvoiceNumber; + + // convert invoice to unitAmountIsTaxExclusive = true + foreach (var invoice in salesInvoiceList) + { + if (invoice.InvoiceLineUnitAmountIsTaxExclusive != unitAmountIsTaxExclusive) + { + if (invoice.InvoiceLineUnitAmountIsTaxExclusive == true) + { + invoice.ConvertToLineUnitAmountIsTaxInclusive(); + } + else + { + invoice.ConvertToLineUnitAmountIsTaxExclusive(); + } + } + } + + var csvLineList = new List(); + foreach (var invoice in salesInvoiceList) + { + if (invoice.InvoiceLineUnitAmountIsTaxExclusive != unitAmountIsTaxExclusive) + { + throw new Exception("UnitAmountIsTaxExclusive check failed"); + } + + string contactName = invoice.ContactName; + string invoiceNumber = "INV-" + invoiceNumberCount.ToString("000000"); + string reference = invoice.InvoiceReference; + string invoiceDate = invoice.InvoiceDate.GetValueOrDefault().ToString("dd MMM yyyy", CultureInfo.InvariantCulture); + string dueDate = invoice.InvoiceDueDate.GetValueOrDefault().ToString("dd MMM yyyy", CultureInfo.InvariantCulture); + string total = invoice.InvoiceTotalAmount.ToString(); + string currencyCode = invoice.InvoiceCurrencyCode.ToString(); + + foreach (var line in invoice.InvoiceLineList) + { + var csvLine = new CsvLine(); + + // header info + csvLine.ContactName = contactName; + csvLine.InvoiceNumber = invoiceNumber; + csvLine.Reference = reference; + csvLine.InvoiceDate = invoiceDate; + csvLine.DueDate = dueDate; + csvLine.Total = total; + + // line info + csvLine.Description = line.Description; + csvLine.Quantity = line.Quantity.ToString(); + csvLine.UnitAmount = line.UnitAmount.ToString(); + csvLine.AccountCode = line.Account.AccountCode.ToString(); + csvLine.TaxType = line.TaxCode.TaxCodeName; + csvLine.TaxAmount = line.TaxAmountTotal.ToString(); + csvLine.Currency = currencyCode; + + csvLineList.Add(csvLine); + } + + invoiceNumberCount++; + } + + var config = new CsvHelper.Configuration.CsvConfiguration(CultureInfo.CurrentCulture); + //config.Delimiter = "\t"; + config.Encoding = Encoding.UTF8; + + savePath = Environment.ExpandEnvironmentVariables(savePath); + using (var writer = new StreamWriter(savePath)) + using (var csv = new CsvWriter(writer, config)) + { + csv.WriteRecords(csvLineList); + } + } + } +} diff --git a/src/bnhtrade.Core/Logic/Account/Currency.cs b/src/bnhtrade.Core/Logic/Account/Currency.cs deleted file mode 100644 index 49ced0b..0000000 --- a/src/bnhtrade.Core/Logic/Account/Currency.cs +++ /dev/null @@ -1,200 +0,0 @@ -using FikaAmazonAPI.ConstructFeed.Messages; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Data.SqlClient; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml.Linq; - -namespace bnhtrade.Core.Logic.Account -{ - public class Currency - { - Log.LogEvent log = new Log.LogEvent(); - - public decimal CurrencyConvertToGbp(string currencyCode, decimal amount, DateTime conversionDate) - { - if (currencyCode == "GBP" || amount == 0M) - { - return amount; - } - - if (currencyCode.Length != 3) - { - throw new Exception("Invalid currency code '" + currencyCode + "'"); - } - - var db = new Data.Database.Account.Currency(); - var exchageRate = db.ReadExchangeRate(currencyCode, conversionDate); - - if (exchageRate != null) - { - return amount / Convert.ToDecimal(exchageRate); - } - else - { - throw new Exception("Currency code '" + currencyCode + "' or date " + conversionDate.ToShortDateString() + " " + conversionDate.ToLongTimeString() + "' does not exist in the Exchange Rate table"); - } - - } - - private DateTime GetHmrcMaxPeriodAvaible() - { - // HMRC monthly sxchange rates are published on the penultimate Thursday of the month before - // For some leeway we'll use the penultimate Friday - - // find penultimate Friday for current month - var ukTimeNow = new Logic.Utilities.DateTime().ConvertUtcToUk(DateTime.UtcNow); - var monthDayCount = DateTime.DaysInMonth(ukTimeNow.Year, ukTimeNow.Month); - var thisMonthPenultimateFriday = DateTime.SpecifyKind(new DateTime(ukTimeNow.Year, ukTimeNow.Month, monthDayCount), DateTimeKind.Unspecified); - int count = 0; - int fridayCount = 0; - while (count != 15) - { - if (thisMonthPenultimateFriday.DayOfWeek == DayOfWeek.Friday) - { - fridayCount++; - if (fridayCount == 2) - { - break; - } - } - thisMonthPenultimateFriday = thisMonthPenultimateFriday.AddDays(-1); - count++; - } - - if (count == 15) - { - throw new Exception("Something went wrong here ErrorID:ef7f5d8f-0f7b-4014-aa65-421ecd5d7367"); - } - - var mostRecentPeriodAvaible = DateTime.SpecifyKind(new DateTime(ukTimeNow.Year, ukTimeNow.Month, 1), DateTimeKind.Unspecified); ; - if (ukTimeNow >= thisMonthPenultimateFriday) - { - mostRecentPeriodAvaible = mostRecentPeriodAvaible.AddMonths(1); - } - - return mostRecentPeriodAvaible; - } - - public void UpdateHmrcExchageRates() - { - log.LogInformation("Starting update database HMRC exchange rates"); - - int exchangeRateSourceId = 1; // id for hmrc - - // retrive most recent data from db - var db = new Data.Database.Account.Currency(); - var dbLatestRates = db.ReadExchangeRateLatest(); - - // sanity check, make sure there are no duplicates - int count = 0; - foreach (var exchageRate in dbLatestRates) - { - count = 0; - var currency = exchageRate.CurrencyCode; - foreach (var subExchageRate in dbLatestRates) - { - if (exchageRate.CurrencyCode == subExchageRate.CurrencyCode) - { - count = 1; - } - } - if (count > 1) - { - throw new FormatException("Datebase returned duplicate information"); - } - } - - // test for no data (first time running) - var hmrcMonthToRetrive = new DateTime(); - if (dbLatestRates.Any() == false) - { - hmrcMonthToRetrive = Data.Database.Constants.GetBusinessStartUk(); - } - - // set first/earliest month to retrive from hmrc website - foreach (var exchageRate in dbLatestRates) - { - var dbEndDateTime = exchageRate.DateTimeEndUk; - - if (hmrcMonthToRetrive == default(DateTime)) - { - hmrcMonthToRetrive = dbEndDateTime; - } - else - { - if (dbEndDateTime < hmrcMonthToRetrive) - { - hmrcMonthToRetrive = dbEndDateTime; - } - } - } - - // check - more coding required to retrive periods before 2021-01-01 - if (hmrcMonthToRetrive < DateTime.SpecifyKind(new DateTime(2021, 1, 1), DateTimeKind.Unspecified)) - { - throw new Exception("This function does not currently retirve exchange rates from HMRC for dates before 2021-01-01"); - } - - // check if retrival from hmrc is required - var hmrcMaxMonthAvaible = GetHmrcMaxPeriodAvaible(); - if (hmrcMonthToRetrive.Year == hmrcMaxMonthAvaible.Year && hmrcMonthToRetrive.Month > hmrcMaxMonthAvaible.Month) - { - // nothing to retrive - log.LogInformation("Exchange rates curretly up to date, exiting."); - return; - } - - // get info from hmrc and insert data in db - while (hmrcMonthToRetrive <= hmrcMaxMonthAvaible) - { - count = 0; - - var url = new string( - "https://www.trade-tariff.service.gov.uk/api/v2/exchange_rates/files/monthly_xml_" - + hmrcMonthToRetrive.Year.ToString() - + "-" - + hmrcMonthToRetrive.Month.ToString() - + ".xml" - ); - var xd = new XDocument(); - xd = XDocument.Load(url); - - foreach (var exchageRate in dbLatestRates) - { - if (exchageRate.DateTimeStartUtc < hmrcMonthToRetrive) - { - //retrive exchange rate from xml - XElement node = xd.Root.Elements("exchangeRate").Where(e => e.Element("currencyCode").Value == exchageRate.CurrencyCode.ToString()).FirstOrDefault(); - decimal rate = decimal.Parse(node.Element("rateNew").Value); - rate = decimal.Round(rate, 4); - - // insert into db - new Data.Database.Account.Currency().InsertExchangeRate( - exchangeRateSourceId - , exchageRate.CurrencyCode - , rate - , new Utilities.DateTime().ConvertUkToUtc(hmrcMonthToRetrive) - , new Utilities.DateTime().ConvertUkToUtc(hmrcMonthToRetrive.AddMonths(1)) - ); - - count++; - } - } - - log.LogInformation( - count + " new exchange rate(s) added to database for " + hmrcMonthToRetrive.ToString("MMMM") + " " + hmrcMonthToRetrive.Year.ToString() - ); - - hmrcMonthToRetrive = hmrcMonthToRetrive.AddMonths(1); - } - - log.LogInformation("Updating database currency exchange rates complete."); - } - } -} diff --git a/src/bnhtrade.Core/Logic/Account/CurrencyService.cs b/src/bnhtrade.Core/Logic/Account/CurrencyService.cs new file mode 100644 index 0000000..5fc5f53 --- /dev/null +++ b/src/bnhtrade.Core/Logic/Account/CurrencyService.cs @@ -0,0 +1,445 @@ +using bnhtrade.Core.Data.Database.UnitOfWork; +using FikaAmazonAPI.AmazonSpApiSDK.Models.FulfillmentInbound; +using FikaAmazonAPI.ConstructFeed.Messages; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data.SqlClient; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using static bnhtrade.Core.Model.Account.PurchaseInvoice; + +namespace bnhtrade.Core.Logic.Account +{ + public class CurrencyService + { + private readonly IUnitOfWork _providedUnitOfWork = null; + private readonly bool _ownsUnitOfWork = false; + private List _exchangeRateList = new List(); + + Log.LogEvent _log = new Log.LogEvent(); + + public string ErrorMessage { get; private set; } = null; + + public bool ErrorMessageIsSet + { + get + { + return !string.IsNullOrEmpty(ErrorMessage); + } + } + + public CurrencyService() + { + _ownsUnitOfWork = true; + } + + internal CurrencyService(IUnitOfWork unitOfWork) + { + _providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); + _ownsUnitOfWork = false; + } + + private void Init() + { + ErrorMessage = null; + } + + public bool ConvertInvoiceToGbp(Model.Account.IInvoice invoice) + { + Init(); + + //checks + if (invoice == null) + { + ErrorMessage = "Invoice cannot be null"; + return false; + } + if (invoice.InvoiceCurrencyCode == Model.Account.CurrencyCode.GBP) + { + return true; // no conversion needed + } + if (invoice.InvoiceDate == null) + { + ErrorMessage = "Invoice date cannot be null"; + return false; + } + + //validate the invoice + var validate = new Logic.Validate.Invoice(); + // Fix for CS1503: Cast 'invoice' to 'Model.Account.Invoice' explicitly + if (validate.IsValidExportInvoice(new List { (Model.Account.Invoice)invoice }) == false) + { + ErrorMessage = "Invoice failed validation. See logs for further details."; + _log.LogError(ErrorMessage); + return false; + } + + // check if exchange rate is already in list + Model.Account.CurrencyExchangeRate existingRate = null; + foreach (var exchangeRate in _exchangeRateList) + { + if (exchangeRate.CurrencyCode == invoice.InvoiceCurrencyCode && exchangeRate.DateTimeWithinPeriodCheck(invoice.InvoiceDate.Value)) + { + existingRate = exchangeRate; + break; + } + } + + // get rates from db and add to field list if not in list already + var rateList = GetExchangeRateObjectList( + new List { invoice.InvoiceCurrencyCode }, invoice.InvoiceDate.Value); + + if (rateList == null || rateList.Count == 0) + { + ErrorMessage = + "Exchange rate for currency code '" + invoice.InvoiceCurrencyCode + "' and date " + invoice.InvoiceDate.Value.ToShortDateString() + "' does not exist in the Exchange Rate table"; + return false; + } + + _exchangeRateList.AddRange(rateList); + existingRate = rateList[0]; + + // do the conversion + // convert header information + decimal invoiceOriginalTotalAmount = invoice.InvoiceTotalAmount.Value; + invoice.InvoiceTotalAmount = existingRate.ConvertToGbp(invoice.InvoiceTotalAmount.Value); + invoice.InvoiceCurrencyCode = Model.Account.CurrencyCode.GBP; + + // convert line items + var lineWeighting = new List<(int, decimal)>(); + var lineCalculatedTotalByWeighting = new List<(int, decimal)>(); + decimal newLineSum = 0; + int i = 0; + foreach (var line in invoice.InvoiceLineList) + { + decimal weighting = line.LineTotalAmount / invoiceOriginalTotalAmount; + decimal lineCalculatedTotal = invoiceOriginalTotalAmount * weighting; + lineWeighting.Add(new(i, weighting)); + lineCalculatedTotalByWeighting.Add(new(i, lineCalculatedTotal)); + + // edit line + if (line.TaxAmountAdjust != 0) + { + line.TaxAmountAdjust = existingRate.ConvertToGbp(line.TaxAmountAdjust); + } + line.UnitAmount = null; + line.SetUnitAmountByLineTotal(decimal.Round(lineCalculatedTotal, 2)); + newLineSum += line.LineTotalAmount; + + i++; + } + + // there may be rounding errors + // section untested - may have to fix some bugs in here when I've got some invoice to test on + if (invoiceOriginalTotalAmount != newLineSum) + { + decimal amountRemainingToDistribute = invoiceOriginalTotalAmount - newLineSum; + lineWeighting = lineWeighting.OrderByDescending(i => i.Item2).ToList(); + + foreach (var line in lineWeighting) + { + // distribute the amount remaining to the lines + decimal amountToDistribute = amountRemainingToDistribute * line.Item2; + if (amountToDistribute > 0 && amountToDistribute < 0.01m) + { + amountToDistribute = 0.01m; // minimum amount to distribute + } + else if (amountToDistribute < 0 && amountToDistribute > -0.01m) + { + amountToDistribute = -0.01m; // minimum amount to distribute + } + else + { + amountToDistribute = Math.Round(amountToDistribute, 2); + } + + amountRemainingToDistribute = amountRemainingToDistribute - amountToDistribute; + invoice.InvoiceLineList[line.Item1].SetUnitAmountByLineTotal( + invoice.InvoiceLineList[line.Item1].LineTotalAmount + amountToDistribute + ); + + if (amountRemainingToDistribute == 0) + { + break; + } + } + + if (amountRemainingToDistribute > 0.01m || amountRemainingToDistribute < -0.01m) + { + // check if the amount remaining to distribute is too large + // this should not happen, but if it does, you'll have to manually fix the invoice or do more coding + ErrorMessage = "Rounding error when converting invoice to GBP. Amount remaining to distribute: " + amountRemainingToDistribute.ToString("C", CultureInfo.CurrentCulture); + _log.LogError(ErrorMessage); + return false; + } + else if (amountRemainingToDistribute != 0 && amountRemainingToDistribute < 0.01m && amountRemainingToDistribute > -0.01m) + { + invoice.InvoiceLineList[lineWeighting[0].Item1].SetUnitAmountByLineTotal( + invoice.InvoiceLineList[lineWeighting[0].Item1].LineTotalAmount + amountRemainingToDistribute + ); + } + } + + // Fix for CS1950: Ensure the list contains valid 'Model.Account.Invoice' objects + if (validate.IsValidExportInvoice(new List { (Model.Account.Invoice)invoice }) == false) + { + ErrorMessage = "Invoice failed validation after conversion to GBP. See logs for further details."; + _log.LogError(ErrorMessage); + return false; + } + else + { + return true; + } + } + + public List GetExchangeRateObjectList(List currencyCodeList, DateTime conversionDate) + { + Init(); + + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + return currentUow.CurrencyRepository.ReadExchangeRate(currencyCodeList, conversionDate); + } + } + + public decimal CurrencyConvertToGbp(string currencyCode, decimal amount, DateTime conversionDate) + { + Init(); + + if (string.IsNullOrEmpty(currencyCode) || currencyCode.Length != 3) + { + throw new Exception("Invalid currency code '" + currencyCode + "'"); + } + + Enum.TryParse(currencyCode, out Model.Account.CurrencyCode enumCurrencyCode); + + return CurrencyConvertToGbp(enumCurrencyCode, amount, conversionDate); + } + + public decimal CurrencyConvertToGbp(Model.Account.CurrencyCode currencyCode, decimal amount, DateTime conversionDate) + { + Init(); + + //checks + if (currencyCode == Model.Account.CurrencyCode.GBP || amount == 0M) + { + return amount; + } + + // read db + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + var exchageRate = currentUow.CurrencyRepository.ReadExchangeRate(currencyCode, conversionDate); + + if (exchageRate != null) + { + return amount / Convert.ToDecimal(exchageRate); + } + else + { + throw new Exception("Currency code '" + currencyCode + "' or date " + conversionDate.ToShortDateString() + " " + conversionDate.ToLongTimeString() + "' does not exist in the Exchange Rate table"); + } + } + } + + private DateTime GetHmrcMaxPeriodAvaible() + { + // HMRC monthly exchange rates are published on the penultimate Thursday of the month before + // For some leeeeeeeeway we'll use the penultimate Friday + + // find penultimate Friday for current month + var ukTimeNow = new Logic.Utilities.DateTime().ConvertUtcToUk(DateTime.UtcNow); + var monthDayCount = DateTime.DaysInMonth(ukTimeNow.Year, ukTimeNow.Month); + var thisMonthPenultimateFriday = DateTime.SpecifyKind(new DateTime(ukTimeNow.Year, ukTimeNow.Month, monthDayCount), DateTimeKind.Unspecified); + int count = 0; + int fridayCount = 0; + while (count != 15) + { + if (thisMonthPenultimateFriday.DayOfWeek == DayOfWeek.Friday) + { + fridayCount++; + if (fridayCount == 2) + { + break; + } + } + thisMonthPenultimateFriday = thisMonthPenultimateFriday.AddDays(-1); + count++; + } + + if (count == 15) + { + throw new Exception("Something went wrong here ErrorID:ef7f5d8f-0f7b-4014-aa65-421ecd5d7367"); + } + + var mostRecentPeriodAvaible = DateTime.SpecifyKind(new DateTime(ukTimeNow.Year, ukTimeNow.Month, 1), DateTimeKind.Unspecified); ; + if (ukTimeNow >= thisMonthPenultimateFriday) + { + mostRecentPeriodAvaible = mostRecentPeriodAvaible.AddMonths(1); + } + + return mostRecentPeriodAvaible; + } + + public void UpdateHmrcExchageRates() + { + Init(); + + _log.LogInformation("Starting update database HMRC exchange rates"); + + int exchangeRateSourceId = 1; // id for hmrc + + // retrive most recent data from db + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + var dbLatestRates = currentUow.CurrencyRepository.ReadExchangeRateLatest(); + + // sanity check, make sure there are no duplicates + int count = 0; + foreach (var exchageRate in dbLatestRates) + { + count = 0; + var currency = exchageRate.CurrencyCode; + foreach (var subExchageRate in dbLatestRates) + { + if (exchageRate.CurrencyCode == subExchageRate.CurrencyCode) + { + count = 1; + } + } + if (count > 1) + { + throw new FormatException("Datebase returned duplicate information"); + } + } + + // test for no data (first time running) + var hmrcMonthToRetrive = new DateTime(); + if (dbLatestRates.Any() == false) + { + hmrcMonthToRetrive = Data.Database.Constants.GetBusinessStartUk(); + } + + // set first/earliest month to retrive from hmrc website + foreach (var exchageRate in dbLatestRates) + { + var dbEndDateTime = exchageRate.DateTimeEndUk; + + if (hmrcMonthToRetrive == default(DateTime)) + { + hmrcMonthToRetrive = dbEndDateTime; + } + else + { + if (dbEndDateTime < hmrcMonthToRetrive) + { + hmrcMonthToRetrive = dbEndDateTime; + } + } + } + + // check - more coding required to retrive periods before 2021-01-01 + if (hmrcMonthToRetrive < DateTime.SpecifyKind(new DateTime(2021, 1, 1), DateTimeKind.Unspecified)) + { + throw new Exception("This function does not currently retirve exchange rates from HMRC for dates before 2021-01-01"); + } + + // check if retrival from hmrc is required + var hmrcMaxMonthAvaible = GetHmrcMaxPeriodAvaible(); + if (hmrcMonthToRetrive.Year == hmrcMaxMonthAvaible.Year && hmrcMonthToRetrive.Month > hmrcMaxMonthAvaible.Month) + { + // nothing to retrive + _log.LogInformation("Exchange rates curretly up to date, exiting."); + return; + } + + // get info from hmrc and insert data in db + while (hmrcMonthToRetrive <= hmrcMaxMonthAvaible) + { + count = 0; + + var url = new string( + "https://www.trade-tariff.service.gov.uk/api/v2/exchange_rates/files/monthly_xml_" + + hmrcMonthToRetrive.Year.ToString() + + "-" + + hmrcMonthToRetrive.Month.ToString() + + ".xml" + ); + var xd = new XDocument(); + xd = XDocument.Load(url); + + foreach (var exchageRate in dbLatestRates) + { + if (exchageRate.DateTimeStartUtc < hmrcMonthToRetrive) + { + //retrive exchange rate from xml + XElement node = xd.Root.Elements("exchangeRate").Where(e => e.Element("currencyCode").Value == exchageRate.CurrencyCode.ToString()).FirstOrDefault(); + decimal rate = decimal.Parse(node.Element("rateNew").Value); + rate = decimal.Round(rate, 4); + + // insert into db + currentUow.CurrencyRepository.InsertExchangeRate( + exchangeRateSourceId + , exchageRate.CurrencyCode + , rate + , new Utilities.DateTime().ConvertUkToUtc(hmrcMonthToRetrive) + , new Utilities.DateTime().ConvertUkToUtc(hmrcMonthToRetrive.AddMonths(1)) + ); + + count++; + } + } + + _log.LogInformation( + count + " new exchange rate(s) added to database for " + hmrcMonthToRetrive.ToString("MMMM") + " " + hmrcMonthToRetrive.Year.ToString() + ); + + hmrcMonthToRetrive = hmrcMonthToRetrive.AddMonths(1); + } + + if (_ownsUnitOfWork) + { + currentUow.Commit(); + } + + _log.LogInformation("Updating database currency exchange rates complete."); + } + } + } +} diff --git a/src/bnhtrade.Core/Logic/Account/GetInvoiceLineItem.cs b/src/bnhtrade.Core/Logic/Account/GetInvoiceLineItem.cs deleted file mode 100644 index 63ef5ef..0000000 --- a/src/bnhtrade.Core/Logic/Account/GetInvoiceLineItem.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace bnhtrade.Core.Logic.Account -{ - public class GetInvoiceLineItem - { - private Dictionary cache; - private Data.Database.Account.ReadInvoiceLineItem dbRead; - private Logic.Log.LogEvent log = new Logic.Log.LogEvent(); - private Logic.Account.GetTaxCodeInfo getTaxCode; - private Logic.Account.GetAccountCodeInfo getAccountCode; - - public GetInvoiceLineItem() - { - CacheInnit(); - dbRead = new Data.Database.Account.ReadInvoiceLineItem(); - getAccountCode = new GetAccountCodeInfo(); - getTaxCode = new GetTaxCodeInfo(); - } - - /// - /// Create new 'default' line item code when a requested item code is not found. - /// - public bool InsertNewOnNoMatch { get; set; } = false; - - public void CacheInnit() - { - cache = new Dictionary(); - } - - /// - /// Prefill cache in one SQL call, saves multiple SQL calls. - /// - /// List of item codes to lookup from database - /// Forces a database read (does not read cache) - public void CacheFill(List itemCodeList, bool forceDbRead = false) - { - if (itemCodeList == null || !itemCodeList.Any()) - { - return; - } - - var itemCodeQueryList = new List(); - foreach (var itemCode in itemCodeList) - { - if (forceDbRead) - { - cache.Remove(itemCode); - itemCodeQueryList.Add(itemCode); - } - else if (!cache.ContainsKey(itemCode)) - { - itemCodeQueryList.Add(itemCode); - } - } - - // query database - var resultList = dbRead.ByItemCode(itemCodeQueryList); - - // get account & tax codes dictionaries - var accountCodeDict = getAccountCode.ConvertToDictionary(getAccountCode.ByAccountCode(dbRead.AccountCodeList.Values.ToList())); - var taxCodeDict = getTaxCode.ConvertToDictionary(getTaxCode.GetByTaxCode(dbRead.TaxCodeList.Values.ToList())); - - // build final itemcode object and add to cache - foreach (var result in resultList.Values) - { - if (dbRead.AccountCodeList.ContainsKey(result.ItemCode)) - { - result.DefaultAccountCode = accountCodeDict[dbRead.AccountCodeList[result.ItemCode]]; - } - - if (dbRead.TaxCodeList.ContainsKey(result.ItemCode)) - { - result.DefaultTaxCode = taxCodeDict[dbRead.TaxCodeList[result.ItemCode]]; - } - - cache.Add(result.ItemCode, result); - } - } - - /// - /// Creates new 'default' item code entry. The item code and title are set the same and will require updating. - /// - /// Item code string - /// - public Model.Account.InvoiceLineItem CreateDefault(string itemCode) - { - new Data.Database.Account.CreateInvoiceLineItem().CreateDefault(itemCode); - var item = dbRead.ByItemCode(itemCode); - cache.Add(item.ItemCode, item); - return item; - } - - public Model.Account.InvoiceLineItem ByItemCode(string itemCode, bool forceDbRead = false) - { - if (string.IsNullOrWhiteSpace(itemCode)) - { - return null; - } - - CacheFill(new List { itemCode }, forceDbRead); - - Model.Account.InvoiceLineItem item = null; - if (cache.ContainsKey(itemCode)) - { - item = cache[itemCode]; - - // check title - if (!item.IsNewReviewRequired && item.Name == item.ItemCode) - { - throw new Exception - ("ItemCode found with the incomplete title. Update title and then try again."); - } - - return item; - } - - if (!InsertNewOnNoMatch) - { - return null; - } - else - { - return CreateDefault(itemCode); - } - } - - /// - /// Tests if ItemCode has Invoice line entry enabled - /// - /// Item code - /// Forces a database read (does not read cache) - /// - public bool InvoiceLineEntryEnabled(string itemCode, bool forceDbRead = false) - { - var item = ByItemCode(itemCode, forceDbRead); - if (item == null) - throw new Exception("Invalid item code"); - return item.InvoiceLineEntryEnabled; - } - - /// - /// Tests if ItemCode is new (default) and a review id required - /// - /// Item code - /// Forces a database read (does not read cache) - /// - public bool IsNewReviewRequired(string itemCode, bool forceDbRead = false) - { - var item = ByItemCode(itemCode, forceDbRead); - if (item == null) - { - throw new Exception("Invalid item code"); - } - return item.IsNewReviewRequired; - } - } -} \ No newline at end of file diff --git a/src/bnhtrade.Core/Logic/Account/GetTaxCodeInfo.cs b/src/bnhtrade.Core/Logic/Account/GetTaxCodeInfo.cs index 83c4eaf..cce8b34 100644 --- a/src/bnhtrade.Core/Logic/Account/GetTaxCodeInfo.cs +++ b/src/bnhtrade.Core/Logic/Account/GetTaxCodeInfo.cs @@ -15,11 +15,6 @@ namespace bnhtrade.Core.Logic.Account dbRead = new Data.Database.Account.ReadTaxCode(); } - public List GetAllActive() - { - return dbRead.GetAllActive(); - } - public List GetByTaxCode(List taxCodeList) { return dbRead.GetByTaxCode(taxCodeList); @@ -42,7 +37,7 @@ namespace bnhtrade.Core.Logic.Account /// Gets list of Tax Code Info for a given list of Sku Numbers /// /// List of SKU numbers - /// Dictionary, key is SkuNumber and value is Tax Code Info + /// Dictionary, key=SkuNumber and value=TaxCodeInfo public Dictionary GetBySkuNumber(List skuNumberList) { var returnList = new Dictionary(); diff --git a/src/bnhtrade.Core/Logic/Account/InvoiceLineItemService.cs b/src/bnhtrade.Core/Logic/Account/InvoiceLineItemService.cs new file mode 100644 index 0000000..6fdde4a --- /dev/null +++ b/src/bnhtrade.Core/Logic/Account/InvoiceLineItemService.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Logic.Account +{ + public class InvoiceLineItemService + { + private Logic.Log.LogEvent log = new Logic.Log.LogEvent(); + + /// + /// Creates new 'default' item code entry. The item code and title are set, will require updating by user before use. + /// + /// Item code string + /// + public Model.Account.InvoiceLineItem CreateNew(string itemCode) + { + new Data.Database.Account.CreateInvoiceLineItem().CreateDefault(itemCode); + var item = new Data.Database.Account.ReadInvoiceLineItem().ByItemCode(new List { itemCode }); + if (item == null || item.Count == 0) + { + log.LogError("InvoiceLineItemService.CreateNew", "Error creating new invoice line item in database for item code: " + itemCode); + throw new Exception("Error creating new invoice line item in database"); + } + return item[0]; + } + + public Dictionary GetLineItems(List itemCodes) + { + var dbResult = new Data.Database.Account.ReadInvoiceLineItem().ByItemCode(itemCodes); + var returnDict = new Dictionary(); + foreach ( var item in dbResult) + { + returnDict.Add(item.Value.ItemCode, item.Value); + } + return returnDict; + } + } +} \ No newline at end of file diff --git a/src/bnhtrade.Core/Logic/Account/PurchaseInvoice.cs b/src/bnhtrade.Core/Logic/Account/PurchaseInvoice.cs index 7866744..53fe9b0 100644 --- a/src/bnhtrade.Core/Logic/Account/PurchaseInvoice.cs +++ b/src/bnhtrade.Core/Logic/Account/PurchaseInvoice.cs @@ -6,7 +6,18 @@ using System.Threading.Tasks; namespace bnhtrade.Core.Logic.Account { - public class PurchaseInvoice : Core.Data.Database.Account.ReadPurchaseInvoice + public class PurchaseInvoice { + /// + /// Get purchase invoices by id + /// + /// purchase id list + /// dictionary where key=id, value=purchaseinvoice + public Dictionary GetPurchaseInvoice(IEnumerable purchaseIdList) + { + var dbRead = new Data.Database.Account.ReadPurchaseInvoice(); + dbRead.PurchaseInvoiceIdList = purchaseIdList; + return dbRead.Read(); + } } } diff --git a/src/bnhtrade.Core/Logic/Export/AccountInvoice/AmazonSettlement.cs b/src/bnhtrade.Core/Logic/Export/AccountInvoice/AmazonSettlement.cs new file mode 100644 index 0000000..122e72a --- /dev/null +++ b/src/bnhtrade.Core/Logic/Export/AccountInvoice/AmazonSettlement.cs @@ -0,0 +1,503 @@ +using Amazon.SQS.Model.Internal.MarshallTransformations; +using bnhtrade.Core.Data.Database.UnitOfWork; +using bnhtrade.Core.Model.Amazon; +using bnhtrade.Core.Test.Export; +using FikaAmazonAPI.ReportGeneration.ReportDataTable; +using Microsoft.Data.SqlClient; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.Eventing.Reader; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using static bnhtrade.Core.Model.Import.AmazonSettlement; + +namespace bnhtrade.Core.Logic.Export.AccountInvoice +{ + internal class AmazonSettlement : Data.Database.Connection + { + private readonly IUnitOfWork _providedUnitOfWork = null; + private readonly bool _ownsUnitOfWork = false; + + private Logic.Log.LogEvent _log = new Logic.Log.LogEvent(); + private List _lineItemCodeList = new List(); + private bool _settlementAmountIsTaxExclusive = false; // i.e. they're tax inclusive + + public AmazonSettlement() + { + _ownsUnitOfWork = true; + } + + internal AmazonSettlement(IUnitOfWork unitOfWork) + { + _providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); + _ownsUnitOfWork = false; + } + + public string ErrorMessage { get; private set; } + + private void Init() + { + ErrorMessage = null; + } + + /// + /// Creates invoices from Amazon settlement reports and saves the invoices to database Export Invoice table + /// + /// + public bool GenerateInvoicesForExportQueue(bool convertToGbp = true) + { + Init(); + _log.LogInformation("Starting processing of Amazon settlement data into export invoice table..."); + + // get list of unprocessed settlement reports to export + var settlementList = GetListOfUnprocessedSettlementReports(); + if (settlementList == null) + { + return false; + } + + // create list of settlement ids for later use + var settlementIdList = new List(); + foreach (var settlement in settlementList) + { + settlementIdList.Add(settlement.SettlementId); + } + + // create list of invoices from settlement reports + var invoiceList = CreateInvoices(settlementList, convertToGbp); + if (invoiceList == null || invoiceList.Any() == false) + { + return false; + } + + // add invoice to export queue and set settlements as processed + Console.Write("\rWriting to database... "); + using (UnitOfWork unitOfWork = new UnitOfWork()) + { + try + { + var queueService = new Logic.Export.AccountInvoice.QueueService(unitOfWork); + // add temp invoice numbers + queueService.AddTempInvoiceNumber(invoiceList, true); + + // write to the database (gets validated there) + var queueInsertResult = queueService.Insert(invoiceList); + if (queueService.ErrorMessageIsSet == false && queueInsertResult.Count() == invoiceList.Count()) + { + // set settlements to isprocessed + unitOfWork.ImportAmazonRepository.SetAmazonSettlementIsProcessed(settlementIdList, true); + unitOfWork.Commit(); + } + else + { + unitOfWork.Rollback(); + string error = queueService.ErrorMessage; + ErrorMessage = "Cancelled processing of Amazon settlement data into export invoice table. " + error; + _log.LogInformation("Cancelled processing of Amazon settlement data into export invoice table..."); + return false; + } + } + catch (Exception ex) + { + string error = "Exeception caught while writing Amazon settlement invoices to DB. Check logs"; + ErrorMessage = "Cancelled processing of Amazon settlement data into export invoice table: " + error; + _log.LogError(error, ex.Message); + _log.LogInformation("Cancelled processing of Amazon settlement data into export invoice table..."); + return false; + } + } + Console.Write("\r"); + _log.LogInformation("\rFinished processing of Amazon settlement data. " + invoiceList.Count() + " invoices created from " + settlementIdList.Count() + " Amazon settlement reports."); + return true; + } + + /// + /// Retrives a list of unprocessed settlement reports from the database, checks for gaps in settlement periods, and validates + /// the settlement data. + /// + /// Import will fail on new marketplace, add here to bypass this check + /// + private List GetListOfUnprocessedSettlementReports() + { + List settlementList = null; + + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + // get list of unprocssed settlement reports to export + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + settlementList = currentUow.ImportAmazonRepository.ReadAmazonSettlements(null, null, false).Values.ToList(); + + if (settlementList.Any() == false) + { + ErrorMessage = "No new settlement reports to process"; + return null; + } + + // test marketplace-name has been sucsessfully entered into settlement table -- + // as this is not supplied in the original report from Amazon and has to be inferred from settlement line data + // this is not picked up in validate stage as null value is valid + foreach (var settlement in settlementList) + { + if (settlement.MarketPlaceNameIsSet == false) + { + string error = "User action required: Enter market place name for amazon settlelment report id:" + settlement.SettlementId + "."; + ErrorMessage = "Cancelled processing of Amazon settlement data into export invoice table: " + error; + _log.LogError( + error + , "Unable to process settlement data from report '" + settlement.SettlementId + + "'. Report header table requires a market place name, which is not supplied in original " + + "report from Amazon. This is useually inferred from settlement lines. " + + "However, in this case, it was not not possible. Manual edit/entry for database table required." + ); + _log.LogInformation("Cancelled processing of Amazon settlement data into export invoice table..."); + return null; + } + } + + // check for time gaps between settlement periods + settlementList = settlementList.OrderBy(x => x.MarketPlace).ThenBy(x => x.StartDate).ToList(); + for (var i = 0; i < settlementList.Count; i++) + { + // first marketplace of type in list? retrive the previously completed settlement for that marketplace to compare datetimes + if (i == 0 || settlementList[i].MarketPlace != settlementList[i - 1].MarketPlace) + { + // get previously completed settlement for this marketplace + var completedSettlement = currentUow.ImportAmazonRepository.ReadAmazonSettlements( + null, new List { settlementList[i].MarketPlace.GetMarketplaceUrl() }, true, true, 1); + if (completedSettlement.Any()) + { + if (completedSettlement.FirstOrDefault().Value.EndDate != settlementList[i].StartDate) + { + string error = (settlementList[i].StartDate - settlementList[i - 1].EndDate).Days + " day gap in " + + settlementList[i].MarketPlace.GetMarketplaceUrl() + " settlement data (" + settlementList[i - 1].EndDate.ToString("dd MMM yyyy") + + " to " + settlementList[i].StartDate.ToString("dd MMM yyyy") + "). Ensure all settlement reports have been imported."; + ErrorMessage = error; + _log.LogError("Cancelled processing of Amazon settlement data into invoice export queue: " + error); + return null; + } + } + else + { + // first settlement for this marketplace, no previous settlement to compare against + // continue on + } + } + else + { + if (settlementList[i - 1].EndDate != settlementList[i].StartDate) + { + string error = (settlementList[i].StartDate - settlementList[i - 1].EndDate).Days + " day gap in " + + settlementList[i].MarketPlace + " settlement data (" + settlementList[i - 1].EndDate.ToString("dd MMM yyyy") + + " to " + settlementList[i].StartDate.ToString("dd MMM yyyy") + "). Ensure all settlement reports have been imported."; + ErrorMessage = error; + _log.LogError("Cancelled processing of Amazon settlement data into invoice export queue: " + error); + return null; + } + } + } + + } + + // validate settlelments + var validate = new Logic.Validate.AmazonSettlement(); + foreach (var settlement in settlementList) + { + if (!validate.IsValid(settlement)) + { + _log.LogError("Error procesing Amazon Settlement data for export.", validate.ValidationResultListToString()); + } + } + if (validate.IsValidResult == false) + { + string error = "Amazon settlements report returned from database failed validation. Check Logs"; + ErrorMessage = "Cancelled processing of Amazon settlement data into export invoice table: " + error; + _log.LogInformation("Cancelled processing of Amazon settlement data into export invoice table..."); + return null; + } + + return settlementList; + } + + private string BuildLineItemCode(Dictionary taxCodeBySkuNumer, string skuNumber, string transactionType, string amountType, string amountDescription) + { + // build the match string + // NB special case for global accounting sale and refunds (also note Goodlwill is included) and sku's where tax is included + string match01 = transactionType; + string match02 = amountType; + string match03 = amountDescription; + string matchString = "<" + match01 + "><" + match02 + "><" + match03 + ">"; + + // add tax info if required + if ((match01 == "Order" || match01 == "Refund") + && (match02 == "ItemPrice" || match02 == "Promotion" || match02 == "ItemWithheldTax")) + { + if (taxCodeBySkuNumer.ContainsKey(skuNumber)) + { + matchString = matchString + ""; + } + else + { + throw new Exception("Sku#" + skuNumber + " tax info not found in dictionary list."); + } + } + + // add to list of generated line item codes + _lineItemCodeList.Add(matchString); + + // return value + return matchString; + } + + private List CreateInvoices(List settlementList, bool convertToGbp = true) + { + // create list of settlement ids for later use + var settlementIdList = new List(); + foreach (var settlement in settlementList) + { + settlementIdList.Add(settlement.SettlementId); + } + + // get dictionary of sku-number to taxcodeId + Console.Write("\rBuilding SKU list... "); + var skuList = new List(); + foreach (var settlement in settlementList) + { + if (settlement.SettlementLineListIsSet) + { + foreach (var line in settlement.SettlementLineList) + { + if (line.SkuIsSet + && !string.IsNullOrWhiteSpace(line.Sku)) + { + skuList.Add(line.Sku); + } + } + } + } + var taxCodeBySkuNumerDict = new Logic.Account.GetTaxCodeInfo().GetBySkuNumber(skuList); + + // loop through each settlement and build list of invoices to export + Console.Write("\rBuilding invoices to export... "); + var invoiceList = new List(); + foreach (var settlement in settlementList) + { + // split settlement line list into months + var monthList = settlement.SettlementLineList + .GroupBy(x => new DateTime(x.PostDateTime.Year, x.PostDateTime.Month, 1, 0, 0, 0, x.PostDateTime.Kind)); + + int monthCount = 0; + foreach (var month in monthList) + { + monthCount++; + var itemCodeTotal = new Dictionary(); + foreach (var line in month) + { + string itemCode = BuildLineItemCode(taxCodeBySkuNumerDict, line.Sku, line.TransactionType, line.AmountType, line.AmountDescription); + if (itemCodeTotal.ContainsKey(itemCode)) + { + itemCodeTotal[itemCode] += line.Amount; + } + else + { + itemCodeTotal.Add(itemCode, line.Amount); + } + } + + // create invoice, one for each month + var invoice = new Model.Account.SalesInvoice(_settlementAmountIsTaxExclusive); + + // create invoice lines forsy + invoice.InvoiceLineList = new List(); + decimal lineUnitAmountTotal = 0m; + decimal lineTaxTotal = 0m; + foreach (var item in itemCodeTotal) + { + var line = new Model.Account.SalesInvoice.InvoiceLine(invoice); + line.ItemCode = item.Key; + line.Quantity = 1; + line.UnitAmount = item.Value; + lineUnitAmountTotal += item.Value; + lineTaxTotal += 0; + invoice.InvoiceLineList.Add(line); + } + + invoice.ContactName = settlement.MarketPlace.GetMarketplaceUrl(); + invoice.InvoiceCurrencyCode = Enum.Parse(settlement.CurrencyCode); + if (monthList.Count() == 1 || monthList.Count() == monthCount) + { invoice.InvoiceDate = settlement.EndDate; } + else + { invoice.InvoiceDate = new DateTime(month.Key.Year, month.Key.Month, 1, 0, 0, 0, DateTimeKind.Utc).AddMonths(1).AddDays(-1); } + invoice.InvoiceDueDate = settlement.DepositDate; + invoice.InvoiceReference = settlement.SettlementId; + invoice.InvoiceTotalAmount = lineUnitAmountTotal + lineTaxTotal; + if (invoice.InvoiceTotalAmount < 0) { invoice.IsCreditNote = true; } + else { invoice.IsCreditNote = false; } + + // invoice complete, add to list + invoiceList.Add(invoice); + } + } + + // sort list of invoices + invoiceList = invoiceList.OrderBy(x => x.InvoiceReference).ThenBy(x => x.InvoiceDate).ToList(); + + // check invoice total against settlement totals + var invoiceTotal = new Dictionary(); + for (int i = 0; i < invoiceList.Count(); i++) + { + if (invoiceTotal.ContainsKey(invoiceList[i].InvoiceReference)) + { + invoiceTotal[invoiceList[i].InvoiceReference] += invoiceList[i].InvoiceTotalAmount.GetValueOrDefault(); + } + else + { + invoiceTotal.Add(invoiceList[i].InvoiceReference, invoiceList[i].InvoiceTotalAmount.GetValueOrDefault()); + } + } + foreach (var settlement in settlementList) + { + if (settlement.TotalAmount != invoiceTotal[settlement.SettlementId]) + { + throw new Exception("invoice totals does not match settlement total."); + } + } + if (settlementIdList.Count != invoiceTotal.Count()) + { + string error = "Not all settlements have been transposed into invoices."; + ErrorMessage = "Cancelled processing of Amazon settlement data into export invoice table: " + error; + _log.LogError(error); + _log.LogInformation("Cancelled processing of Amazon settlement data into export invoice table..."); + return null; + } + + // add invoice item code data to lines + // also clean invoices of any disabled lines (remove lines and possibly invoices) + var lineItemService = new Logic.Account.InvoiceLineItemService(); + var lineItemDict = lineItemService.GetLineItems(_lineItemCodeList); + bool newTypeFound = false; + string newTypeText = null; + + for (int i = 0; i < invoiceList.Count(); i++) + { + for (int j = 0; j < invoiceList[i].InvoiceLineList.Count(); j++) + { + var itemCode = lineItemDict[invoiceList[i].InvoiceLineList[j].ItemCode]; + // flag new type and throw exception further on + if (itemCode.IsNewReviewRequired) + { + newTypeFound = true; + if (string.IsNullOrWhiteSpace(itemCode.ItemCode)) + { + newTypeText = itemCode.Name; + } + else + { + newTypeText = itemCode.ItemCode; + } + } + // clean invoices of any disabled lines (remove lines and possibly invoices) + else if (itemCode.InvoiceLineEntryEnabled == false) + { + // remove line + invoiceList[i].InvoiceTotalAmount = + invoiceList[i].InvoiceTotalAmount + - (invoiceList[i].InvoiceLineList[j].Quantity * invoiceList[i].InvoiceLineList[j].UnitAmount); + invoiceList[i].InvoiceLineList.RemoveAt(j); + j = j - 1; + + // remove invoice? + if (invoiceList[i].InvoiceLineList.Count == 0) + { + invoiceList.RemoveAt(i); + if (i > 0) + { + i = i - 1; + } + } + } + // get here add info to lines + else + { + invoiceList[i].InvoiceLineList[j].Account = itemCode.DefaultAccountCode; + invoiceList[i].InvoiceLineList[j].Description = itemCode.Name; + invoiceList[i].InvoiceLineList[j].ItemCode = itemCode.ItemCode; + invoiceList[i].InvoiceLineList[j].TaxCode = itemCode.DefaultTaxCode; + } + } + } + + if (newTypeFound) + { + string error = "User action required: Parameters required for Invoice line item code '" + newTypeText + "'. Set in tblAccountInvoiceLineItem."; + ErrorMessage = "Cancelled processing of Amazon settlement data into export invoice table: " + error; + _log.LogError(error); + _log.LogInformation("Cancelled processing of Amazon settlement data into export invoice table..."); + return null; + } + + // postfix invoices references that span multiple months with -n + if (invoiceList.Count > 1) + { + string lastRef = invoiceList[0].InvoiceReference; + int countRef = 1; + for (int i = 1; i < invoiceList.Count(); i++) + { + if (invoiceList[i].InvoiceReference == lastRef) + { + if (countRef == 1) + { + invoiceList[i - 1].InvoiceReference = lastRef + "-" + countRef; + } + invoiceList[i].InvoiceReference = lastRef + "-" + (countRef += 1); + } + else + { + // shouldn't normally be more than 2 date ranges, log and move on. + if (countRef > 2) + { + _log.LogError( + countRef + " invoices where created from Amazon Settlement Id" + lastRef + "." + , "Settlement period appears to span more 3 months or more. Whilst this is possible, it is unsual. Confirm his is correct."); + } + + lastRef = invoiceList[i].InvoiceReference; + countRef = 1; + } + } + } + + // check for any non gbp invoices + if (convertToGbp) + { + for (var i = 0; i < invoiceList.Count; i++) + { + if (invoiceList[i].InvoiceCurrencyCode != Model.Account.CurrencyCode.GBP) + { + // convert to gbp + var invoice = invoiceList[i]; + var currencyService = new Logic.Account.CurrencyService(); + if (currencyService.ConvertInvoiceToGbp(invoice) == false) + { + ErrorMessage = "Cancelled processing of Amazon settlement data into export invoice table: Error converting invoice to GBP."; + return null; // error message is set in currency service + } + } + } + } + return invoiceList; + } + } +} \ No newline at end of file diff --git a/src/bnhtrade.Core/Logic/Export/AccountInvoice/InvoiceService.cs b/src/bnhtrade.Core/Logic/Export/AccountInvoice/InvoiceService.cs new file mode 100644 index 0000000..5fa5772 --- /dev/null +++ b/src/bnhtrade.Core/Logic/Export/AccountInvoice/InvoiceService.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Logic.Export.AccountInvoice +{ + internal class InvoiceService + { + } +} diff --git a/src/bnhtrade.Core/Logic/Export/AccountInvoice/QueueService.cs b/src/bnhtrade.Core/Logic/Export/AccountInvoice/QueueService.cs new file mode 100644 index 0000000..5f5df6a --- /dev/null +++ b/src/bnhtrade.Core/Logic/Export/AccountInvoice/QueueService.cs @@ -0,0 +1,293 @@ +using bnhtrade.Core.Data.Database.UnitOfWork; +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Transactions; +using static bnhtrade.Core.Model.Account.Invoice; +using static System.Formats.Asn1.AsnWriter; + +namespace bnhtrade.Core.Logic.Export.AccountInvoice +{ + + /// + /// Processes the Export Invoice table and exports to Xero + /// + public class QueueService + { + private Log.LogEvent _log = new Log.LogEvent(); + private IEnumerable _exportSaleInvoiceIdList = new List(); + private readonly IUnitOfWork _providedUnitOfWork = null; + private readonly bool _ownsUnitOfWork = false; + + public QueueService() + { + _ownsUnitOfWork = true; + } + + internal QueueService(IUnitOfWork unitOfWork) + { + _providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); + _ownsUnitOfWork = false; + } + + public string ErrorMessage { get; private set; } = null; + + public bool ErrorMessageIsSet + { + get + { + if ( ErrorMessage == null) + { + return false; + } + else + { + return true; + } + } + } + + private void Init() + { + ErrorMessage = null; + } + + public void ImportAll() + { + new Logic.Export.AccountInvoice.AmazonSettlement().GenerateInvoicesForExportQueue(true); + } + + public string GetNextTempInvoiceNumber() + { + Init(); + + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + string result = null; + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + result = "_tmp" + currentUow.SequenceGenerator.GetNext("ExportTempInvoiceNumber").ToString("00000000"); + if (_ownsUnitOfWork) + { + currentUow.Commit(); + } + } + return result; + } + + public void AddTempInvoiceNumber(IEnumerable invoiceList, bool overwriteExisting) + { + Init(); + + for (int i = 0; i < invoiceList.Count(); i++) + { + if (invoiceList.ElementAt(i).InvoiceNumber != null && overwriteExisting == false) + { + continue; + } + invoiceList.ElementAt(i).InvoiceNumber = GetNextTempInvoiceNumber(); + } + } + + /// + /// Read invoices from datbase (with validation) + /// + /// list of invoice id to retrive + /// Dictionary where key=id, value=invoice-model + /// Failed validation + public Dictionary ReadInvoiceById(IEnumerable invoiceIdList) + { + Init(); + + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + using (_ownsUnitOfWork ? currentUow : null) + { + var returnList = currentUow.ExportInvoiceRepository.GetSalesInvoiceById(invoiceIdList); + var validate = new Logic.Validate.Invoice(); + bool isValid = validate.IsValidExportInvoice(returnList.Values.ToList()); + + if (isValid == false) + { + ErrorMessage = "Reading invoices from database failed validation. See logs for further details."; + _log.LogError("ErrorMessage", validate.ValidationResultListToString()); + throw new Exception(ErrorMessage); + } + + return returnList; + } + } + + /// + /// Adds sales invoice to database export table, ready for export + /// + /// List of sales invoices + /// Dictionary where key=invoice id, value=invoice model + internal Dictionary Insert(IEnumerable invoiceList) + { + Init(); + + // validate the list of invoices + var validateInvoice = new Validate.Invoice(); + validateInvoice.IsValidExportInvoice(invoiceList); + if (validateInvoice.IsValidResult == false) + { + string error = "Sales invoice(s) failed validation."; + _log.LogError(error, validateInvoice.ValidationResultListToString()); + ErrorMessage = error + " Check logs for further info"; + return null; + } + validateInvoice = null; + + // save to database + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + var result = currentUow.ExportInvoiceRepository.InsertSalesInvoices(invoiceList); + if (_ownsUnitOfWork) + { + currentUow.Commit(); + } + return result; + } + } + + /// + /// Gets count of new invoices ready to export to external accounting software + /// + /// Count of new invoices as int + public int Count(Model.Account.InvoiceType invoiceType) + { + Init(); + + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + var result = currentUow.ExportInvoiceRepository.GetNewInvoiceNumbers(invoiceType).Count(); + if (_ownsUnitOfWork) + { + currentUow.Commit(); + } + return result; + } + } + + /// + /// + /// + /// + /// + public void ExportSalesInvoice(string filePath, int firstInvoiceNumber) + { + Init(); + + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + var invoiceType = Model.Account.InvoiceType.Sale; + + var idList = currentUow.ExportInvoiceRepository.GetNewInvoiceNumbers(invoiceType); + _exportSaleInvoiceIdList = idList.Keys.ToList(); + var invoiceList = ReadInvoiceById(idList.Keys.ToList()); + + var exportToFile = new Data.Xero.SalesInvoice(); + exportToFile.ExportToCsv(invoiceList.Values.ToList(), firstInvoiceNumber, filePath); + } + } + + /// + /// Call this after ExportSalesInvoice() to mark exported invoices as complete + /// + /// number of invoices effected + public int? ExportSalesInvoiceIsComplete() + { + Init(); + + if (_exportSaleInvoiceIdList.Any() == false) + { + ErrorMessage = "Nothing to set as complete, did you call the ExportSalesInvoice method first?"; + return null; + } + + var parameters = new Dictionary(); + foreach (var id in _exportSaleInvoiceIdList) + { + parameters.Add(id, true); + } + + // update database + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + int count = currentUow.ExportInvoiceRepository.SetInvoiceIsCompleteValue(parameters); + if (_exportSaleInvoiceIdList.Count() == count) + { + currentUow.Commit(); + return count; + } + else + { + currentUow.Rollback(); + ErrorMessage = "ExportSalesInvoiceIsComplete() Incorrect number of rows updated, changes rolled back."; + _log.LogError(ErrorMessage); + throw new Exception(ErrorMessage); + } + } + } + } +} + diff --git a/src/bnhtrade.Core/Logic/Export/AmazonSettlement.cs b/src/bnhtrade.Core/Logic/Export/AmazonSettlement.cs deleted file mode 100644 index 9f4db85..0000000 --- a/src/bnhtrade.Core/Logic/Export/AmazonSettlement.cs +++ /dev/null @@ -1,347 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Transactions; - -namespace bnhtrade.Core.Logic.Export -{ - public class AmazonSettlement - { - private Logic.Log.LogEvent log = new Logic.Log.LogEvent(); - private List lineItemCodeList = new List(); - - public AmazonSettlement() - { - } - - public void ToInvoice() - { - log.LogInformation("Starting processing of Amazon settlement data into export invoice table..."); - - // check settlement reports consistancy - var consistencyCheck = new Data.Database.Consistency.ImportAmazonSettlement().PeriodDateGaps(); - if (consistencyCheck == false) - { return; } - - // get list of unprocssed settlement reports to export - var settlementData = new Data.Database.Import.AmazonSettlementRead(); - var settlementList = settlementData.AllUnprocessed(); - settlementData = null; - - if (settlementList == null) - { - log.LogInformation("No new settlements to process, exiting import..."); - return; - } - - // create list of settlement ids - var settlementIdList = new List(); - for (int i = 0; i < settlementList.Count(); i++) - { - settlementIdList.Add(settlementList[i].SettlementId); - } - - // test marketplace-name has been sucsessfully entered into settlement table -- - // as this is not supplied in the original report from Amazon and has to be inferred from settlement line data - // this is not picked up in validate stage as null value is valid - for (int i = 0; i < settlementList.Count(); i++) - { - if (!settlementList[i].MarketPlaceNameIsSet) - { - log.LogError( - "Action required: Enter market place name for settlelment report id " + settlementList[i].SettlementId + "." - , "Unable to process settlement data from one settlement report '" + settlementList[i].SettlementId + - "'. Report header table requires a market place name, which is not supplied in original " + - "report from Amazon. This is useually inferred from settlement lines. " + - "However, in this case, it was not not possible. Manual edit/entry for database table required." - ); - } - } - - // validate settlelments - var validate = new Logic.Validate.AmazonSettlement(); - for (int i = 0; i < settlementList.Count(); i++) - { - if (!validate.IsValid(settlementList[i])) - { - log.LogError("Error procesing Amazon Settlement data for export.", validate.ValidationResultListToString()); - } - } - if (validate.IsValidResult == false) { return; } - - // get dictionary of sku-number to taxcodeId - Console.Write("\rBuilding SKU list... "); - var skuList = new List(); - foreach (var settlement in settlementList) - { - if (settlement.SettlementLineListIsSet) - { - foreach (var line in settlement.SettlementLineList) - { - if (line.SkuIsSet - && !string.IsNullOrWhiteSpace(line.Sku)) - { - skuList.Add(line.Sku); - } - } - } - } - var taxCodeBySkuNumer = new Logic.Account.GetTaxCodeInfo().GetBySkuNumber(skuList); - - // loop through each settlement and build list of invoices to export - Console.Write("\rBuilding invoices to export... "); - var invoiceList = new List(); - for (int i = 0; i < settlementList.Count(); i++) - { - // split settlement line list into months - // List - var monthList = settlementList[i].SettlementLineList - .GroupBy(x => new DateTime(x.PostDateTime.Year, x.PostDateTime.Month, 1, 0, 0, 0, x.PostDateTime.Kind)); - //.GroupBy(x => string.Format("{0}-{1}", x.PostDateTime.Year, x.PostDateTime.Month)); - //.GroupBy(x => new { x.PostDateTime.Month, x.PostDateTime.Year }); - - int monthCount = 0; - foreach (var month in monthList) - { - monthCount++; - var itemCodeTotal = new Dictionary(); - foreach (var line in month) - { - string itemCode = BuildLineItemCode(taxCodeBySkuNumer, line.Sku, line.TransactionType, line.AmountType, line.AmountDescription); - if (itemCodeTotal.ContainsKey(itemCode)) - { - itemCodeTotal[itemCode] += line.Amount; - } - else - { - itemCodeTotal.Add(itemCode, line.Amount); - } - } - - // create invoice, one for each month - var invoice = new Model.Account.SalesInvoice(); - - // create invoice lines forsy - invoice.InvoiceLineList = new List(); - decimal lineNetTotal = 0m; - decimal lineTaxTotal = 0m; - foreach (var item in itemCodeTotal) - { - var line = new Model.Account.SalesInvoice.InvoiceLine(invoice.UnitAmountIsTaxExclusive); - line.ItemCode = item.Key; - line.Quantity = 1; - line.UnitAmount = item.Value; - lineNetTotal += item.Value; - lineTaxTotal += 0; - invoice.InvoiceLineList.Add(line); - } - - invoice.ContactName = settlementList[i].MarketPlaceName; - invoice.InvoiceCurrencyCode = settlementList[i].CurrencyCode; - if (monthList.Count() == 1 || monthList.Count() == monthCount) - { invoice.InvoiceDate = settlementList[i].EndDate; } - else - { invoice.InvoiceDate = new DateTime(month.Key.Year, month.Key.Month, 1, 0, 0, 0, DateTimeKind.Utc).AddMonths(1).AddDays(-1); } - invoice.InvoiceDueDate = settlementList[i].DepositDate; - invoice.InvoiceReference = settlementList[i].SettlementId; - invoice.InvoiceTotalAmount = lineNetTotal + lineTaxTotal; - if (invoice.InvoiceTotalAmount < 0) { invoice.IsCreditNote = true; } - else { invoice.IsCreditNote = false; } - - // invoice complete, add to list - invoiceList.Add(invoice); - } - } - - // sort list of invoices - invoiceList = invoiceList.OrderBy(x => x.InvoiceReference).ThenBy(x => x.InvoiceDate).ToList(); - - // check invoice total against settlement totals - var invoiceTotal = new Dictionary(); - for (int i = 0; i < invoiceList.Count(); i++) - { - if (invoiceTotal.ContainsKey(invoiceList[i].InvoiceReference)) - { - invoiceTotal[invoiceList[i].InvoiceReference] += invoiceList[i].InvoiceTotalAmount.GetValueOrDefault(); - } - else - { - invoiceTotal.Add(invoiceList[i].InvoiceReference, invoiceList[i].InvoiceTotalAmount.GetValueOrDefault()); - } - } - for (int i = 0; i < settlementList.Count(); i++) - { - if (settlementList[i].TotalAmount != invoiceTotal[settlementList[i].SettlementId]) - { - throw new Exception("invoice totals does not match settlement total."); - } - } - if (settlementIdList.Count != invoiceTotal.Count()) - { - log.LogError("Stopping Settlement export. Not all settlements have been transposed into invoices."); - return; - } - - // add invoice item code data to lines - // also clean invoices of any disabled lines (remove lines and possibly invoices) - var getLineItemInfo = new Logic.Account.GetInvoiceLineItem(); - getLineItemInfo.InsertNewOnNoMatch = true; - getLineItemInfo.CacheFill(lineItemCodeList); - bool newTypeFound = false; - string newTypeText = null; - - for (int i = 0; i < invoiceList.Count(); i++) - { - for (int j = 0; j < invoiceList[i].InvoiceLineList.Count(); j++) - { - var itemCode = getLineItemInfo.ByItemCode(invoiceList[i].InvoiceLineList[j].ItemCode); - // error! itemCode should never be null - if (itemCode == null) - { - throw new Exception("Item code is null"); - } - // flag new type and throw exception further on - else if (itemCode.IsNewReviewRequired) - { - newTypeFound = true; - if (string.IsNullOrWhiteSpace(itemCode.ItemCode)) - { - newTypeText = itemCode.Name; - } - else - { - newTypeText = itemCode.ItemCode; - } - } - // clean invoices of any disabled lines (remove lines and possibly invoices) - else if (itemCode.InvoiceLineEntryEnabled == false) - { - // remove line - invoiceList[i].InvoiceTotalAmount = invoiceList[i].InvoiceTotalAmount - invoiceList[i].InvoiceLineList[j].LineTotalAmount; - invoiceList[i].InvoiceLineList.RemoveAt(j); - j = j - 1; - - // remove invoice? - if (invoiceList[i].InvoiceLineList.Count == 0) - { - invoiceList.RemoveAt(i); - if (i > 0) - { - i = i - 1; - } - } - } - // get here add info to lines - else - { - invoiceList[i].InvoiceLineList[j].AccountCode = itemCode.DefaultAccountCode; - invoiceList[i].InvoiceLineList[j].Description = itemCode.Name; - invoiceList[i].InvoiceLineList[j].ItemCode = itemCode.ItemCode; - invoiceList[i].InvoiceLineList[j].TaxCode = itemCode.DefaultTaxCode; - } - } - } - - if (newTypeFound) - { - if (newTypeFound) - { - throw new Exception("Parameters required for Invoice line item code '"+ newTypeText + "'. Set in tblAccountInvoiceLineItem."); - } - return; - } - - // postfix invoices references that span multiple months with -n - if (invoiceList.Count > 1) - { - string lastRef = invoiceList[0].InvoiceReference; - int countRef = 1; - for (int i = 1; i < invoiceList.Count(); i++) - { - if (invoiceList[i].InvoiceReference == lastRef) - { - if (countRef == 1) - { - invoiceList[i - 1].InvoiceReference = lastRef + "-" + countRef; - } - invoiceList[i].InvoiceReference = lastRef + "-" + (countRef += 1); - } - else - { - // shouldn't normally be more than 2 date ranges, log and move on. - if (countRef > 2) - { - log.LogError( - countRef + " invoices where created from Amazon Settlement Id" + lastRef + "." - , "Settlement period appears to span more 3 months or more. Whilst this is possible, it is unsual. Confirm his is correct."); - } - - lastRef = invoiceList[i].InvoiceReference; - countRef = 1; - } - } - } - - Console.Write("\rWriting to database... "); - using (TransactionScope scope = new TransactionScope()) - { - try - { - var saveInv = new Logic.Export.SalesInvoice(); - // add temp invoice numbers - saveInv.AddTempInvoiceNumber(invoiceList, true); - - // write to the database (gets validated there) - saveInv.SaveSalesInvoice(invoiceList); - - // set settlements to isprocessed - new Data.Database.Import.AmazonSettlementUpdate().SetIsProcessedTrue(settlementIdList); - - scope.Complete(); - } - catch (Exception ex) - { - scope.Dispose(); - log.LogError("Exeception caught while writing Amazon settlement invoices to DB. Changes were rolled back." - , ex.Message); - return; - } - } - Console.Write("\r"); - log.LogInformation("\rFinished processing of Amazon settlement data. " + invoiceList.Count() + " invoices created from " + settlementIdList.Count() + " Amazon settlement reports."); - } - - private string BuildLineItemCode(Dictionary taxCodeBySkuNumer, string skuNumber, string transactionType, string amountType, string amountDescription) - { - // build the match string - // NB special case for global accounting sale and refunds (also note Goodlwill is included) and sku's where tax is included - string match01 = transactionType; - string match02 = amountType; - string match03 = amountDescription; - string matchString = "<" + match01 + "><" + match02 + "><" + match03 + ">"; - - // add tax info if required - if ((match01 == "Order" || match01 == "Refund") - && (match02 == "ItemPrice" || match02 == "Promotion" || match02 == "ItemWithheldTax")) - { - if (taxCodeBySkuNumer.ContainsKey(skuNumber)) - { - matchString = matchString + ""; - } - else - { - throw new Exception("Sku#" + skuNumber + " tax info not found in dictionary list."); - } - } - - // add to list of generated line item codes - lineItemCodeList.Add(matchString); - - // return value - return matchString; - } - } -} \ No newline at end of file diff --git a/src/bnhtrade.Core/Logic/Export/AmazonSubmitFile.cs b/src/bnhtrade.Core/Logic/Export/AmazonSubmitFile.cs index 0963170..d12b906 100644 --- a/src/bnhtrade.Core/Logic/Export/AmazonSubmitFile.cs +++ b/src/bnhtrade.Core/Logic/Export/AmazonSubmitFile.cs @@ -49,7 +49,7 @@ namespace bnhtrade.Core.Logic.Export int queueId = 0; using (var scope = new TransactionScope()) { - queueId = new Data.Database.Export.CreateAmazonFeedSubmission().Execute(feedType, fileInfo); + queueId = new Data.Database.Export.AmazonFeedSubmissionInsert().Execute(feedType, fileInfo); // validate the result var validateResults = new List(); @@ -89,7 +89,7 @@ namespace bnhtrade.Core.Logic.Export } // set the amazon feed Id - var dbUpdate = new Data.Database.Export.UpdateAmazonFeedSubmission(); + var dbUpdate = new Data.Database.Export.AmazonFeedSubmissionUpdate(); dbUpdate.AddAmazonFeedId(queueId, feedSubmission.FeedSubmissionId); // update progress info diff --git a/src/bnhtrade.Core/Logic/Export/SalesInvoice.cs b/src/bnhtrade.Core/Logic/Export/SalesInvoice.cs deleted file mode 100644 index ab9e455..0000000 --- a/src/bnhtrade.Core/Logic/Export/SalesInvoice.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace bnhtrade.Core.Logic.Export -{ - public class SalesInvoice - { - private Logic.Log.LogEvent log = new Log.LogEvent(); - - public SalesInvoice() - { - } - - public string GetNextTempInvoiceNumber() - { - var sequence = new Data.Database.Programmability.Sequence(); - return "_tmp" + sequence.GetNext("ExportTempInvoiceNumber").ToString("00000000"); - } - - public void AddTempInvoiceNumber(IEnumerable invoiceList, bool overwriteExisting) - { - for (int i = 0; i < invoiceList.Count(); i++) - { - if (invoiceList.ElementAt(i).InvoiceNumber != null && overwriteExisting == false) - { - continue; - } - invoiceList.ElementAt(i).InvoiceNumber = GetNextTempInvoiceNumber(); - } - } - - public void SaveSalesInvoice(List invoiceList) - { - // validate the list of invoices - var validateInvoice = new Logic.Validate.SalesInvoice(); - validateInvoice.IsValidExportInvoice(invoiceList); - if (validateInvoice.IsValidResult == false) - { - log.LogError("Invalid Sales invoice(s) found. See extended info.", validateInvoice.ValidationResultListToString()); - return; - } - validateInvoice = null; - - // save to database - new Data.Database.Export.CreateSalesInvoice().Execute(invoiceList); - } - } -} \ No newline at end of file diff --git a/src/bnhtrade.Core/Logic/Import/AmazonSettlement.cs b/src/bnhtrade.Core/Logic/Import/AmazonSettlement.cs index 8530c81..bb9a18d 100644 --- a/src/bnhtrade.Core/Logic/Import/AmazonSettlement.cs +++ b/src/bnhtrade.Core/Logic/Import/AmazonSettlement.cs @@ -1,70 +1,97 @@ using bnhtrade.Core.Data.Amazon.Report; +using bnhtrade.Core.Data.Database.UnitOfWork; +using System; using System.Linq; namespace bnhtrade.Core.Logic.Import { public class AmazonSettlement { + private readonly IUnitOfWork _providedUnitOfWork = null; + private readonly bool _ownsUnitOfWork = false; private Data.Amazon.Report.SettlementReport amazonReport; private Logic.Log.LogEvent log = new Log.LogEvent(); - public AmazonSettlement() { - amazonReport = new Data.Amazon.Report.SettlementReport(); + _ownsUnitOfWork = true; + } + + internal AmazonSettlement(IUnitOfWork unitOfWork) + { + _providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); + _ownsUnitOfWork = false; } public void SyncDatabase() { string operation = "Import Amazon Settlement Reports"; - log.LogInformation("Started '" + operation + "' operation."); // get avaiable reports from amazon api - var spapiReportIdList = amazonReport.ListAvaliableReports(); - int reportCount = spapiReportIdList.Count(); - - if (reportCount == 0) + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) { - log.LogInformation("Exiting '" + operation + "' operation. No settlement reports availble on Amazon SP-API."); - return; + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; } - // query db and remove reports that have already been imported - var dbReportList = new Data.Database.Import.AmazonSettlementHeaderRead().BySpapiReportId(spapiReportIdList); - foreach (var dbReport in dbReportList) + + + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) { - if (dbReport.SpapiReportIdIsSet) + // get avaiable reports from amazon api + var spapiReportIdList = amazonReport.ListAvaliableReports(); + int reportCount = spapiReportIdList.Count(); + + if (reportCount == 0) { - for (int i = 0; i < spapiReportIdList.Count; i++) + log.LogInformation("Exiting '" + operation + "' operation. No settlement reports availble on Amazon SP-API."); + return; + } + + // query db and remove reports that have already been imported + var dbReportList = currentUow.ImportAmazonRepository.ReadAmazonSettlementHeaderInfoBySpapiReportId(spapiReportIdList); + foreach (var dbReport in dbReportList) + { + if (dbReport.SpapiReportIdIsSet) { - if (spapiReportIdList[i] == dbReport.SpapiReportId) + for (int i = 0; i < spapiReportIdList.Count; i++) { - spapiReportIdList.RemoveAt(i); - i--; - break; + if (spapiReportIdList[i] == dbReport.SpapiReportId) + { + spapiReportIdList.RemoveAt(i); + i--; + break; + } } } } - } - if (!spapiReportIdList.Any()) - { - log.LogInformation("Exiting '" + operation + "' operation. No new reports to import (" + reportCount + " avaibale)."); + if (!spapiReportIdList.Any()) + { + log.LogInformation("Exiting '" + operation + "' operation. No new reports to import (" + reportCount + " avaibale)."); + return; + } + + // import into database + for (int i = 0; i < spapiReportIdList.Count(); i++) + { + UI.Console.WriteLine("Importing settlement report " + (i + 1) + " of " + spapiReportIdList.Count() + " (ReportID:" + spapiReportIdList[i] + ")."); + var filePath = amazonReport.GetReportFile(spapiReportIdList[i]); + bool ack = currentUow.ImportAmazonRepository.CreateAmazonSettlements(filePath, spapiReportIdList[i]); + log.LogInformation("Settlment Report imported (ReportID:" + spapiReportIdList[i] + ")."); + } + + if (_ownsUnitOfWork) + { + currentUow.Commit(); + } return; } - - // import into database - var dbInsert = new Data.Database.Import.AmazonSettlementInsert(); - for (int i = 0; i < spapiReportIdList.Count(); i++) - { - UI.Console.WriteLine("Importing settlement report " + (i + 1) + " of " + spapiReportIdList.Count() + " (ReportID:" + spapiReportIdList[i] + ")."); - var filePath = amazonReport.GetReportFile(spapiReportIdList[i]); - bool ack = dbInsert.ByFlatFile(filePath, spapiReportIdList[i]); - log.LogInformation("Settlment Report imported (ReportID:" + spapiReportIdList[i] + ")."); - } - - return; } } } diff --git a/src/bnhtrade.Core/Logic/Utilities/ListFunction.cs b/src/bnhtrade.Core/Logic/Utilities/ListFunction.cs index 5712e89..2d04095 100644 --- a/src/bnhtrade.Core/Logic/Utilities/ListFunction.cs +++ b/src/bnhtrade.Core/Logic/Utilities/ListFunction.cs @@ -16,7 +16,7 @@ namespace bnhtrade.Core.Logic.Utilities /// /// /// Unique list - public List UniqueList(List inputList, bool removeNullorWhitespace = true) + public List UniqueList(IEnumerable inputList, bool removeNullorWhitespace = true) { List outputList = new List(); diff --git a/src/bnhtrade.Core/Logic/Utilities/NightlyRoutine.cs b/src/bnhtrade.Core/Logic/Utilities/NightlyRoutine.cs index 153b206..a5a843e 100644 --- a/src/bnhtrade.Core/Logic/Utilities/NightlyRoutine.cs +++ b/src/bnhtrade.Core/Logic/Utilities/NightlyRoutine.cs @@ -18,8 +18,6 @@ namespace bnhtrade.Core.Logic.Utilities { log.LogInformation("Nightly scheduled tasks started."); - var export = new bnhtrade.Core.Logic.Export.AmazonSettlement(); - bool stockUpdate = false; bool exchangeRate = false; bool accountProcess = false; @@ -28,9 +26,9 @@ namespace bnhtrade.Core.Logic.Utilities { try { - if (stockUpdate == false) { stockUpdate = true; new bnhtrade.Core.Logic.Import.Amazon().SyncAllWithDatabase(); ; } - if (exchangeRate == false) { exchangeRate = true; new Logic.Account.Currency().UpdateHmrcExchageRates(); } - if (accountProcess == false) { accountProcess = true; export.ToInvoice(); } + if (stockUpdate == false) { stockUpdate = true; new Logic.Import.Amazon().SyncAllWithDatabase(); ; } + if (exchangeRate == false) { exchangeRate = true; new Logic.Account.CurrencyService().UpdateHmrcExchageRates(); } + if (accountProcess == false) { accountProcess = true; new Logic.Export.AccountInvoice.QueueService().ImportAll(); } // if (stockProcess == false) { stockProcess = true; stock.ProcessFbaStockImportData(); } // ^^^^^^ best to process manually, case, fba inventory recepts, if a correction is made days later (ie -1) the already incorrect value diff --git a/src/bnhtrade.Core/Logic/Validate/AccountInvoiceLineItem.cs b/src/bnhtrade.Core/Logic/Validate/AccountInvoiceLineItem.cs new file mode 100644 index 0000000..5b80d2e --- /dev/null +++ b/src/bnhtrade.Core/Logic/Validate/AccountInvoiceLineItem.cs @@ -0,0 +1,23 @@ +using bnhtrade.Core.Model.Account; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Logic.Validate +{ + public class AccountInvoiceLineItem : Validate + { + public bool IsValid(Model.Account.InvoiceLineItem invoiceLineItem) + { + return base.IsValid(invoiceLineItem); + } + + public bool IsValid(List invoiceLineItems) + { + return base.IsValid(invoiceLineItems); + } + } +} diff --git a/src/bnhtrade.Core/Logic/Validate/AmazonSettlement.cs b/src/bnhtrade.Core/Logic/Validate/AmazonSettlement.cs index 46cdebc..ae2e9f6 100644 --- a/src/bnhtrade.Core/Logic/Validate/AmazonSettlement.cs +++ b/src/bnhtrade.Core/Logic/Validate/AmazonSettlement.cs @@ -1,4 +1,5 @@ -using System; +using bnhtrade.Core.Model.Amazon; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -56,7 +57,7 @@ namespace bnhtrade.Core.Logic.Validate { if (ValidateMarketPlaceName) { ValidationResultAdd("MarketPlaceName is a required value."); } } - else { IsValidMarketPlaceName(settlementList[i].MarketPlaceName); } + else { IsValidMarketPlaceName(settlementList[i].MarketPlace.GetMarketplaceUrl()); } if (!settlementList[i].SettlementIdIsSet) { ValidationResultAdd("SettlementId is a required value."); } else { IsValidSettlementId(settlementList[i].SettlementId); } @@ -315,23 +316,6 @@ namespace bnhtrade.Core.Logic.Validate return true; } - - - - - - - - - - - - - - - - - public bool IsValidTransactionType(string transactionType) { if (!stringCheck.MaxLength(transactionType, 50)) diff --git a/src/bnhtrade.Core/Logic/Validate/SalesInvoice.cs b/src/bnhtrade.Core/Logic/Validate/Invoice.cs similarity index 69% rename from src/bnhtrade.Core/Logic/Validate/SalesInvoice.cs rename to src/bnhtrade.Core/Logic/Validate/Invoice.cs index 74570c1..2dec103 100644 --- a/src/bnhtrade.Core/Logic/Validate/SalesInvoice.cs +++ b/src/bnhtrade.Core/Logic/Validate/Invoice.cs @@ -6,27 +6,22 @@ using System.Threading.Tasks; namespace bnhtrade.Core.Logic.Validate { - public class SalesInvoice : Logic.Validate.Validate + public class Invoice : Logic.Validate.Validate { - public SalesInvoice() + public Invoice() { } public bool IsValidExportInvoice(IEnumerable invoiceList) { - if (!IsValid(invoiceList)) + bool valid = IsValid(invoiceList); + if (valid == false) { return false; } foreach (var invoice in invoiceList) { - if (invoice.UnitAmountIsTaxExclusive == false) - { - ValidationResultAdd("Tax inclusive line unit amounts are not supported"); - return false; - } - foreach (var invoiceLine in invoice.InvoiceLineList) { if (invoiceLine.Quantity != 1) diff --git a/src/bnhtrade.Core/Logic/_boilerplate/UnitOfWorkPattern.cs b/src/bnhtrade.Core/Logic/_boilerplate/UnitOfWorkPattern.cs new file mode 100644 index 0000000..93fabc8 --- /dev/null +++ b/src/bnhtrade.Core/Logic/_boilerplate/UnitOfWorkPattern.cs @@ -0,0 +1,53 @@ +using bnhtrade.Core.Data.Database.UnitOfWork; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Logic._BoilerPlate +{ + internal class UnitOfWorkPattern + { + private readonly IUnitOfWork _providedUnitOfWork = null; + private readonly bool _ownsUnitOfWork = false; + + public UnitOfWorkPattern() + { + _ownsUnitOfWork = true; + } + + internal UnitOfWorkPattern(IUnitOfWork unitOfWork) + { + _providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork)); + _ownsUnitOfWork = false; + } + + public void LogicThatUsesService() + { + IUnitOfWork currentUow = null; + if (_ownsUnitOfWork) + { + currentUow = new UnitOfWork(); + } + else + { + currentUow = _providedUnitOfWork; + } + + using (currentUow != null && _ownsUnitOfWork ? currentUow : null) + { + + // + // Perform operations using currentUow, e.g. repository calls + // + + if (_ownsUnitOfWork) + { + currentUow.Commit(); + } + } + } + + } +} \ No newline at end of file diff --git a/src/bnhtrade.Core/Model/Account/CurrencyExchangeRate.cs b/src/bnhtrade.Core/Model/Account/CurrencyExchangeRate.cs index 5558f78..dfdb7d9 100644 --- a/src/bnhtrade.Core/Model/Account/CurrencyExchangeRate.cs +++ b/src/bnhtrade.Core/Model/Account/CurrencyExchangeRate.cs @@ -8,11 +8,20 @@ namespace bnhtrade.Core.Model.Account { public class CurrencyExchangeRate { - public CurrencyExchangeRate(CurrencyCode currencyCode, int exchangeRateSource, DateTime dateTimeStartUtc, DateTime dateTimeEndUtc) + public CurrencyExchangeRate(CurrencyCode currencyCode, int exchangeRateSource, decimal currencyUnitsPerGbp, DateTime dateTimeStartUtc, DateTime dateTimeEndUtc) { this.CurrencyCode = currencyCode; this.ExchangeRateSource = exchangeRateSource; + if (currencyUnitsPerGbp <= 0) + { + throw new ArgumentException("Currency units per GBP must be greater than zero."); + } + else + { + this.CurrencyUnitsPerGbp = currencyUnitsPerGbp; + } + if (dateTimeEndUtc > dateTimeStartUtc) { this.DateTimeStartUtc = dateTimeStartUtc; @@ -50,6 +59,10 @@ namespace bnhtrade.Core.Model.Account public DateTime DateTimeEndUtc { get; private set; } + public decimal ConvertToGbp(decimal amountToConvert) + { + return amountToConvert / CurrencyUnitsPerGbp; + } /// /// Checks whether a given datetime falls within the the exchange rate period diff --git a/src/bnhtrade.Core/Model/Account/Invoice.cs b/src/bnhtrade.Core/Model/Account/Invoice.cs index aae6d67..c6f0479 100644 --- a/src/bnhtrade.Core/Model/Account/Invoice.cs +++ b/src/bnhtrade.Core/Model/Account/Invoice.cs @@ -1,4 +1,5 @@ -using System; +using FikaAmazonAPI.AmazonSpApiSDK.Models.FulfillmentInbound; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -28,41 +29,33 @@ namespace bnhtrade.Core.Model.Account decimal? UnitAmount { get; set; } - Model.Account.Account AccountCode { get; set; } + Model.Account.Account Account { get; set; } Model.Account.TaxCodeInfo TaxCode { get; set; } - decimal? TaxAmountAdjust { get; set; } + decimal TaxAmountAdjust { get; set; } - decimal? TaxAmount { get; } + decimal? TaxAmountTotal { get; } decimal LineTotalAmount { get; } } - - public abstract class Invoice : InvoiceHeader, IInvoice + public abstract class Invoice : InvoiceHeader, IInvoice, IValidatableObject { - private bool unitAmountIsTaxExclusive = false; + protected Invoice(Model.Account.InvoiceType invoiceType, bool invoiceLineUnitAmountIsTaxExclusive) : base (invoiceType) + { + // force the UnitAmountIsTaxExclusive to be set at invoice creation to avoid issues, + InvoiceLineUnitAmountIsTaxExclusive = invoiceLineUnitAmountIsTaxExclusive; + } public decimal InvoiceNetAmount { get; } public decimal InvoiceTaxAmount { get; } - public bool UnitAmountIsTaxExclusive - { - get { return unitAmountIsTaxExclusive; } - set - { - unitAmountIsTaxExclusive = value; - if (InvoiceLineList != null) - { - for (int i = 0; i < InvoiceLineList.Count; i++) - { - InvoiceLineList[i].UnitAmountIsTaxExclusive = unitAmountIsTaxExclusive; - } - } - } - } + /// + /// Invoice line unit amount is tax exclusive + /// + public bool InvoiceLineUnitAmountIsTaxExclusive { get; set; } public List InvoiceLineList { get; set; } = new List(); @@ -75,16 +68,18 @@ namespace bnhtrade.Core.Model.Account } } - public class InvoiceLine : IInvoiceLine, IValidatableObject + public class InvoiceLine : IInvoiceLine, IValidatableObject { + private Invoice parentInvoice; private decimal? unitAmount; private Model.Account.TaxCodeInfo taxCode; - private decimal? taxAmountAdjust; + private decimal taxAmountAdjust; private decimal? quantity; - public InvoiceLine(bool unitAmountIsTaxExclusive) + public InvoiceLine(Invoice parentInvoice) { - UnitAmountIsTaxExclusive = unitAmountIsTaxExclusive; + if (parentInvoice == null) { throw new ArgumentNullException(nameof(parentInvoice), "Parent invoice cannot be null"); } + this.parentInvoice = parentInvoice; } public string ItemCode { get; set; } @@ -92,158 +87,333 @@ namespace bnhtrade.Core.Model.Account public string Description { get; set; } [Required(), Range(0, 9999999.99)] - public decimal? Quantity + public decimal? Quantity { get { return quantity; } - set + set { if (value == null) { quantity = null; } - else { quantity = decimal.Round((decimal)value, 4, MidpointRounding.AwayFromZero); } + else { quantity = decimal.Round(value.GetValueOrDefault(), 4, MidpointRounding.AwayFromZero); } } } [Required()] - public decimal? UnitAmount - { + public decimal? UnitAmount + { get { return unitAmount; } - set - { + set + { if (value == null) { unitAmount = null; } else { unitAmount = decimal.Round(value.GetValueOrDefault(), 4, MidpointRounding.AwayFromZero); } } } - public bool UnitAmountIsTaxExclusive { get; set; } - - [Required()] - public Model.Account.Account AccountCode - { - get; - set; + public bool UnitAmountIsTaxExclusive + { + get + { + return parentInvoice.InvoiceLineUnitAmountIsTaxExclusive; + } } [Required()] - public Model.Account.TaxCodeInfo TaxCode - { + public Model.Account.Account Account + { + get; + set; + } + + [Required()] + public Model.Account.TaxCodeInfo TaxCode + { get { return taxCode; } set { - if (value == null || TaxCode == null || value.TaxCode != taxCode.TaxCode) - { - TaxAmountAdjust = 0; + if (value == null || TaxCode == null || value.TaxCode != taxCode.TaxCode) + { + TaxAmountAdjust = 0; } taxCode = value; } } - [Required()] + /// + /// Tax amount calculated from line amount and the taxcode rate (before any adjustments) + /// public decimal? TaxAmount { - get + get { - return TaxAmountAdjust + GetCalculatedTax(); + decimal calculatedTax = 0; + if (CanCalculateLineTotalAmount) + { + if (UnitAmountIsTaxExclusive) + { + calculatedTax = ((decimal)Quantity * (decimal)UnitAmount) * (TaxCode.TaxRate / 100); + } + else + { + calculatedTax = ((decimal)Quantity * (decimal)UnitAmount) * (TaxCode.TaxRate / (100 + TaxCode.TaxRate)); + } + return RoundAmount(calculatedTax); + } + else + { + return null; + } } } - public decimal? TaxAmountAdjust + /// + /// Will adjust the calculated the tax total amount for the line + /// + public decimal TaxAmountAdjust { get { return taxAmountAdjust; } - set + set { taxAmountAdjust = RoundAmount(value); } + } + + /// + /// The calculated total tax amount for the line (including any adjustments) + /// + [Required()] + public decimal? TaxAmountTotal + { + get { - if (value == null) { taxAmountAdjust = null; } - else { taxAmountAdjust = decimal.Round((decimal)value, 2, MidpointRounding.AwayFromZero); } + if (CanCalculateLineTotalAmount) + { + return taxAmountAdjust + TaxAmount.GetValueOrDefault(); + } + else + { + return null; + } } } public decimal LineTotalAmount { - get + get { - if (CanCalculateLine()) + if (CanCalculateLineTotalAmount) { - return ((decimal)Quantity * (decimal)UnitAmount) + (decimal)TaxAmount; - } - else { return 0; } - } - } - - private bool CanCalculateLine() - { - if (Quantity == null || UnitAmount == null || TaxCode == null) { return false; } - else { return true; } - } - - private decimal GetCalculatedTax() - { - decimal calculatedTax = 0; - if (CanCalculateLine()) - { - if (UnitAmountIsTaxExclusive) - { - calculatedTax = ((decimal)Quantity * (decimal)UnitAmount) * (TaxCode.TaxRate / 100); + return ((decimal)Quantity * (decimal)UnitAmount) + (decimal)TaxAmountTotal; } else { - calculatedTax = ((decimal)Quantity * (decimal)UnitAmount) * (TaxCode.TaxRate / (100 + TaxCode.TaxRate)); + throw new InvalidOperationException("Cannot calculate line amount, ensure te Quantity, UnitAmount and TaxCode info is set."); } - return decimal.Round(calculatedTax, 2, MidpointRounding.AwayFromZero); - } - else - { - return 0; } } - public void SetTaxAmountAdjust(decimal lineTaxAmount) + public bool CanCalculateLineTotalAmount { - decimal adjustedAmount = lineTaxAmount - GetCalculatedTax(); - if (adjustedAmount == 0) { TaxAmountAdjust = null; } - else { TaxAmountAdjust = adjustedAmount; } + get + { + if (Quantity == null || UnitAmount == null || TaxCode == null) { return false; } + else { return true; } + } + } + + /// + /// This will calculate and update the tax adjustment property from a given tax amount total + /// + /// The required tax amount total + public void SetTaxAdjustmentByTaxTotal(decimal taxAmountTotal) + { + if (CanCalculateLineTotalAmount) + { + TaxAmountAdjust = taxAmountTotal - TaxAmount.Value; + } + } + + /// + /// Sets the unit amount based on the specified line total amount. + /// + /// This method calculates the unit amount for the current item using the + /// provided line total amount. The calculation considers whether the unit amount is tax-exclusive or + /// tax-inclusive, as well as the associated tax rate and quantity. If the quantity is not set, it defaults + /// to 1. + /// The total amount for the line item. Must be a whole number to two decimal places. + /// Thrown if is not a whole number to two decimal places. + /// Thrown if the TaxCode is null or the quantity is not set. + public void SetUnitAmountByLineTotal(decimal lineTotalAmount) + { + //checks + if (lineTotalAmount * 100m != Math.Truncate(lineTotalAmount * 100m)) + { + throw new ArgumentException("Line total amount must be a whole number to two decimal places", nameof(lineTotalAmount)); + } + if (TaxCode == null) + { + throw new InvalidOperationException("Cannot set unit amount, ensure the Quantity is set."); + } + else if (Quantity == null) + { + Quantity = 1; + } + + decimal taxRate = (TaxCode.TaxRate / 100); // convert to decimal rate + + if (UnitAmountIsTaxExclusive) + { + // set unit amount to net + UnitAmount = (lineTotalAmount - TaxAmountAdjust) / (Quantity.Value * taxRate + Quantity.Value); + } + else + { + // set unit amount to gross + UnitAmount = (lineTotalAmount - TaxAmountAdjust) / Quantity.Value; + } + + if (LineTotalAmount != lineTotalAmount) + { + throw new InvalidOperationException("SetUnitAmountByLineTotal() has a bug, check code"); + } + } + + /// + /// Rounds decimals to two decimal places inline with Xero specs + /// + /// The amount to round + /// Rounded amount to two decimal places + /// + private decimal RoundAmount(decimal amount) + { + return decimal.Round(amount, 2, MidpointRounding.AwayFromZero); + } + + public bool ParentInvoiceCheck(Invoice invoice) + { + if (invoice == null) { return false; } + return Object.ReferenceEquals(invoice, parentInvoice); } public IEnumerable Validate(ValidationContext validationContext) { - throw new NotImplementedException(); - var resultList = new List(); - if (TaxAmount > LineTotalAmount / 2) + if (CanCalculateLineTotalAmount == false) { - resultList.Add(new ValidationResult("Line tax amount is greater than net amount")); + resultList.Add(new ValidationResult("Cannot calulate line amount, data missing")); } + return resultList; } } + public bool ConvertToLineUnitAmountIsTaxExclusive() + { + if (InvoiceLineUnitAmountIsTaxExclusive) + { + return true; + } + + // test whether there's enough info to do the convertion + foreach (var line in this.InvoiceLineList) + { + if (line.CanCalculateLineTotalAmount == false) + { + // can't do it return false + return false; + } + } + + // do the convertion + InvoiceLineUnitAmountIsTaxExclusive = true; + foreach (var line in this.InvoiceLineList) + { + decimal lineTotal = line.LineTotalAmount; + decimal taxTotal = line.TaxAmountTotal.Value; + + // set unit amount to net and update + line.UnitAmount = ((line.Quantity.Value * line.UnitAmount.Value) - line.TaxAmountTotal.Value) / line.Quantity.Value; + line.SetTaxAdjustmentByTaxTotal(taxTotal); + + //fail safe + if (line.LineTotalAmount != lineTotal) + { + throw new Exception("ConvertToLineUnitAmountIsTaxExclusive() has a bug, check code"); + } + } + return true; + } + + public bool ConvertToLineUnitAmountIsTaxInclusive() + { + if (!InvoiceLineUnitAmountIsTaxExclusive) + { + return true; + } + + // test whether there's enough info to do the convertion + foreach (var line in this.InvoiceLineList) + { + if (line.CanCalculateLineTotalAmount == false) + { + // can't do it return false + return false; + } + } + + // do the convertion + InvoiceLineUnitAmountIsTaxExclusive = false; + foreach (var line in this.InvoiceLineList) + { + decimal lineTotal = line.LineTotalAmount; + decimal taxTotal = line.TaxAmountTotal.Value; + + // set unit amount to gross and update + line.UnitAmount = (line.UnitAmount / line.Quantity) + (line.TaxAmountTotal / line.Quantity); + line.SetTaxAdjustmentByTaxTotal(taxTotal); + + //fail safe + if (line.LineTotalAmount != lineTotal) + { + throw new Exception("ConvertToLineUnitAmountIsTaxExclusive() has a bug, check code"); + } + } + return true; + } + + public decimal GetCalculatedInvoiceTotal() + { + decimal lineTotal = 0; + foreach (var line in InvoiceLineList) + { + lineTotal += line.LineTotalAmount; + } + return lineTotal; + } + public new IEnumerable Validate(ValidationContext validationContext) { - var subResult = base.Validate(validationContext); var resultList = new List(); - resultList.AddRange(subResult); + // add results from invoice header + resultList.AddRange(base.Validate(validationContext)); - // loop though lines and check sum totals + // loop though invoice lines and add results from there if (!InvoiceLineListIsSet) { - resultList.Add(new ValidationResult("No lines set on Invoice")); + resultList.Add(new ValidationResult("No lines set on Invoice (InvoiceNumber:" + InvoiceNumber + ")")); } - else + foreach (var line in InvoiceLineList) { - decimal lineTotal = 0; - foreach (var line in InvoiceLineList) + if (line.ParentInvoiceCheck(this) == false) { - lineTotal += line.LineTotalAmount; - - if (UnitAmountIsTaxExclusive != line.UnitAmountIsTaxExclusive) - { - resultList.Add(new ValidationResult("Invalid UnitAmountIsTaxExclusive")); - } + resultList.Add(new ValidationResult("Incorrect parent invoice set on invoice line")); } + resultList.AddRange(line.Validate(validationContext)); + } - // check totals - if (InvoiceTotalAmount != lineTotal) - { resultList.Add(new ValidationResult("Invoice line total does not equal invoice total")); } + // check invoice total against calculated total from invoice lines + decimal calculatedInvoiceTotal = GetCalculatedInvoiceTotal(); // don't know why this needs to be here, but it won't pickup a failed validation otherwise + if (InvoiceTotalAmount != calculatedInvoiceTotal) + { + resultList.Add(new ValidationResult("Invoice line total does not equal invoice total (InvoiceNumber:" + InvoiceNumber +")")); } return resultList; diff --git a/src/bnhtrade.Core/Model/Account/InvoiceHeader.cs b/src/bnhtrade.Core/Model/Account/InvoiceHeader.cs index e6e92b8..215fe9f 100644 --- a/src/bnhtrade.Core/Model/Account/InvoiceHeader.cs +++ b/src/bnhtrade.Core/Model/Account/InvoiceHeader.cs @@ -9,11 +9,13 @@ namespace bnhtrade.Core.Model.Account { public interface IInvoiceHeader { + Model.Account.InvoiceType InvoiceType { get; } + string ContactName { get; set; } decimal? InvoiceTotalAmount { get; set; } - string InvoiceCurrencyCode { get; set; } + Model.Account.CurrencyCode InvoiceCurrencyCode { get; set; } DateTime? InvoiceDate { get; set; } @@ -30,23 +32,27 @@ namespace bnhtrade.Core.Model.Account { private decimal? invoiceAmount; - public InvoiceHeader() + public InvoiceHeader(Model.Account.InvoiceType invoiceType) { + this.InvoiceType = invoiceType; IsCreditNote = false; } + [Required()] + public Model.Account.InvoiceType InvoiceType { get; private set; } + [Required()] public string ContactName { get; set; } + [Required()] + public Model.Account.CurrencyCode InvoiceCurrencyCode { get; set; } + [Required()] public DateTime? InvoiceDate { get; set; } [Required()] public DateTime? InvoiceDueDate { get; set; } - [Required(), MinLength(3), MaxLength(3)] - public string InvoiceCurrencyCode { get; set; } - [Required()] public string InvoiceNumber { get; set; } @@ -55,7 +61,7 @@ namespace bnhtrade.Core.Model.Account [Required()] public decimal? InvoiceTotalAmount { - get { return (decimal)invoiceAmount; } + get { return invoiceAmount.GetValueOrDefault(); } set { if (value == null) { invoiceAmount = null; } diff --git a/src/bnhtrade.Core/Model/Account/InvoiceLineItem.cs b/src/bnhtrade.Core/Model/Account/InvoiceLineItem.cs index 955ecb6..506f239 100644 --- a/src/bnhtrade.Core/Model/Account/InvoiceLineItem.cs +++ b/src/bnhtrade.Core/Model/Account/InvoiceLineItem.cs @@ -43,14 +43,12 @@ namespace bnhtrade.Core.Model.Account set; } - [Required()] public Model.Account.Account DefaultAccountCode { get; set; } - [Required()] public Model.Account.TaxCodeInfo DefaultTaxCode { get; @@ -61,7 +59,27 @@ namespace bnhtrade.Core.Model.Account { var resultList = new List(); - resultList.AddRange(DefaultTaxCode.Validate(validationContext)); + if (InvoiceLineEntryEnabled) + { + if (DefaultAccountCode == null) + { + resultList.Add(new ValidationResult("DefaultAccountCode is required when InvoiceLineEntryEnabled is true.")); + } + if (DefaultTaxCode == null) + { + resultList.Add(new ValidationResult("DefaultTaxCode is required when InvoiceLineEntryEnabled is true.")); + } + } + + if (DefaultAccountCode != null) + { + resultList.AddRange(DefaultTaxCode.Validate(validationContext)); + } + + if (DefaultTaxCode != null) + { + resultList.AddRange(DefaultTaxCode.Validate(validationContext)); + } return resultList; } diff --git a/src/bnhtrade.Core/Model/Account/InvoiceType.cs b/src/bnhtrade.Core/Model/Account/InvoiceType.cs new file mode 100644 index 0000000..5a35497 --- /dev/null +++ b/src/bnhtrade.Core/Model/Account/InvoiceType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Model.Account +{ + public enum InvoiceType + { + Purchase = 1, + Sale = 2 + } +} diff --git a/src/bnhtrade.Core/Model/Account/PurchaseInvoice.cs b/src/bnhtrade.Core/Model/Account/PurchaseInvoice.cs index 6fe3a0f..6a1b2ac 100644 --- a/src/bnhtrade.Core/Model/Account/PurchaseInvoice.cs +++ b/src/bnhtrade.Core/Model/Account/PurchaseInvoice.cs @@ -8,6 +8,11 @@ namespace bnhtrade.Core.Model.Account { public class PurchaseInvoice { + // the purchase/sales invoice models are fragmented. The sales invoice inherits from an abstract invoice class. However, this purchase invoice + // model doesn't, it was setup in the early development to mirror an Amazon report. + // At some point I would like to correct this, i.e. have the purchase invoice model inherit from the abstract invoice class. + // this will take some unpicking + public int PurchaseID { get; set; } public int PurchaseNumber { get; set; } diff --git a/src/bnhtrade.Core/Model/Account/SalesInvoice.cs b/src/bnhtrade.Core/Model/Account/SalesInvoice.cs index 02d522c..c143628 100644 --- a/src/bnhtrade.Core/Model/Account/SalesInvoice.cs +++ b/src/bnhtrade.Core/Model/Account/SalesInvoice.cs @@ -8,6 +8,12 @@ namespace bnhtrade.Core.Model.Account { public class SalesInvoice : Invoice { - + public SalesInvoice(bool invoiceLineUnitAmountIsTaxExclusive) : base(Model.Account.InvoiceType.Sale, invoiceLineUnitAmountIsTaxExclusive) + { + // the purchase/sales invoice models are fragmented. This sales invoice inherits from an abstract invoice. However, the purchase invoice + // model doesn't, it was setup in the early development to mirror an Amazon report. + // At some point I would like to correct this, i.e. have the purchase invoice model inherit from the abstract invoice class. + // this will take some unpicking + } } } \ No newline at end of file diff --git a/src/bnhtrade.Core/Model/Amazon/MarketPlaceEnum.cs b/src/bnhtrade.Core/Model/Amazon/MarketPlaceEnum.cs new file mode 100644 index 0000000..c2101b3 --- /dev/null +++ b/src/bnhtrade.Core/Model/Amazon/MarketPlaceEnum.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Model.Amazon +{ + public enum MarketPlaceEnum + { + NONE = 0, + AmazonUK = 1, + AmazonDE = 2, + AmazonES = 3, + AmazonFR = 4, + AmazonIT = 5, + AmazonBE = 6, + AmazonIE = 7, + AmazonNL = 8, + AmazonPL = 9, + AmazonSE = 10 + } + + public static class MarketPlaceEnumExtensions + { + public static string GetMarketplaceUrl(this MarketPlaceEnum marketPlace) + { + return marketPlace switch + { + MarketPlaceEnum.AmazonUK => "Amazon.co.uk", + MarketPlaceEnum.AmazonDE => "Amazon.de", + MarketPlaceEnum.AmazonES => "Amazon.es", + MarketPlaceEnum.AmazonFR => "Amazon.fr", + MarketPlaceEnum.AmazonIT => "Amazon.it", + MarketPlaceEnum.AmazonBE => "Amazon.com.be", + MarketPlaceEnum.AmazonIE => "Amazon.ie", + MarketPlaceEnum.AmazonNL => "Amazon.nl", + MarketPlaceEnum.AmazonPL => "Amazon.pl", + MarketPlaceEnum.AmazonSE => "Amazon.se", + _ => throw new ArgumentOutOfRangeException(nameof(marketPlace), marketPlace, null) + }; + } + + public static MarketPlaceEnum FromMarketplaceUrl(string url) + { + if (string.IsNullOrWhiteSpace(url)) + throw new ArgumentNullException(nameof(url)); + + return url.Trim().ToLowerInvariant() switch + { + "amazon.co.uk" => MarketPlaceEnum.AmazonUK, + "amazon.de" => MarketPlaceEnum.AmazonDE, + "amazon.es" => MarketPlaceEnum.AmazonES, + "amazon.fr" => MarketPlaceEnum.AmazonFR, + "amazon.it" => MarketPlaceEnum.AmazonIT, + "amazon.com.be" => MarketPlaceEnum.AmazonBE, + "amazon.ie" => MarketPlaceEnum.AmazonIE, + "amazon.nl" => MarketPlaceEnum.AmazonNL, + "amazon.pl" => MarketPlaceEnum.AmazonPL, + "amazon.se" => MarketPlaceEnum.AmazonSE, + _ => throw new ArgumentException($"Unknown marketplace URL: {url}", nameof(url)) + }; + } + } +} diff --git a/src/bnhtrade.Core/Model/Export/AccountInvoiceType.cs b/src/bnhtrade.Core/Model/Export/AccountInvoiceType.cs new file mode 100644 index 0000000..7920e13 --- /dev/null +++ b/src/bnhtrade.Core/Model/Export/AccountInvoiceType.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Model.Export +{ + + /// + /// Sale or purchase invoice, id matches the database id + /// + public enum AccountInvoiceType + { + Purchase = 1, + Sale = 2 + } +} diff --git a/src/bnhtrade.Core/Model/Import/AmazonSettlementHeader.cs b/src/bnhtrade.Core/Model/Import/AmazonSettlementHeader.cs index bfb9ce5..a1f014b 100644 --- a/src/bnhtrade.Core/Model/Import/AmazonSettlementHeader.cs +++ b/src/bnhtrade.Core/Model/Import/AmazonSettlementHeader.cs @@ -1,4 +1,5 @@ -using System; +using bnhtrade.Core.Model.Amazon; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -9,7 +10,6 @@ namespace bnhtrade.Core.Model.Import { public class AmazonSettlementHeader { - private string marketplaceName = null; private string settlementId = null; private string currencyCode = null; private DateTime? startDate = null; @@ -26,7 +26,7 @@ namespace bnhtrade.Core.Model.Import public AmazonSettlementHeader(AmazonSettlementHeader toCopy) { - this.marketplaceName = toCopy.MarketPlaceName; + this.MarketPlace = toCopy.MarketPlace; this.settlementId = toCopy.SettlementId; this.currencyCode = toCopy.CurrencyCode; if (toCopy.StartDateIsSet) { this.startDate = toCopy.StartDate; } @@ -37,15 +37,21 @@ namespace bnhtrade.Core.Model.Import if (toCopy.SpapiReportIdIsSet) { this.spapiReportId = toCopy.SpapiReportId; } } - public string MarketPlaceName - { - get { return marketplaceName; } - set { this.marketplaceName = value; } - } + public Model.Amazon.MarketPlaceEnum MarketPlace { get; set; } public bool MarketPlaceNameIsSet { - get { return marketplaceName != null; } + get + { + if (MarketPlace == 0) + { + return false; + } + else + { + return true; + } + } } public string SettlementId diff --git a/src/bnhtrade.Core/Test/Account/Account.cs b/src/bnhtrade.Core/Test/Account/Account.cs index 04c9ded..9579e1f 100644 --- a/src/bnhtrade.Core/Test/Account/Account.cs +++ b/src/bnhtrade.Core/Test/Account/Account.cs @@ -19,15 +19,13 @@ namespace bnhtrade.Core.Test.Account public void UpdateHmrcExchageRates() { - var logic = new bnhtrade.Core.Logic.Account.Currency(); + var logic = new bnhtrade.Core.Logic.Account.CurrencyService(); logic.UpdateHmrcExchageRates(); } public void PurchaseInvoice() { - var read = new Data.Database.Account.ReadPurchaseInvoice(); - read.PurchaseInvoiceIdList = new List { 14718, 100, 101, 102, 300, 400, 1200, 2734, 6339, 9999 }; // 10 in total - var result = read.Read(); + var result = new bnhtrade.Core.Logic.Account.PurchaseInvoice().GetPurchaseInvoice( new List { 14718, 100, 101, 102, 300, 400, 1200, 2734, 6339, 9999 }); // 10 in total } public void PurchaseInvoiceLine() diff --git a/src/bnhtrade.Core/Test/AutoExec.cs b/src/bnhtrade.Core/Test/AutoExec.cs index 1959d74..76c0de9 100644 --- a/src/bnhtrade.Core/Test/AutoExec.cs +++ b/src/bnhtrade.Core/Test/AutoExec.cs @@ -15,8 +15,8 @@ namespace bnhtrade.Core.Test } private void AmazonSettlement() { - var instance = new Core.Logic.Export.AmazonSettlement(); - instance.ToInvoice(); + var instance = new Core.Logic.Export.AccountInvoice.AmazonSettlement(); + instance.GenerateInvoicesForExportQueue(true); } } } diff --git a/src/bnhtrade.Core/Test/Export/Export.cs b/src/bnhtrade.Core/Test/Export/Export.cs index 21db05b..bdf2358 100644 --- a/src/bnhtrade.Core/Test/Export/Export.cs +++ b/src/bnhtrade.Core/Test/Export/Export.cs @@ -9,12 +9,9 @@ namespace bnhtrade.Core.Test.Export { public class Export { - private string sqlConnectionString; - public Export(string sqlConnectionString) + public Export() { - this.sqlConnectionString = sqlConnectionString; - - UpdateStatusInfo(); + DeleteExportInvoice(); } private void SubmitAmazonInventoryLoader() @@ -39,5 +36,33 @@ namespace bnhtrade.Core.Test.Export { //new Core.Logic.Export.AmazonSubmitFileStatus(sqlConnectionString).UpdateStatusInfo(); } + + private void AccountInvoiceToXero() + { + var unitOfWork = new bnhtrade.Core.Data.Database.UnitOfWork.UnitOfWork(); + var list = unitOfWork.ExportInvoiceRepository.GetSalesInvoiceById(new List { 550, 555 }); + } + + private void ExportSalesInvoiceQueue() + { + var invoice = new Model.Account.SalesInvoice(true); + invoice.InvoiceLineList = new List(); + + string path = @"%USERPROFILE%\Downloads\TEST.CSV"; + var queueService = new bnhtrade.Core.Logic.Export.AccountInvoice.QueueService(); + queueService.ImportAll(); + queueService.ExportSalesInvoice(path, 1234); + var return1 = queueService.ExportSalesInvoiceIsComplete(); + } + private void DeleteExportInvoice() + { + //var uow = new bnhtrade.Core.Data.Database.UnitOfWork.UnitOfWork(); + using (var uow = new bnhtrade.Core.Data.Database.UnitOfWork.UnitOfWork()) + { + int dfkjl = uow.ExportInvoiceRepository.DeleteInvoice(new List { 576, 577, 584 }); + int lskdjflsd = uow.ImportAmazonRepository.SetAmazonSettlementIsProcessed(new List { "24684639382f", "14720584692f", "14386695522f" }, false); + uow.Commit(); + } + } } } diff --git a/src/bnhtrade.Core/Test/Import/Report.cs b/src/bnhtrade.Core/Test/Import/Report.cs index 0e316dd..5894b7f 100644 --- a/src/bnhtrade.Core/Test/Import/Report.cs +++ b/src/bnhtrade.Core/Test/Import/Report.cs @@ -16,8 +16,8 @@ namespace bnhtrade.Core.Test.Import public void ReadSettlementFromDatabase() { - var instance = new Data.Database.Import.AmazonSettlementRead(); - var answer = instance.BySettlementId("11796400482"); + //var instance = new Data.Database.Import.AmazonSettlementRead(); + //var answer = instance.BySettlementId("11796400482"); } public void GetFromSPAPI() @@ -35,7 +35,7 @@ namespace bnhtrade.Core.Test.Import public void GetSettlementReport() { var setList = new List { "15734813312", "14473886262", "13984682252" }; - var result = new Data.Database.Import.AmazonSettlementRead().BySettlementIdList(setList); + //var result = new Data.Database.Import.AmazonSettlementRead().BySettlementId(setList); } public void FBAInventory() diff --git a/src/bnhtrade.Core/Test/Logic/Export.cs b/src/bnhtrade.Core/Test/Logic/Export.cs index 13ace2b..1875b12 100644 --- a/src/bnhtrade.Core/Test/Logic/Export.cs +++ b/src/bnhtrade.Core/Test/Logic/Export.cs @@ -11,13 +11,24 @@ namespace bnhtrade.Core.Test.Logic public Logic() { // method you want to start here - UpdateXeroWithAmzonSettlementData(); + UpdateExportInvoiceQueue(); } public void UpdateXeroWithAmzonSettlementData() { - var instance = new bnhtrade.Core.Logic.Export.AmazonSettlement(); - instance.ToInvoice(); + var instance = new bnhtrade.Core.Logic.Export.AccountInvoice.AmazonSettlement(); + instance.GenerateInvoicesForExportQueue(true); + } + public void ReadAmazonSettlement() + { + using (var uow = new Data.Database.UnitOfWork.UnitOfWork()) + { + var list = uow.ImportAmazonRepository.ReadAmazonSettlements(marketPlaceNameList: new List { "Amazon.fr" }, returnTop: 3); + } + } + public void UpdateExportInvoiceQueue() + { + new Core.Logic.Export.AccountInvoice.QueueService().ImportAll(); } } } diff --git a/src/bnhtrade.Core/Test/SQLLoop.cs b/src/bnhtrade.Core/Test/SQLLoop.cs index 74f68db..b2b2eba 100644 --- a/src/bnhtrade.Core/Test/SQLLoop.cs +++ b/src/bnhtrade.Core/Test/SQLLoop.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; diff --git a/src/bnhtrade.Core/bnhtrade.Core.csproj b/src/bnhtrade.Core/bnhtrade.Core.csproj index 924196e..ee5c558 100644 --- a/src/bnhtrade.Core/bnhtrade.Core.csproj +++ b/src/bnhtrade.Core/bnhtrade.Core.csproj @@ -27,13 +27,14 @@ + - + diff --git a/src/bnhtrade.ScheduledTasks/Program.cs b/src/bnhtrade.ScheduledTasks/Program.cs index f797450..890d547 100644 --- a/src/bnhtrade.ScheduledTasks/Program.cs +++ b/src/bnhtrade.ScheduledTasks/Program.cs @@ -1,7 +1,8 @@ -using System; -using System.Threading; +using bnhtrade.Core; +using System; using System.Configuration; -using bnhtrade.Core; +using System.Threading; +using System.Transactions; namespace bnhtradeScheduledTasks { @@ -13,6 +14,11 @@ namespace bnhtradeScheduledTasks static void Main(string[] args) { + if (OperatingSystem.IsWindows()) + { + TransactionManager.ImplicitDistributedTransactions = false; + } + if (args.Length == 0) { string consoleHeader = "Welcome to THE application!\n"; @@ -58,6 +64,7 @@ namespace bnhtradeScheduledTasks Console.WriteLine("<7> Update Fba Return Data"); Console.WriteLine("<8> (depreciated) Update Fba Adustment Data"); Console.WriteLine("<9> Update Fba Removal Order Data"); + Console.WriteLine("<10> Add all invoices to export queue"); Console.WriteLine(); Console.WriteLine("<0> Back"); Console.WriteLine(""); @@ -93,7 +100,6 @@ namespace bnhtradeScheduledTasks { Console.Clear(); new bnhtrade.Core.Logic.Import.AmazonSettlement().SyncDatabase(); - new bnhtrade.Core.Logic.Export.AmazonSettlement().ToInvoice(); Console.WriteLine("Complete, press any key to continue..."); Console.ReadKey(); } @@ -132,6 +138,14 @@ namespace bnhtradeScheduledTasks Console.WriteLine("Complete, press any key to continue..."); Console.ReadKey(); } + else if (input == "10") + { + Console.Clear(); + new bnhtrade.Core.Logic.Export.AccountInvoice.QueueService().ImportAll(); + Console.WriteLine("Complete, press any key to continue..."); + Console.ReadKey(); + } + } while (true); } else if (input == "2") @@ -252,6 +266,7 @@ namespace bnhtradeScheduledTasks Console.WriteLine("Main Menu > Account"); Console.WriteLine(); Console.WriteLine("<1> Update HMRC Exchange Rates"); + Console.WriteLine("<2> Generate invoices for export queue"); Console.WriteLine(); Console.WriteLine("<0> Back"); Console.WriteLine(""); @@ -265,10 +280,19 @@ namespace bnhtradeScheduledTasks else if (input == "1") { Console.Clear(); - new bnhtrade.Core.Logic.Account.Currency().UpdateHmrcExchageRates(); + new bnhtrade.Core.Logic.Account.CurrencyService().UpdateHmrcExchageRates(); Console.WriteLine("Complete, press any key to continue..."); Console.ReadKey(); } + else if (input == "2") + { + Console.Clear(); + var queueService = new bnhtrade.Core.Logic.Export.AccountInvoice.QueueService(); + queueService.ImportAll(); + Console.WriteLine("Complete, press any key to continue..."); + Console.ReadKey(); + } + } while (true); } else if (input == "8") @@ -329,7 +353,7 @@ namespace bnhtradeScheduledTasks { Console.Clear(); - //new bnhtrade.Core.Test.Export.Export(dbCredentials.ConnectionString); + new bnhtrade.Core.Test.Export.Export(); Console.WriteLine("Done"); Console.WriteLine("Complete, press any key to continue..."); diff --git a/src/bnhtrade.gui/Helper/InputDialog.cs b/src/bnhtrade.gui/Helper/InputDialog.cs new file mode 100644 index 0000000..5e55132 --- /dev/null +++ b/src/bnhtrade.gui/Helper/InputDialog.cs @@ -0,0 +1,46 @@ +using System; +using System.Windows.Forms; + +public static class InputDialog +{ + public static bool ShowIntDialog(string prompt, string title, out int value) + { + value = 0; + using (Form form = new Form()) + using (Label label = new Label()) + using (TextBox textBox = new TextBox()) + using (Button buttonOk = new Button()) + using (Button buttonCancel = new Button()) + { + form.Text = title; + label.Text = prompt; + textBox.Width = 100; + + buttonOk.Text = "OK"; + buttonCancel.Text = "Cancel"; + buttonOk.DialogResult = DialogResult.OK; + buttonCancel.DialogResult = DialogResult.Cancel; + + label.SetBounds(9, 10, 372, 13); + textBox.SetBounds(12, 30, 372, 20); + buttonOk.SetBounds(228, 60, 75, 23); + buttonCancel.SetBounds(309, 60, 75, 23); + + label.AutoSize = true; + form.ClientSize = new System.Drawing.Size(396, 95); + form.Controls.AddRange(new Control[] { label, textBox, buttonOk, buttonCancel }); + form.AcceptButton = buttonOk; + form.CancelButton = buttonCancel; + form.FormBorderStyle = FormBorderStyle.FixedDialog; + form.StartPosition = FormStartPosition.CenterParent; + form.MinimizeBox = false; + form.MaximizeBox = false; + + if (form.ShowDialog() == DialogResult.OK) + { + return int.TryParse(textBox.Text, out value); + } + return false; + } + } +} \ No newline at end of file diff --git a/src/bnhtrade.gui/Home.Designer.cs b/src/bnhtrade.gui/Home.Designer.cs index 43c956d..5e6edfe 100644 --- a/src/bnhtrade.gui/Home.Designer.cs +++ b/src/bnhtrade.gui/Home.Designer.cs @@ -31,6 +31,9 @@ components = new System.ComponentModel.Container(); tabControl1 = new TabControl(); tabPage1 = new TabPage(); + btnNewInvoiceExport = new Button(); + label2 = new Label(); + txtExportInvoiceCount = new TextBox(); tabPage2 = new TabPage(); Receiving = new TabPage(); labelDataGridCount = new Label(); @@ -50,12 +53,17 @@ textboxOrderSearch = new TextBox(); tabAccounts = new TabPage(); btnExchangeRate = new Button(); + tabTest = new TabPage(); + label3 = new Label(); + btnExport = new Button(); tabControl1.SuspendLayout(); + tabPage1.SuspendLayout(); Receiving.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)dataGridView1).BeginInit(); ((System.ComponentModel.ISupportInitialize)bsReceivingLines).BeginInit(); ((System.ComponentModel.ISupportInitialize)purchaseLineStatusBindingSource).BeginInit(); tabAccounts.SuspendLayout(); + tabTest.SuspendLayout(); SuspendLayout(); // // tabControl1 @@ -65,6 +73,7 @@ tabControl1.Controls.Add(tabPage2); tabControl1.Controls.Add(Receiving); tabControl1.Controls.Add(tabAccounts); + tabControl1.Controls.Add(tabTest); tabControl1.Location = new Point(14, 16); tabControl1.Margin = new Padding(3, 4, 3, 4); tabControl1.Name = "tabControl1"; @@ -75,6 +84,9 @@ // // tabPage1 // + tabPage1.Controls.Add(btnNewInvoiceExport); + tabPage1.Controls.Add(label2); + tabPage1.Controls.Add(txtExportInvoiceCount); tabPage1.Location = new Point(4, 29); tabPage1.Margin = new Padding(3, 4, 3, 4); tabPage1.Name = "tabPage1"; @@ -83,6 +95,34 @@ tabPage1.TabIndex = 0; tabPage1.Text = "Home"; tabPage1.UseVisualStyleBackColor = true; + tabPage1.Click += tabPage1_Click; + // + // btnNewInvoiceExport + // + btnNewInvoiceExport.Location = new Point(29, 46); + btnNewInvoiceExport.Name = "btnNewInvoiceExport"; + btnNewInvoiceExport.Size = new Size(74, 27); + btnNewInvoiceExport.TabIndex = 2; + btnNewInvoiceExport.Text = "Export"; + btnNewInvoiceExport.UseVisualStyleBackColor = true; + btnNewInvoiceExport.Click += btnNewInvoiceExport_Click; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new Point(228, 49); + label2.Name = "label2"; + label2.Size = new Size(236, 20); + label2.TabIndex = 1; + label2.Text = "Invoice(s) ready for export to Xero"; + // + // txtExportInvoiceCount + // + txtExportInvoiceCount.Location = new Point(124, 46); + txtExportInvoiceCount.Name = "txtExportInvoiceCount"; + txtExportInvoiceCount.Size = new Size(87, 27); + txtExportInvoiceCount.TabIndex = 0; + txtExportInvoiceCount.TextAlign = HorizontalAlignment.Center; // // tabPage2 // @@ -273,6 +313,37 @@ btnExchangeRate.UseVisualStyleBackColor = true; btnExchangeRate.Click += btnExchangeRate_Click; // + // tabTest + // + tabTest.Controls.Add(label3); + tabTest.Controls.Add(btnExport); + tabTest.Location = new Point(4, 29); + tabTest.Name = "tabTest"; + tabTest.Padding = new Padding(3); + tabTest.Size = new Size(1160, 678); + tabTest.TabIndex = 4; + tabTest.Text = "Testing"; + tabTest.UseVisualStyleBackColor = true; + // + // label3 + // + label3.AutoSize = true; + label3.Location = new Point(197, 56); + label3.Name = "label3"; + label3.Size = new Size(52, 20); + label3.TabIndex = 1; + label3.Text = "Export"; + // + // btnExport + // + btnExport.Location = new Point(49, 52); + btnExport.Name = "btnExport"; + btnExport.Size = new Size(130, 29); + btnExport.TabIndex = 0; + btnExport.Text = "Export"; + btnExport.UseVisualStyleBackColor = true; + btnExport.Click += btnExport_Click; + // // Home // AutoScaleDimensions = new SizeF(8F, 20F); @@ -284,12 +355,16 @@ Text = "Form1"; Load += Form1_Load; tabControl1.ResumeLayout(false); + tabPage1.ResumeLayout(false); + tabPage1.PerformLayout(); Receiving.ResumeLayout(false); Receiving.PerformLayout(); ((System.ComponentModel.ISupportInitialize)dataGridView1).EndInit(); ((System.ComponentModel.ISupportInitialize)bsReceivingLines).EndInit(); ((System.ComponentModel.ISupportInitialize)purchaseLineStatusBindingSource).EndInit(); tabAccounts.ResumeLayout(false); + tabTest.ResumeLayout(false); + tabTest.PerformLayout(); ResumeLayout(false); } @@ -316,5 +391,11 @@ private Label labelDataGridCount; private TabPage tabAccounts; private Button btnExchangeRate; + private Label label2; + private TextBox txtExportInvoiceCount; + private Button btnNewInvoiceExport; + private TabPage tabTest; + private Label label3; + private Button btnExport; } } diff --git a/src/bnhtrade.gui/Home.cs b/src/bnhtrade.gui/Home.cs index b4ce227..0fb7d93 100644 --- a/src/bnhtrade.gui/Home.cs +++ b/src/bnhtrade.gui/Home.cs @@ -1,4 +1,6 @@ using bnhtrade.gui.Forms.Account; +using System.Diagnostics; +using System.Threading.Tasks.Dataflow; namespace bnhtrade.gui { @@ -6,15 +8,30 @@ namespace bnhtrade.gui { bool initTabReceiving = false; + Core.Logic.Log.LogEvent log = new Core.Logic.Log.LogEvent(); public Home() { InitializeComponent(); } + private void UpdateInvoiceExportBox() + { + int count = new bnhtrade.Core.Logic.Export.AccountInvoice.QueueService().Count(Core.Model.Account.InvoiceType.Sale); + if (count > 0) + { + btnNewInvoiceExport.Enabled = true; + } + else + { + btnNewInvoiceExport.Enabled = false; + } + this.txtExportInvoiceCount.Text = count.ToString(); + } + private void Form1_Load(object sender, EventArgs e) { - + UpdateInvoiceExportBox(); } private void tabPage3_Click(object sender, EventArgs e) @@ -103,10 +120,8 @@ namespace bnhtrade.gui var row = dataGridView1.CurrentCell.RowIndex; var lineSummary = (bnhtrade.Core.Model.Account.PurchaseInvoiceLineSummary)bsReceivingLines[row]; int purchaseId = lineSummary.PurchaseId; - var dbRead = new Core.Logic.Account.PurchaseInvoice(); - dbRead.PurchaseInvoiceIdList = new List { purchaseId }; - var dbresult = dbRead.Read(); - var purchaseInvoice = dbresult[purchaseId]; + var purchaseInvoiceList = new Core.Logic.Account.PurchaseInvoice().GetPurchaseInvoice(new List { purchaseId }); + var purchaseInvoice = purchaseInvoiceList[purchaseId]; var form = new PurchaseInvoice(purchaseInvoice); form.Show(); } @@ -115,7 +130,105 @@ namespace bnhtrade.gui { var myForm = new FormConsole(); myForm.Show(); - new bnhtrade.Core.Logic.Account.Currency().UpdateHmrcExchageRates(); + new bnhtrade.Core.Logic.Account.CurrencyService().UpdateHmrcExchageRates(); + } + + private void btnNewInvoiceExport_Click(object sender, EventArgs e) + { + // check if there are invoices to export + UpdateInvoiceExportBox(); + int invoices = int.Parse(txtExportInvoiceCount.Text); + if (invoices == 0) + { + MessageBox.Show("No invoices to export"); + return; + } + + // get next invoice number from user + // open Xero invoice search page in default browser + Process.Start(new ProcessStartInfo + { + FileName = "https://go.xero.com/AccountsReceivable/Search.aspx?graphSearch=False&dateWithin=any&pageSize=25&orderBy=InvoiceNumber&direction=DESC&unsentOnly=False", + UseShellExecute = true + }); + // prompt user for invoice number + int nextInvoiceNumber; + if (InputDialog.ShowIntDialog("Enter the next sales invoice number:", "Input Required", out nextInvoiceNumber) != true) + { + MessageBox.Show("Input cancelled or invalid. Stopping import."); + return; + } + + // open file picker dialog to select export file + string savePath = null; + using (SaveFileDialog saveFileDialog = new SaveFileDialog()) + { + saveFileDialog.Filter = "CSV files (*.csv)|*.csv|All files (*.*)|*.*"; + saveFileDialog.Title = "Export Invoices to CSV"; + saveFileDialog.InitialDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads"); + saveFileDialog.FileName = "Xero_SalesInvoices_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".csv"; + if (saveFileDialog.ShowDialog() == DialogResult.OK) + { + savePath = saveFileDialog.FileName; + } + else + { + return; + } + } + + + // create export invoices and export to CSV + var queueService = new bnhtrade.Core.Logic.Export.AccountInvoice.QueueService(); + queueService.ImportAll(); + queueService.ExportSalesInvoice(savePath, nextInvoiceNumber); + + // open import page + Process.Start(new ProcessStartInfo + { + FileName = "https://go.xero.com/Import/Import.aspx?type=IMPORTTYPE/ARINVOICES", + UseShellExecute = true + }); + + // did the import go okay + var result = MessageBox.Show( + "Import invoices to Xero ensuring UnitAmount field option is set to 'Tax Exclusive' on the import page." + + Environment.NewLine + Environment.NewLine + + "Once the import has completed suscessfully click 'Okay' to mark the invoice(s) as complete/imported.", + "Import Confirmation", + MessageBoxButtons.OKCancel, + MessageBoxIcon.Question + ); + + if (result == DialogResult.OK) + { + var completeCount = queueService.ExportSalesInvoiceIsComplete(); + if (completeCount != invoices) + { + MessageBox.Show("Error marking invoices as complete. Please check the logs for more details.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + log.LogError("ExportSalesInvoiceIsComplete returned " + completeCount + " but expected " + invoices + ". Please check the logs for more details.", queueService.ErrorMessage); + } + else + { + MessageBox.Show(completeCount + " invoices marked as complete.", "Export Complete", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + } + else + { + MessageBox.Show("Operation cancelled. No invoices marked as complete."); + } + UpdateInvoiceExportBox(); + return; + } + + private void btnExport_Click(object sender, EventArgs e) + { + new Core.Test.Export.Export(); + } + + private void tabPage1_Click(object sender, EventArgs e) + { + } } } diff --git a/src/bnhtrade.gui/Program.cs b/src/bnhtrade.gui/Program.cs index ebc23e5..c088e10 100644 --- a/src/bnhtrade.gui/Program.cs +++ b/src/bnhtrade.gui/Program.cs @@ -1,3 +1,5 @@ +using System.Transactions; + namespace bnhtrade.gui { internal static class Program @@ -8,6 +10,8 @@ namespace bnhtrade.gui [STAThread] static void Main() { + TransactionManager.ImplicitDistributedTransactions = true; + // To customize application configuration such as set high DPI settings or default font, // see https://aka.ms/applicationconfiguration. ApplicationConfiguration.Initialize(); diff --git a/src/bnhtrade.gui/Utilities/FilePath.cs b/src/bnhtrade.gui/Utilities/FilePath.cs new file mode 100644 index 0000000..ac3e5f5 --- /dev/null +++ b/src/bnhtrade.gui/Utilities/FilePath.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.gui.Utilities +{ + internal class FilePath + { + internal string GetFromUser(string defaultFileName = null, string defaultFilePath = null) + { + return ""; + } + } +}