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

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