Added invoice export function and started implementation of unitofwork pattern (#43)

* complete read invoices from db

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* updated nuget package spapi

* WIP

* wip, now test

* wip, jut need to fix tax inclusive line amounts not supported

* wip

* wip, before I f everything up

* no, it complies now, this is the one before I f everything up

* wip

* wip

* wip, logic ready for testing

* wip it builds!!!!

* wip tested, working, need to complete the gui section

* wip

* wip

* wip - created export invoice data delete, time for testing

* wip testing phase

* wip - delete function fully tested and working

* wip on to sorting out the issue with settlement invoices not tallying

* wip

* wip

* wip

* wip

* wip before I complete change the ReadInvoiceLineItem sections

* that appears to have worked, on with the main quest

* no it's doesn't work, saving before i remove the confusing cache system (just use a dictionary!!)

* wipping picadilli

* wip

* wip

* implemented uow on inovice export, now for testing

* wip

* wip all tested do invoice currency convertion fearure

* wip

* pretty much done so long as xero accepts the exported invoices

* Complete!
This commit is contained in:
Bobbie Hodgetts
2025-06-26 23:29:22 +01:00
committed by GitHub
parent 8bbf885a48
commit 29f9fae508
82 changed files with 4606 additions and 2837 deletions

View File

@@ -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
}
}
});

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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
{
/// <summary>
/// Returns excahnge rate, in decimal format, for a given currency and datetime
/// </summary>
/// <param name="currencyCode">currency code</param>
/// <param name="date">dat and time</param>
/// <returns></returns>
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<Model.Account.CurrencyExchangeRate> ReadExchangeRate(List<Model.Account.CurrencyCode> currencyCodeList = null, DateTime date = default(DateTime))
{
throw new NotImplementedException("Complete, but untested");
var returnList = new List<Model.Account.CurrencyExchangeRate>();
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<string> stringList = new List<string>();
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<Model.Account.CurrencyExchangeRate> ReadExchangeRateLatest(List<Model.Account.CurrencyCode> currencyCodeList = null)
{
var returnList = new List<Model.Account.CurrencyExchangeRate>();
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<string>();
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;
}
}
}
}

View File

@@ -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;

View File

@@ -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<string, int> AccountCodeList { get; private set; }
public Dictionary<string, string> TaxCodeList { get; private set; }
private void Innit()
/// <summary>
/// Reads all invoice line items from the database.
/// </summary>
/// <returns>dictionary where key=id, value=object</returns>
internal Dictionary<int, Model.Account.InvoiceLineItem> All()
{
AccountCodeList = new Dictionary<string, int>();
TaxCodeList = new Dictionary<string, string>();
return Execute();
}
public Model.Account.InvoiceLineItem ByItemCode(string itemCode)
/// <summary>
/// Read list of invoice line items by item code.
/// </summary>
/// <param name="itemCodes">List of item coeds to query db against</param>
/// <returns>dictionary where key=id, value=object</returns>
/// <exception cref="ArgumentException"></exception>
internal Dictionary<int, Model.Account.InvoiceLineItem> ByItemCode(List<string> itemCodes)
{
if (string.IsNullOrWhiteSpace(itemCode)) { return null; }
var result = ByItemCode(new List<string>{ 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<string, Model.Account.InvoiceLineItem> ByItemCode(List<string> itemCodeList)
private Dictionary<int, Model.Account.InvoiceLineItem> Execute(SqlWhereBuilder sqlwhere = null)
{
Innit();
var resultList = new Dictionary<string, Model.Account.InvoiceLineItem>();
if (itemCodeList == null || !itemCodeList.Any())
{ return resultList; }
var resultList = new Dictionary<int, Model.Account.InvoiceLineItem>();
var accountCodeIdList = new Dictionary<int, uint>(); // key=LineItemID, value=AccountChartOfID
var taxCodeIdList = new Dictionary<int, int>(); // 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;
}
}
}

View File

@@ -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<int> PurchaseInvoiceIdList { get; set; }
public IEnumerable<int> PurchaseInvoiceIdList { get; set; }
public ReadPurchaseInvoice()
{

View File

@@ -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<Model.Account.TaxCodeInfo> Execute(string sqlWhere, Dictionary<string, object> parameters)
private Dictionary<int, Model.Account.TaxCodeInfo> Execute(string sqlWhere, Dictionary<string, object> parameters)
{
var resultList = new List<Model.Account.TaxCodeInfo>();
var resultList = new Dictionary<int, Model.Account.TaxCodeInfo>();
//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<Model.Account.TaxCodeInfo> GetByTaxCodeId(List<int> taxcodeIdList)
public Dictionary<int, Model.Account.TaxCodeInfo> GetByTaxCodeId(List<int> taxcodeIdList)
{
var resultList = new List<Model.Account.TaxCodeInfo>();
var resultList = new Dictionary<int, Model.Account.TaxCodeInfo>();
if (taxcodeIdList == null || !taxcodeIdList.Any())
{
@@ -117,7 +120,12 @@ namespace bnhtrade.Core.Data.Database.Account
return Execute(whereBuilder.SqlWhereString, whereBuilder.ParameterList);
}
public List<Model.Account.TaxCodeInfo> GetAllActive()
/// <summary>
/// Gets all active Tax Code objects
/// </summary>
/// <returns>Dictionary where database record ID is the key</returns>
public Dictionary<int, Model.Account.TaxCodeInfo> GetAllActive()
{
string sqlWhere = @"
WHERE IsActive=@isActive;";

View File

@@ -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;
}
}
}

View File

@@ -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<string> 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<string>();
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;
}
}
}

View File

@@ -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 ()
{
}

View File

@@ -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()
{
}

View File

@@ -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()
{
}

View File

@@ -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<Model.Account.SalesInvoice> 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();
}
}
}
}

View File

@@ -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<string, int> dicTablePkBySettlementId = new Dictionary<string, int>();
private int? returnTop = null;
private List<string> settlementIdList;
private List<string> 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<string> SettlementIdList
{
get { return settlementIdList; }
set
{
if (value.Any())
{ settlementIdList = value; }
}
}
private List<string> 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<Model.Import.AmazonSettlementHeader> AllUnprocessed()
{
Innit();
FilterOutIsProcessed = true;
return ReadHeaderList();
}
public Model.Import.AmazonSettlementHeader BySettlementId(string settlementId)
{
Innit();
// create settlement list
var idList = new List<string>();
idList.Add(settlementId);
var settlementList = BySettlementId(idList);
// return answer
if (settlementList == null || !settlementList.Any())
{ return null; }
else
{ return settlementList.First(); }
}
public List<Model.Import.AmazonSettlementHeader> BySettlementId(List<string> settlementIdList)
{
Innit();
if (settlementIdList == null || !settlementIdList.Any())
{ return new List<Model.Import.AmazonSettlementHeader>(); }
SettlementIdList = settlementIdList;
return ReadHeaderList();
}
public List<Model.Import.AmazonSettlementHeader> BySpapiReportId(List<string> spapiReportIdList)
{
Innit();
if (spapiReportIdList == null || !spapiReportIdList.Any())
{ return new List<Model.Import.AmazonSettlementHeader>(); }
SpapiReportIdList = spapiReportIdList;
return ReadHeaderList();
}
private List<Model.Import.AmazonSettlementHeader> ReadHeaderList()
{
var returnHeaderList = new List<Model.Import.AmazonSettlementHeader>();
// 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<string, string>();
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<string, string>();
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;
}
}
}

View File

@@ -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 ()
{
}
/// <summary>
///
/// </summary>
/// <param name="filePath"></param>
/// <param name="reportId">The unique Amazon SP-API report id (not settlement id)</param>
/// <returns></returns>
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();
}
}
}
}
}

View File

@@ -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<Model.Import.AmazonSettlement> BySettlementIdList(List<string> settlementIdList)
{
var settlementList = new List<Model.Import.AmazonSettlement>();
// 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<Model.Import.AmazonSettlement.SettlementLine>();
var LineListDic = new Dictionary<string, List<Model.Import.AmazonSettlement.SettlementLine>>();
var headerList = new List<Model.Import.AmazonSettlementHeader>();
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<Model.Import.AmazonSettlement.SettlementLine>();
}
}
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<string, int> dicTablePkBySettlementId = new Dictionary<string, int>();
private int? returnTop = null;
private List<string> 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<string> 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<Model.Import.AmazonSettlement> AllUnprocessed()
{
Innit();
FilterOutIsProcessed = true;
return ExecuteDbQuery();
}
public Model.Import.AmazonSettlement BySettlementId(string settlementId)
{
Innit();
// create settlement list
var idList = new List<string>();
idList.Add(settlementId);
var settlementList = BySettlementId(idList);
// return answer
if (settlementList == null || !settlementList.Any())
{ return null; }
else
{ return settlementList.First(); }
}
public List<Model.Import.AmazonSettlement> BySettlementId(List<string> settlementIdList)
{
Innit();
if (settlementIdList == null || !settlementIdList.Any())
{ return null; }
SettlementIdList = settlementIdList;
return ExecuteDbQuery();
}
private List<Model.Import.AmazonSettlement> 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<Model.Import.AmazonSettlement> 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<string, string>();
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<Model.Import.AmazonSettlement>();
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<Model.Import.AmazonSettlement.SettlementLine> 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<Model.Import.AmazonSettlement.SettlementLine>();
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;
}
}
}
}
}
}

View File

@@ -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<string> 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.");
}
}
}
}
}
}

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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)
{
}
/// <summary>
/// Returns excahnge rate, in decimal format, for a given currency and datetime
/// </summary>
/// <param name="currencyCode">currency code</param>
/// <param name="date">dat and time</param>
/// <returns></returns>
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<Model.Account.CurrencyExchangeRate> ReadExchangeRate(List<Model.Account.CurrencyCode> currencyCodeList = null, DateTime date = default(DateTime))
{
//throw new NotImplementedException("Complete, but untested");
var returnList = new List<Model.Account.CurrencyExchangeRate>();
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<string> stringList = new List<string>();
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<Model.Account.CurrencyExchangeRate> ReadExchangeRateLatest(List<Model.Account.CurrencyCode> currencyCodeList = null)
{
var returnList = new List<Model.Account.CurrencyExchangeRate>();
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<string>();
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;
}
}
}

View File

@@ -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
//
/// <summary>
/// Don't call this directly, use the logic layer instead (for validation and other checks).
/// </summary>
/// <param name="invoiceList">list of sales invoices to insert</param>
/// <returns>dectionary, key= invoice id, value= new invoice </returns>
public Dictionary<int, Model.Account.SalesInvoice> InsertSalesInvoices(IEnumerable<Model.Account.SalesInvoice> invoiceList)
{
var result = InsertBaseExecute(invoiceList);
var returnList = new Dictionary<int, Model.Account.SalesInvoice>();
foreach (var item in result)
{
returnList.Add(item.Key, (Model.Account.SalesInvoice)item.Value);
}
return returnList;
}
private Dictionary<int, Model.Account.Invoice> InsertBaseExecute(IEnumerable<Model.Account.Invoice> invoiceList)
{
if (invoiceList == null)
{
throw new ArgumentException("Invoice list is null");
}
var returnList = new Dictionary<int, Model.Account.Invoice>();
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
//
/// <summary>
/// Returns list of invoice numbers that have not yet been exported (i.e. IsComplete=True)
/// </summary>
/// <param name="invoiceType">Sales or Purchase</param>
/// <returns>Dictionary where key=ExportAccountInvoiceID, value=InvoiceNumber</returns>
public Dictionary<int, string> GetNewInvoiceNumbers(Model.Account.InvoiceType invoiceType)
{
var returnList = new Dictionary<int, string>();
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;
}
/// <summary>
/// Get list of invoices by invoice id
/// </summary>
/// <param name="idList">list of invoice ids</param>
/// <returns>dictionary key=id, value=invoice-model</returns>
public Dictionary<int, Model.Account.SalesInvoice> GetSalesInvoiceById(IEnumerable<int> idList)
{
var returnList = new Dictionary<int, Model.Account.SalesInvoice>();
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<Model.Account.CurrencyCode>(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
//
/// <summary>
/// Update the 'IsComplete' field by invoice id
/// </summary>
/// <param name="updateDictionary">key=ExportAccountInvoiceTypeID, value=IsComplete</param>
/// <returns>Number of row effected</returns>
public int SetInvoiceIsCompleteValue(Dictionary<int, bool> 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<int> { invoiceLineId });
return DeleteInvoiceExecuteInvoiceLine(sqlWhereBuilder);
}
public int DeleteInvoice(IEnumerable<int> 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.");
}
}
/// <summary>
/// Helper method Only intended for use with ExecuteInvoice() or ExecuteInvoiceLine()
/// </summary>
/// <param name="sql">the full sql statement</param>
/// <param name="sqlWhereBuilder">instance of the sql where builder helper</param>
/// <returns>Number of row effected (deleted)</returns>
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);
}
}
}

View File

@@ -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<Model.Import.AmazonSettlementHeader> ReadAmazonSettlementHeaderInfoBySettlementId(List<string> settlementIdList)
{
if (settlementIdList == null || !settlementIdList.Any())
{ return new List<Model.Import.AmazonSettlementHeader>(); }
return ReadAmazonSettlementHeaders(settlementIdList);
}
public List<Model.Import.AmazonSettlementHeader> ReadAmazonSettlementHeaderInfoBySpapiReportId(List<string> spapiReportIdList)
{
if (spapiReportIdList == null || !spapiReportIdList.Any())
{ return new List<Model.Import.AmazonSettlementHeader>(); }
return ReadAmazonSettlementHeaders(null, spapiReportIdList);
}
private List<Model.Import.AmazonSettlementHeader> ReadAmazonSettlementHeaders(
List<string> settlementIdList = null, List<string> spapiReportIdList = null, bool filterOutIsProcessed = false,
bool DescendingOrder = false, int? returnTop = null)
{
var returnHeaderList = new List<Model.Import.AmazonSettlementHeader>();
Dictionary<string, int> dicTablePkBySettlementId = new Dictionary<string, int>();
// 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<string, string>();
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<string, string>();
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
//
/// <summary>
/// Gets Amazon settlement report information from the database.
/// </summary>
/// <returns>Dictionary where key=id and value=settlement</returns>
public Dictionary<int, Model.Import.AmazonSettlement> ReadAmazonSettlements(
List<string> settlementIdList = null, List<string> marketPlaceNameList = null, bool? isProcessed = null,
bool descendingOrder = false, int? returnTop = null )
{
var returnList = new Dictionary<int, Model.Import.AmazonSettlement>();
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
//
/// <summary>
/// Set the IsProcessed flag to true or false for the specified settlement Ids.
/// </summary>
/// <param name="settlementIdList">List of settlement id (not table id)</param>
/// <param name="isProcessed">value to set the isProcessed to</param>
/// <returns>Number of rows effected</returns>
public int SetAmazonSettlementIsProcessed(List<string> 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();
}
}
/// <summary>
/// Takes a Settlement Report flat file from Amazon and imports it into the database.
/// </summary>
/// <param name="filePath">path to the Amazon report flat file</param>
/// <param name="reportId">The unique Amazon SP-API report id (not settlement id)</param>
/// <returns></returns>
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();
}
}
}
}

View File

@@ -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);
}
}
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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<int, string> SelectByList(List<string> selectList)
{
var returnList = new Dictionary<int, string>();
// 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;
}
}
}
}

View File

@@ -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<Model.Account.CurrencyExchangeRate> ReadExchangeRate(List<Model.Account.CurrencyCode> currencyCodeList = null, DateTime date = default(DateTime));
public List<Model.Account.CurrencyExchangeRate> ReadExchangeRateLatest(List<Model.Account.CurrencyCode> currencyCodeList = null);
public int InsertExchangeRate(int exchangeRateSource, Model.Account.CurrencyCode currencyCode,
decimal currencyUnitsPerGbp, DateTime periodStartUtc, DateTime periodEnd, bool checkOverride = false);
}
}

View File

@@ -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<int, Model.Account.SalesInvoice> InsertSalesInvoices(IEnumerable<Model.Account.SalesInvoice> invoiceList);
internal Dictionary<int, string> GetNewInvoiceNumbers(Model.Account.InvoiceType invoiceType);
internal Dictionary<int, Model.Account.SalesInvoice> GetSalesInvoiceById(IEnumerable<int> idList);
internal int SetInvoiceIsCompleteValue(Dictionary<int, bool> updateDictionary);
internal int DeleteInvoiceLine(int invoiceLineId);
internal int DeleteInvoice(IEnumerable<int> invoiceIdList);
}
}

View File

@@ -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<Model.Import.AmazonSettlementHeader> ReadAmazonSettlementHeaderInfoBySettlementId(List<string> settlementIdList);
public List<Model.Import.AmazonSettlementHeader> ReadAmazonSettlementHeaderInfoBySpapiReportId(List<string> spapiReportIdList);
public Dictionary<int, Model.Import.AmazonSettlement> ReadAmazonSettlements(
List<string> settlementIdList = null, List<string> marketPlaceNameList = null, bool? isProcessed = null,
bool descendingOrder = false, int? returnTop = null);
public int SetAmazonSettlementIsProcessed(List<string> settlementIdList, bool isProcessed);
public bool CreateAmazonSettlements(string filePath, string reportId);
public int SetSpapiReportId(string settlementId, string spapiReportId);
}
}

View File

@@ -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);
}
}

View File

@@ -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<int, string> SelectByList(List<string> selectList);
internal int Insert();
internal int Update();
}
}

View File

@@ -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<int> { 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.");
}

View File

@@ -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.
/// </summary>
public class SqlWhereBuilder
{
@@ -44,6 +48,21 @@ namespace bnhtrade.Core.Data.Database
public Dictionary<string, object> ParameterList { get; private set; }
public int ParameterListCount
{
get
{
if (ParameterList == null || ParameterList.Any() == false)
{
return 0;
}
else
{
return ParameterList.Count();
}
}
}
/// <summary>
/// Initialises the class
/// </summary>
@@ -83,7 +102,7 @@ namespace bnhtrade.Core.Data.Database
/// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="phraseList">List of phrases to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void LikeAnd(string columnReference, List<string> phraseList, string wherePrefix = null)
public void LikeAnd(string columnReference, IEnumerable<string> phraseList, string wherePrefix = null)
{
Like(columnReference, phraseList, true, wherePrefix);
}
@@ -94,12 +113,12 @@ namespace bnhtrade.Core.Data.Database
/// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="phraseList">List of phrases to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void LikeOr(string columnReference, List<string> phraseList, string wherePrefix = null)
public void LikeOr(string columnReference, IEnumerable<string> phraseList, string wherePrefix = null)
{
Like(columnReference, phraseList, false, wherePrefix);
}
private void Like(string columnReference, List<string> phraseList, bool isAnd, string wherePrefix = null)
private void Like(string columnReference, IEnumerable<string> 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
/// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="orValueList">List of values to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void In(string columnReference, List<string> orValueList, string wherePrefix = null)
public void In(string columnReference, IEnumerable<object> orValueList, string wherePrefix = null)
{
if (orValueList == null || !orValueList.Any())
{
@@ -208,19 +227,19 @@ namespace bnhtrade.Core.Data.Database
/// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="orValueList">List of values to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void In(string columnReference, List<int> orValueList, string wherePrefix = null)
public void In(string columnReference, IEnumerable<string> orValueList, string wherePrefix = null)
{
var stringList = new List<string>();
var objectList = new List<object>();
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);
}
/// <summary>
@@ -229,19 +248,55 @@ namespace bnhtrade.Core.Data.Database
/// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="orValueList">List of values to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void In(string columnReference, List<uint> orValueList, string wherePrefix = null)
public void In(string columnReference, IEnumerable<int> orValueList, string wherePrefix = null)
{
var stringList = new List<string>();
var objectList = new List<object>();
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);
}
/// <summary>
/// Append an 'In' statement and parameter list to the class properties
/// </summary>
/// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="orValueList">List of values to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void In(string columnReference, IEnumerable<uint> orValueList, string wherePrefix = null)
{
var objectList = new List<object>();
if (orValueList != null && orValueList.Any())
{
foreach (var value in orValueList)
{
objectList.Add(value.ToString());
}
}
In(columnReference, objectList, wherePrefix);
}
public void In(string columnReference, IEnumerable<DateTime> orValueList, string wherePrefix = null)
{
var objectList = new List<object>();
if (orValueList != null && orValueList.Any())
{
foreach(var value in orValueList)
{
objectList.Add(value.ToString());
}
}
In(columnReference, objectList, wherePrefix);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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
{

View File

@@ -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<string> Select()
{
using (SqlConnection conn = new SqlConnection())
var returnList = new List<string>();
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<string> stringList)
internal string SelectByList(List<string> 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<Tuple<string, string>>();
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();

View File

@@ -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;
/// <summary>
/// Results filter

View File

@@ -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<Core.Model.Account.SalesInvoice> 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<CsvLine>();
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);
}
}
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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<Model.Account.CurrencyExchangeRate> _exchangeRateList = new List<Model.Account.CurrencyExchangeRate>();
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> { (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<Model.Account.CurrencyCode> { 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> { (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<Model.Account.CurrencyExchangeRate> GetExchangeRateObjectList(List<Model.Account.CurrencyCode> 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.");
}
}
}
}

View File

@@ -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<string, Model.Account.InvoiceLineItem> 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();
}
/// <summary>
/// Create new 'default' line item code when a requested item code is not found.
/// </summary>
public bool InsertNewOnNoMatch { get; set; } = false;
public void CacheInnit()
{
cache = new Dictionary<string, Model.Account.InvoiceLineItem>();
}
/// <summary>
/// Prefill cache in one SQL call, saves multiple SQL calls.
/// </summary>
/// <param name="itemCodeList">List of item codes to lookup from database</param>
/// <param name="forceDbRead">Forces a database read (does not read cache)</param>
public void CacheFill(List<string> itemCodeList, bool forceDbRead = false)
{
if (itemCodeList == null || !itemCodeList.Any())
{
return;
}
var itemCodeQueryList = new List<string>();
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);
}
}
/// <summary>
/// Creates new 'default' item code entry. The item code and title are set the same and will require updating.
/// </summary>
/// <param name="itemCode">Item code string</param>
/// <returns></returns>
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<string> { 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);
}
}
/// <summary>
/// Tests if ItemCode has Invoice line entry enabled
/// </summary>
/// <param name="itemCode">Item code</param>
/// <param name="forceDbRead">Forces a database read (does not read cache)</param>
/// <returns></returns>
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;
}
/// <summary>
/// Tests if ItemCode is new (default) and a review id required
/// </summary>
/// <param name="itemCode">Item code</param>
/// <param name="forceDbRead">Forces a database read (does not read cache)</param>
/// <returns></returns>
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;
}
}
}

View File

@@ -15,11 +15,6 @@ namespace bnhtrade.Core.Logic.Account
dbRead = new Data.Database.Account.ReadTaxCode();
}
public List<Model.Account.TaxCodeInfo> GetAllActive()
{
return dbRead.GetAllActive();
}
public List<Model.Account.TaxCodeInfo> GetByTaxCode(List<string> 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
/// </summary>
/// <param name="skuNumberList">List of SKU numbers</param>
/// <returns>Dictionary, key is SkuNumber and value is Tax Code Info</returns>
/// <returns>Dictionary, key=SkuNumber and value=TaxCodeInfo</returns>
public Dictionary<string, Model.Account.TaxCodeInfo> GetBySkuNumber(List<string> skuNumberList)
{
var returnList = new Dictionary<string, Model.Account.TaxCodeInfo>();

View File

@@ -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();
/// <summary>
/// Creates new 'default' item code entry. The item code and title are set, will require updating by user before use.
/// </summary>
/// <param name="itemCode">Item code string</param>
/// <returns></returns>
public Model.Account.InvoiceLineItem CreateNew(string itemCode)
{
new Data.Database.Account.CreateInvoiceLineItem().CreateDefault(itemCode);
var item = new Data.Database.Account.ReadInvoiceLineItem().ByItemCode(new List<string> { 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<string, Model.Account.InvoiceLineItem> GetLineItems(List<string> itemCodes)
{
var dbResult = new Data.Database.Account.ReadInvoiceLineItem().ByItemCode(itemCodes);
var returnDict = new Dictionary<string, Model.Account.InvoiceLineItem>();
foreach ( var item in dbResult)
{
returnDict.Add(item.Value.ItemCode, item.Value);
}
return returnDict;
}
}
}

View File

@@ -6,7 +6,18 @@ using System.Threading.Tasks;
namespace bnhtrade.Core.Logic.Account
{
public class PurchaseInvoice : Core.Data.Database.Account.ReadPurchaseInvoice
public class PurchaseInvoice
{
/// <summary>
/// Get purchase invoices by id
/// </summary>
/// <param name="purchaseIdList">purchase id list</param>
/// <returns>dictionary where key=id, value=purchaseinvoice</returns>
public Dictionary<int, Model.Account.PurchaseInvoice> GetPurchaseInvoice(IEnumerable<int> purchaseIdList)
{
var dbRead = new Data.Database.Account.ReadPurchaseInvoice();
dbRead.PurchaseInvoiceIdList = purchaseIdList;
return dbRead.Read();
}
}
}

View File

@@ -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<string> _lineItemCodeList = new List<string>();
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;
}
/// <summary>
/// Creates invoices from Amazon settlement reports and saves the invoices to database Export Invoice table
/// </summary>
/// <exception cref="Exception"></exception>
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<string>();
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;
}
/// <summary>
/// Retrives a list of unprocessed settlement reports from the database, checks for gaps in settlement periods, and validates
/// the settlement data.
/// </summary>
/// <param name="newMarketplaceNameList">Import will fail on new marketplace, add here to bypass this check</param>
/// <returns></returns>
private List<Model.Import.AmazonSettlement> GetListOfUnprocessedSettlementReports()
{
List<Model.Import.AmazonSettlement> 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<string> { 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<string, Model.Account.TaxCodeInfo> 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 = "<AmazonReport><SettlementReportLine><" + 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 + "<TaxCode=" + taxCodeBySkuNumer[skuNumber].TaxCode + ">";
}
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<Model.Account.SalesInvoice> CreateInvoices(List<Model.Import.AmazonSettlement> settlementList, bool convertToGbp = true)
{
// create list of settlement ids for later use
var settlementIdList = new List<string>();
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<string>();
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<Model.Account.SalesInvoice>();
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<string, decimal>();
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<Model.Account.SalesInvoice.InvoiceLine>();
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<Model.Account.CurrencyCode>(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<string, decimal>();
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;
}
}
}

View File

@@ -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
{
}
}

View File

@@ -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
{
/// <summary>
/// Processes the Export Invoice table and exports to Xero
/// </summary>
public class QueueService
{
private Log.LogEvent _log = new Log.LogEvent();
private IEnumerable<int> _exportSaleInvoiceIdList = new List<int>();
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<Model.Account.IInvoice> 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();
}
}
/// <summary>
/// Read invoices from datbase (with validation)
/// </summary>
/// <param name="invoiceIdList">list of invoice id to retrive</param>
/// <returns>Dictionary where key=id, value=invoice-model</returns>
/// <exception cref="Exception">Failed validation</exception>
public Dictionary<int, Model.Account.SalesInvoice> ReadInvoiceById(IEnumerable<int> 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;
}
}
/// <summary>
/// Adds sales invoice to database export table, ready for export
/// </summary>
/// <param name="invoiceList">List of sales invoices</param>
/// <returns>Dictionary where key=invoice id, value=invoice model</returns>
internal Dictionary<int, Model.Account.SalesInvoice> Insert(IEnumerable<Model.Account.SalesInvoice> 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;
}
}
/// <summary>
/// Gets count of new invoices ready to export to external accounting software
/// </summary>
/// <returns>Count of new invoices as int</returns>
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;
}
}
/// <summary>
///
/// </summary>
/// <param name="filePath"></param>
/// <param name="firstInvoiceNumber"></param>
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);
}
}
/// <summary>
/// Call this after ExportSalesInvoice() to mark exported invoices as complete
/// </summary>
/// <returns>number of invoices effected</returns>
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<int, bool>();
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);
}
}
}
}
}

View File

@@ -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<string> lineItemCodeList = new List<string>();
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<string>();
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<string>();
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<Model.Account.SalesInvoice>();
for (int i = 0; i < settlementList.Count(); i++)
{
// split settlement line list into months
// List<Model.Account.SalesInvoice.InvoiceLine>
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<string, decimal>();
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<Model.Account.SalesInvoice.InvoiceLine>();
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<string, decimal>();
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<string, Model.Account.TaxCodeInfo> 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 = "<AmazonReport><SettlementReportLine><" + 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 + "<TaxCode=" + taxCodeBySkuNumer[skuNumber].TaxCode + ">";
}
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;
}
}
}

View File

@@ -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<ValidationResult>();
@@ -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

View File

@@ -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<Model.Account.IInvoice> 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<Model.Account.SalesInvoice> 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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -16,7 +16,7 @@ namespace bnhtrade.Core.Logic.Utilities
/// <param name="inputList"></param>
/// <param name="includeWhiteSpace"></param>
/// <returns>Unique list</returns>
public List<string> UniqueList(List<string> inputList, bool removeNullorWhitespace = true)
public List<string> UniqueList(IEnumerable<string> inputList, bool removeNullorWhitespace = true)
{
List<string> outputList = new List<string>();

View File

@@ -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

View File

@@ -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<Model.Account.InvoiceLineItem> invoiceLineItems)
{
return base.IsValid(invoiceLineItems);
}
}
}

View File

@@ -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))

View File

@@ -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<Model.Account.Invoice> 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)

View File

@@ -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();
}
}
}
}
}

View File

@@ -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;
}
/// <summary>
/// Checks whether a given datetime falls within the the exchange rate period

View File

@@ -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;
}
}
}
}
/// <summary>
/// Invoice line unit amount is tax exclusive
/// </summary>
public bool InvoiceLineUnitAmountIsTaxExclusive { get; set; }
public List<InvoiceLine> InvoiceLineList { get; set; } = new List<InvoiceLine>();
@@ -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()]
/// <summary>
/// Tax amount calculated from line amount and the taxcode rate (before any adjustments)
/// </summary>
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
/// <summary>
/// Will adjust the calculated the tax total amount for the line
/// </summary>
public decimal TaxAmountAdjust
{
get { return taxAmountAdjust; }
set
set { taxAmountAdjust = RoundAmount(value); }
}
/// <summary>
/// The calculated total tax amount for the line (including any adjustments)
/// </summary>
[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; }
}
}
/// <summary>
/// This will calculate and update the tax adjustment property from a given tax amount total
/// </summary>
/// <param name="taxAmountTotal">The required tax amount total</param>
public void SetTaxAdjustmentByTaxTotal(decimal taxAmountTotal)
{
if (CanCalculateLineTotalAmount)
{
TaxAmountAdjust = taxAmountTotal - TaxAmount.Value;
}
}
/// <summary>
/// Sets the unit amount based on the specified line total amount.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="lineTotalAmount">The total amount for the line item. Must be a whole number to two decimal places.</param>
/// <exception cref="ArgumentException">Thrown if <paramref name="lineTotalAmount"/> is not a whole number to two decimal places.</exception>
/// <exception cref="InvalidOperationException">Thrown if the <c>TaxCode</c> is null or the quantity is not set.</exception>
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");
}
}
/// <summary>
/// Rounds decimals to two decimal places inline with Xero specs
/// </summary>
/// <param name="amount">The amount to round</param>
/// <returns>Rounded amount to two decimal places</returns>
/// <exception cref="ArgumentException"></exception>
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<ValidationResult> Validate(ValidationContext validationContext)
{
throw new NotImplementedException();
var resultList = new List<ValidationResult>();
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<ValidationResult> Validate(ValidationContext validationContext)
{
var subResult = base.Validate(validationContext);
var resultList = new List<ValidationResult>();
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;

View File

@@ -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; }

View File

@@ -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<ValidationResult>();
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;
}

View File

@@ -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
}
}

View File

@@ -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; }

View File

@@ -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
}
}
}

View File

@@ -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))
};
}
}
}

View File

@@ -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
{
/// <summary>
/// Sale or purchase invoice, id matches the database id
/// </summary>
public enum AccountInvoiceType
{
Purchase = 1,
Sale = 2
}
}

View File

@@ -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

View File

@@ -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<int> { 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<int> { 14718, 100, 101, 102, 300, 400, 1200, 2734, 6339, 9999 }); // 10 in total
}
public void PurchaseInvoiceLine()

View File

@@ -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);
}
}
}

View File

@@ -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<int> { 550, 555 });
}
private void ExportSalesInvoiceQueue()
{
var invoice = new Model.Account.SalesInvoice(true);
invoice.InvoiceLineList = new List<Model.Account.Invoice.InvoiceLine>();
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<int> { 576, 577, 584 });
int lskdjflsd = uow.ImportAmazonRepository.SetAmazonSettlementIsProcessed(new List<string> { "24684639382f", "14720584692f", "14386695522f" }, false);
uow.Commit();
}
}
}
}

View File

@@ -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<string> { "15734813312", "14473886262", "13984682252" };
var result = new Data.Database.Import.AmazonSettlementRead().BySettlementIdList(setList);
//var result = new Data.Database.Import.AmazonSettlementRead().BySettlementId(setList);
}
public void FBAInventory()

View File

@@ -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<string> { "Amazon.fr" }, returnTop: 3);
}
}
public void UpdateExportInvoiceQueue()
{
new Core.Logic.Export.AccountInvoice.QueueService().ImportAll();
}
}
}

View File

@@ -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;

View File

@@ -27,13 +27,14 @@
<None Remove="Data\Database\Inventory\**" />
</ItemGroup>
<ItemGroup>
<Folder Include="Data\Database\Programmability\" />
<Folder Include="Model\Inventory\" />
<Folder Include="Model\Product\ProductPricing\" />
<Folder Include="Test\Product\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.Core" Version="3.7.402.47" />
<PackageReference Include="CSharpAmazonSpAPI" Version="1.7.17" />
<PackageReference Include="CSharpAmazonSpAPI" Version="1.8.9" />
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />