mirror of
https://github.com/stokebob/bnhtrade.git
synced 2026-03-20 06:57:15 +00:00
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:
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
445
src/bnhtrade.Core/Logic/Account/CurrencyService.cs
Normal file
445
src/bnhtrade.Core/Logic/Account/CurrencyService.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
41
src/bnhtrade.Core/Logic/Account/InvoiceLineItemService.cs
Normal file
41
src/bnhtrade.Core/Logic/Account/InvoiceLineItemService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
293
src/bnhtrade.Core/Logic/Export/AccountInvoice/QueueService.cs
Normal file
293
src/bnhtrade.Core/Logic/Export/AccountInvoice/QueueService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
23
src/bnhtrade.Core/Logic/Validate/AccountInvoiceLineItem.cs
Normal file
23
src/bnhtrade.Core/Logic/Validate/AccountInvoiceLineItem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
53
src/bnhtrade.Core/Logic/_boilerplate/UnitOfWorkPattern.cs
Normal file
53
src/bnhtrade.Core/Logic/_boilerplate/UnitOfWorkPattern.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user