Bug fix in ExportSalesInvoice and converted more code over to repository and service pattern

This commit is contained in:
2025-07-07 15:22:21 +01:00
parent 5900a6e6e4
commit 5cd653d700
64 changed files with 2623 additions and 2517 deletions

View File

@@ -6,18 +6,18 @@ using System.Threading.Tasks;
namespace bnhtrade.Core.Logic.Account
{
public class GetAccountCodeInfo
public class AccountCodeService : UnitOfWorkBase
{
private Data.Database.Account.ReadAccountCode readAccountCode;
public GetAccountCodeInfo()
public AccountCodeService()
{
readAccountCode = new Data.Database.Account.ReadAccountCode();
}
public Dictionary<uint, Model.Account.Account> GetAll()
public Dictionary<int, Model.Account.Account> GetAll()
{
return readAccountCode.All();
return WithUnitOfWork(uow =>
{
return uow.AccountCodeRepository.ReadAccountCode();
});
}
public Model.Account.Account ByAccountCode(int accountCode)
@@ -35,7 +35,10 @@ namespace bnhtrade.Core.Logic.Account
public List<Model.Account.Account> ByAccountCode(List<int> accountCodeList)
{
return readAccountCode.ByAccountCode(accountCodeList).Values.ToList();
return WithUnitOfWork(uow =>
{
return uow.AccountCodeRepository.ReadAccountCode(null, accountCodeList).Values.ToList();
});
}
public Dictionary<int, Model.Account.Account> ConvertToDictionary(List<Model.Account.Account> accountCodeList)

View File

@@ -3,21 +3,22 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace bnhtrade.Core.Logic.Account
{
public class GetTaxCodeInfo
public class GetTaxCodeInfo : UnitOfWorkBase
{
private Data.Database.Account.ReadTaxCode dbRead;
public GetTaxCodeInfo()
{
dbRead = new Data.Database.Account.ReadTaxCode();
}
public List<Model.Account.TaxCodeInfo> GetByTaxCode(List<string> taxCodeList)
{
return dbRead.GetByTaxCode(taxCodeList);
return WithUnitOfWork(uow =>
{
return uow.AccountTaxRepository.ReadTaxCodeInfo(null, taxCodeList).Values.ToList();
});
}
public Model.Account.TaxCodeInfo GetByTaxCode(string taxCode)
@@ -48,7 +49,11 @@ namespace bnhtrade.Core.Logic.Account
}
// get db list
var dbList = dbRead.GetTaxCodeBySkuNumber(skuNumberList);
var dbList = WithUnitOfWork(uow =>
{
return uow.AccountTaxRepository.GetTaxCodeBySkuNumber(skuNumberList);
});
// build dictionary
foreach (var item in dbList)

View File

@@ -1,4 +1,5 @@
using System;
using bnhtrade.Core.Data.Database.UnitOfWork;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -6,9 +7,17 @@ using System.Threading.Tasks;
namespace bnhtrade.Core.Logic.Account
{
public class InvoiceLineItemService
public class InvoiceLineItemService : UnitOfWorkBase
{
private Logic.Log.LogEvent log = new Logic.Log.LogEvent();
private Logic.Log.LogEvent _log = new Logic.Log.LogEvent();
public InvoiceLineItemService()
{
}
internal InvoiceLineItemService(IUnitOfWork unitOfWork) : base(unitOfWork)
{
}
/// <summary>
/// Creates new 'default' item code entry. The item code and title are set, will require updating by user before use.
@@ -17,25 +26,32 @@ namespace bnhtrade.Core.Logic.Account
/// <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)
return WithUnitOfWork(uow =>
{
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];
uow.InvoiceRepository.CreateDefaultInvoiceLineItem(itemCode);
var item = uow.InvoiceRepository.GetInvoiceLineItem(null, 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");
}
CommitIfOwned(uow);
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)
return WithUnitOfWork(uow =>
{
returnDict.Add(item.Value.ItemCode, item.Value);
}
return returnDict;
var dbResult = uow.InvoiceRepository.GetInvoiceLineItem(null, 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

@@ -8,7 +8,7 @@ using System.Transactions;
namespace bnhtrade.Core.Logic.Account
{
public class Journal
public class Journal : UnitOfWorkBase
{
public int AccountJournalInsert(int journalTypeId, DateTime entryDate, string currencyCode,
decimal amount, int debitAccountId = 0, int creditAccountId = 0, bool lockEntry = false)
@@ -19,7 +19,12 @@ namespace bnhtrade.Core.Logic.Account
public bool AccountJournalDelete(int accountJournalId)
{
return new Data.Database.Account.DeleteJournal().AccountJournalDelete(accountJournalId);
return WithUnitOfWork(uow =>
{
bool result = uow.JournalRepository.DeleteJournal(accountJournalId);
CommitIfOwned(uow);
return result;
});
}
}
}

View File

@@ -1,23 +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 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

@@ -1,21 +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 PurchaseInvoiceMisc
{
public List<Model.Account.PurchaseInvoiceLineStatus> ReadLineStatusToList()
{
return new Data.Database.Account.ReadPurchaseInvoiceLineStatus().Read().Values.ToList();
}
public List<Model.Account.PurchaseInvoiceLineSummary> GetLineSummary(DateTime maxDate, string lineStatus, List<string> wordSearchList)
{
return new Data.Database.Account.ReadPurchaseInvoiceLineSummary().Read(maxDate,lineStatus, wordSearchList);
}
}
}

View File

@@ -0,0 +1,55 @@
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.Account
{
public class PurchaseInvoiceService : UnitOfWorkBase
{
public PurchaseInvoiceService() { }
internal PurchaseInvoiceService(IUnitOfWork unitOfWork) : base(unitOfWork)
{
}
/// <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)
{
return WithUnitOfWork(uow =>
{
var invoiceRepository = uow.InvoiceRepository;
var returnList = invoiceRepository.ReadPurchaseInvoice(purchaseIdList);
return returnList;
});
}
public List<Model.Account.PurchaseInvoiceLineStatus> ReadLineStatusToList()
{
return WithUnitOfWork(uow =>
{
var invoiceRepository = uow.InvoiceRepository;
var returnList = invoiceRepository.ReadPurchaseInvoiceLineStatus().Values.ToList();
return returnList;
});
}
public List<Model.Account.PurchaseInvoiceLineSummary> GetLineSummary(DateTime maxDate, string lineStatus, List<string> wordSearchList)
{
return WithUnitOfWork(uow =>
{
var invoiceRepository = uow.InvoiceRepository;
var returnList = invoiceRepository.ReadPurchaseInvoiceLineSummary(maxDate, lineStatus, wordSearchList);
return returnList;
});
}
}
}

View File

@@ -1,5 +1,6 @@
using Amazon.SQS.Model.Internal.MarshallTransformations;
using bnhtrade.Core.Data.Database.UnitOfWork;
using bnhtrade.Core.Model.Account;
using bnhtrade.Core.Model.Amazon;
using bnhtrade.Core.Test.Export;
using FikaAmazonAPI.ReportGeneration.ReportDataTable;
@@ -17,24 +18,18 @@ using static bnhtrade.Core.Model.Import.AmazonSettlement;
namespace bnhtrade.Core.Logic.Export.AccountInvoice
{
internal class AmazonSettlement : Data.Database.Connection
internal class AmazonSettlement : UnitOfWorkBase
{
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)
internal AmazonSettlement(IUnitOfWork unitOfWork) : base(unitOfWork)
{
_providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
_ownsUnitOfWork = false;
}
public string ErrorMessage { get; private set; }
@@ -53,34 +48,34 @@ namespace bnhtrade.Core.Logic.Export.AccountInvoice
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 WithUnitOfWork(uow =>
{
return false;
}
// get list of unprocessed settlement reports to export
var settlementList = GetUnprocessedSettlementReports(uow, true);
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 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;
}
// 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())
{
// add invoice to export queue and set settlements as processed
Console.Write("\rWriting to database... ");
try
{
var queueService = new Logic.Export.AccountInvoice.QueueService(unitOfWork);
var queueService = new Logic.Export.AccountInvoice.QueueService(uow);
// add temp invoice numbers
queueService.AddTempInvoiceNumber(invoiceList, true);
@@ -89,12 +84,15 @@ namespace bnhtrade.Core.Logic.Export.AccountInvoice
if (queueService.ErrorMessageIsSet == false && queueInsertResult.Count() == invoiceList.Count())
{
// set settlements to isprocessed
unitOfWork.ImportAmazonRepository.SetAmazonSettlementIsProcessed(settlementIdList, true);
unitOfWork.Commit();
uow.AmazonSettlementRepository.UpdateAmazonSettlementIsProcessed(settlementIdList, true);
uow.Commit();
Console.Write("\r");
_log.LogInformation("\rFinished processing of Amazon settlement data. " + invoiceList.Count() + " invoices created from " + settlementIdList.Count() + " Amazon settlement reports.");
return true;
}
else
{
unitOfWork.Rollback();
uow.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...");
@@ -109,115 +107,115 @@ namespace bnhtrade.Core.Logic.Export.AccountInvoice
_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>
/// <param name="updateNullMarketplaceByCurrency">Insert 'Amazon.co.uk' if the market place name is missing and the currecy is GBP</param>
/// <returns></returns>
private List<Model.Import.AmazonSettlement> GetListOfUnprocessedSettlementReports()
private List<Model.Import.AmazonSettlement> GetUnprocessedSettlementReports(IUnitOfWork uow, bool updateNullMarketplaceByCurrency = true)
{
List<Model.Import.AmazonSettlement> settlementList = null;
IUnitOfWork currentUow = null;
if (_ownsUnitOfWork)
// get list of unprocessed settlement reports to export
settlementList = uow.AmazonSettlementRepository.ReadAmazonSettlements(null, null, false).Values.ToList();
if (settlementList.Any() == false)
{
currentUow = new UnitOfWork();
}
else
{
currentUow = _providedUnitOfWork;
ErrorMessage = "No new settlement reports to process";
return null;
}
// get list of unprocssed settlement reports to export
using (currentUow != null && _ownsUnitOfWork ? currentUow : 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 or added below
// via currency (null marketplace anme is not picked up in validate stage as null value is valid)
foreach (var settlement in settlementList)
{
settlementList = currentUow.ImportAmazonRepository.ReadAmazonSettlements(null, null, false).Values.ToList();
if (settlementList.Any() == false)
if (settlement.MarketPlaceNameIsSet == false && updateNullMarketplaceByCurrency && settlement.CurrencyCode == CurrencyCode.GBP.ToString())
{
ErrorMessage = "No new settlement reports to process";
// update database with market place name
uow.AmazonSettlementRepository.UpdateAmazonSettlementMarketPlaceName(
settlement.SettlementId
, MarketPlaceEnum.AmazonUK
);
// add to settlement
settlement.MarketPlace = MarketPlaceEnum.AmazonUK;
}
else 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;
}
}
// 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)
// 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)
{
if (settlement.MarketPlaceNameIsSet == false)
// get previously completed settlement for this marketplace
var completedSettlement = uow.AmazonSettlementRepository.ReadAmazonSettlements(
null, new List<string> { settlementList[i].MarketPlace.GetMarketplaceUrl() }, true, true, 1);
if (completedSettlement.Any())
{
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)
if (completedSettlement.FirstOrDefault().Value.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")
+ 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
if (settlementList == null || settlementList.Any() == false)
{
_log.LogInformation("No new settlement reports to process.");
return null;
}
// validate settlements
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());
_log.LogError("Error processing Amazon Settlement data for export.", validate.ValidationResultListToString());
}
}
if (validate.IsValidResult == false)

View File

@@ -1,12 +0,0 @@
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

@@ -15,22 +15,17 @@ namespace bnhtrade.Core.Logic.Export.AccountInvoice
/// <summary>
/// Processes the Export Invoice table and exports to Xero
/// </summary>
public class QueueService
public class QueueService : UnitOfWorkBase
{
private Log.LogEvent _log = new Log.LogEvent();
private IEnumerable<int> _exportSaleInvoiceIdList = new List<int>();
private readonly IUnitOfWork _providedUnitOfWork = null;
private readonly bool _ownsUnitOfWork = false;
private UnitOfWork _exportUow = null;
public QueueService()
{
_ownsUnitOfWork = true;
}
internal QueueService(IUnitOfWork unitOfWork)
internal QueueService(IUnitOfWork unitOfWork) : base(unitOfWork)
{
_providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
_ownsUnitOfWork = false;
}
public string ErrorMessage { get; private set; } = null;
@@ -64,26 +59,11 @@ namespace bnhtrade.Core.Logic.Export.AccountInvoice
{
Init();
IUnitOfWork currentUow = null;
if (_ownsUnitOfWork)
{
currentUow = new UnitOfWork();
}
else
{
currentUow = _providedUnitOfWork;
}
string result = null;
using (currentUow != null && _ownsUnitOfWork ? currentUow : null)
return WithUnitOfWork(uow =>
{
result = "_tmp" + currentUow.SequenceGenerator.GetNext("ExportTempInvoiceNumber").ToString("00000000");
if (_ownsUnitOfWork)
{
currentUow.Commit();
}
}
return result;
return "_tmp" + uow.SequenceGenerator.GetNext("ExportTempInvoiceNumber").ToString("00000000");
});
}
public void AddTempInvoiceNumber(IEnumerable<Model.Account.IInvoice> invoiceList, bool overwriteExisting)
@@ -110,30 +90,26 @@ namespace bnhtrade.Core.Logic.Export.AccountInvoice
{
Init();
IUnitOfWork currentUow = null;
if (_ownsUnitOfWork)
return WithUnitOfWork(uow =>
{
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());
return ReadInvoiceById(uow, invoiceIdList);
});
}
if (isValid == false)
{
ErrorMessage = "Reading invoices from database failed validation. See logs for further details.";
_log.LogError("ErrorMessage", validate.ValidationResultListToString());
throw new Exception(ErrorMessage);
}
private Dictionary<int, Model.Account.SalesInvoice> ReadInvoiceById(IUnitOfWork uow, IEnumerable<int> invoiceIdList)
{
var returnList = uow.ExportInvoiceRepository.GetSalesInvoiceById(invoiceIdList);
var validate = new Logic.Validate.Invoice();
bool isValid = validate.IsValidExportInvoice(returnList.Values.ToList());
return returnList;
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>
@@ -158,25 +134,12 @@ namespace bnhtrade.Core.Logic.Export.AccountInvoice
validateInvoice = null;
// save to database
IUnitOfWork currentUow = null;
if (_ownsUnitOfWork)
return WithUnitOfWork(uow =>
{
currentUow = new UnitOfWork();
}
else
{
currentUow = _providedUnitOfWork;
}
using (currentUow != null && _ownsUnitOfWork ? currentUow : null)
{
var result = currentUow.ExportInvoiceRepository.InsertSalesInvoices(invoiceList);
if (_ownsUnitOfWork)
{
currentUow.Commit();
}
var result = uow.ExportInvoiceRepository.InsertSalesInvoices(invoiceList);
CommitIfOwned(uow);
return result;
}
});
}
/// <summary>
@@ -187,25 +150,10 @@ namespace bnhtrade.Core.Logic.Export.AccountInvoice
{
Init();
IUnitOfWork currentUow = null;
if (_ownsUnitOfWork)
return WithUnitOfWork(uow =>
{
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;
}
return uow.ExportInvoiceRepository.GetNewInvoiceNumbers(invoiceType).Count();
});
}
/// <summary>
@@ -213,80 +161,45 @@ namespace bnhtrade.Core.Logic.Export.AccountInvoice
/// </summary>
/// <param name="filePath"></param>
/// <param name="firstInvoiceNumber"></param>
public void ExportSalesInvoice(string filePath, int firstInvoiceNumber)
public bool ExportSalesInvoice(string filePath, int firstInvoiceNumber, Func<bool> getUserConfirmation)
{
Init();
IUnitOfWork currentUow = null;
if (_ownsUnitOfWork)
{
currentUow = new UnitOfWork();
}
else
{
currentUow = _providedUnitOfWork;
}
using (currentUow != null && _ownsUnitOfWork ? currentUow : null)
return WithUnitOfWork(uow =>
{
var invoiceType = Model.Account.InvoiceType.Sale;
var idList = currentUow.ExportInvoiceRepository.GetNewInvoiceNumbers(invoiceType);
_exportSaleInvoiceIdList = idList.Keys.ToList();
var invoiceList = ReadInvoiceById(idList.Keys.ToList());
// get list of unprocessed invoices
var newInvoiceIdDict = uow.ExportInvoiceRepository.GetNewInvoiceNumbers(invoiceType);
// update db entries with invoice numbers and set to iscompleted=true
foreach (var newInvoiceId in newInvoiceIdDict)
{
string invoiceNumber = "INV-" + firstInvoiceNumber.ToString("000000");
uow.ExportInvoiceRepository.UpdateInvoiceHeaderDetail(newInvoiceId.Key, invoiceNumber, true);
firstInvoiceNumber++;
}
// read invoices from database
var invoiceList = ReadInvoiceById(uow, newInvoiceIdDict.Keys.ToList());
var exportToFile = new Data.Xero.SalesInvoice();
exportToFile.ExportToCsv(invoiceList.Values.ToList(), firstInvoiceNumber, filePath);
}
}
exportToFile.ExportToCsv(invoiceList.Values.ToList(), filePath);
/// <summary>
/// Call this after ExportSalesInvoice() to mark exported invoices as complete
/// </summary>
/// <returns>number of invoices effected</returns>
public int? ExportSalesInvoiceIsComplete()
{
Init();
// get user confitmation before marking as exported
bool userInput = getUserConfirmation.Invoke();
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)
if (userInput == false)
{
currentUow.Commit();
return count;
RollbackIfOwned(uow);
ErrorMessage = "User cancelled export, invoices not marked as exported.";
_log.LogInformation(ErrorMessage);
return false;
}
else
{
currentUow.Rollback();
ErrorMessage = "ExportSalesInvoiceIsComplete() Incorrect number of rows updated, changes rolled back.";
_log.LogError(ErrorMessage);
throw new Exception(ErrorMessage);
}
}
CommitIfOwned(uow);
return true;
});
}
}
}

View File

@@ -5,43 +5,26 @@ using System.Linq;
namespace bnhtrade.Core.Logic.Import
{
public class AmazonSettlement
public class AmazonSettlement : UnitOfWorkBase
{
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()
{
_ownsUnitOfWork = true;
}
internal AmazonSettlement(IUnitOfWork unitOfWork)
internal AmazonSettlement(IUnitOfWork unitOfWork) : base(unitOfWork)
{
_providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
_ownsUnitOfWork = false;
}
public void SyncDatabase()
{
string operation = "Import Amazon Settlement Reports";
log.LogInformation("Started '" + operation + "' operation.");
var amazonReport = new Data.Amazon.Report.SettlementReport();
// get avaiable reports from amazon api
IUnitOfWork currentUow = null;
if (_ownsUnitOfWork)
{
currentUow = new UnitOfWork();
}
else
{
currentUow = _providedUnitOfWork;
}
using (currentUow != null && _ownsUnitOfWork ? currentUow : null)
WithUnitOfWork(uow =>
{
// get avaiable reports from amazon api
var spapiReportIdList = amazonReport.ListAvaliableReports();
@@ -54,7 +37,7 @@ namespace bnhtrade.Core.Logic.Import
}
// query db and remove reports that have already been imported
var dbReportList = currentUow.ImportAmazonRepository.ReadAmazonSettlementHeaderInfoBySpapiReportId(spapiReportIdList);
var dbReportList = uow.AmazonSettlementRepository.ReadAmazonSettlementHeaderInfoBySpapiReportId(spapiReportIdList);
foreach (var dbReport in dbReportList)
{
if (dbReport.SpapiReportIdIsSet)
@@ -82,16 +65,12 @@ namespace bnhtrade.Core.Logic.Import
{
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]);
bool ack = uow.AmazonSettlementRepository.CreateAmazonSettlements(filePath, spapiReportIdList[i]);
log.LogInformation("Settlment Report imported (ReportID:" + spapiReportIdList[i] + ").");
}
if (_ownsUnitOfWork)
{
currentUow.Commit();
}
return;
}
CommitIfOwned(uow);
});
}
}
}

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Transactions;
using System.Windows.Forms;
namespace bnhtrade.Core.Logic.Sku
{
public class GetSkuId
public class SkuService : UnitOfWorkBase
{
/// <summary>
/// Used for retriving an SKU ID by parameters. If no match, can create a new SKU if required.
@@ -15,11 +17,11 @@ namespace bnhtrade.Core.Logic.Sku
/// <param name="noMatchCreateNew">When set to true, if no match is made, function will create a new sku and return the id for that</param>
/// <returns>Return the id for the new or existing sku or, dependant on set parameters, 0 if not found</returns>
/// <exception cref="Exception"></exception>
public int Request(int productId, int conditionId, int accountTaxCodeId, bool noMatchCreateNew)
public int GetSkuId(int productId, int conditionId, int accountTaxCodeId, bool noMatchCreateNew)
{
using (TransactionScope scope = new TransactionScope())
return WithUnitOfWork(uow =>
{
int? skuId = new Data.Database.Sku.GetSkuId().ByParameters(productId, conditionId, accountTaxCodeId);
int? skuId = uow.SkuRepository.ReadSkuId(productId, conditionId, accountTaxCodeId);
if (skuId != null)
{
return (int)skuId;
@@ -30,9 +32,11 @@ namespace bnhtrade.Core.Logic.Sku
}
else
{
return new Data.Database.Sku.InsertSku().InsertNew(productId, conditionId, accountTaxCodeId);
int newSkuId = uow.SkuRepository.InsertNewSku(productId, conditionId, accountTaxCodeId);
CommitIfOwned(uow);
return newSkuId;
}
}
});
}
}
}

View File

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace bnhtrade.Core.Logic.Stock
{
public class StatusBalance
public class StatusBalance : UnitOfWorkBase
{
public StatusBalance()
{
@@ -35,91 +35,93 @@ namespace bnhtrade.Core.Logic.Stock
/// <returns></returns>
public Model.Stock.StatusBalance GetStatusBalance(string skuNumber, int statusId, bool includeDetails = true)
{
if (string.IsNullOrWhiteSpace(skuNumber))
// need to enroll moree of the code into the unit of work as I redo the database layer
return WithUnitOfWork(uow =>
{
throw new Exception("SKU number is null, empty, or whitespace");
}
// get list of transactions for availale stock
var readStatusTransaction = new Data.Database.Stock.ReadStatusTransaction();
var statusTransaction = readStatusTransaction.BySku(statusId, skuNumber);
// create quantity list
List<int> qtyList = new List<int>();
for (int i = 0; i < statusTransaction.TransactionList.Count; i++)
{
qtyList.Add(statusTransaction.TransactionList[i].Quantity);
}
// tally list
// loop, in reverse, to find credits to tally with debits
for (int iCr = qtyList.Count - 1; iCr > -1; iCr--)
{
if (qtyList[iCr] < 0)
if (string.IsNullOrWhiteSpace(skuNumber))
{
int crStockNumber = statusTransaction.TransactionList[iCr].StockNumber;
DateTime crDate = statusTransaction.TransactionList[iCr].EntryDate;
throw new Exception("SKU number is null, empty, or whitespace");
}
// loop, in reverse, to find debits
for (int iDr = qtyList.Count - 1; iDr > -1; iDr--)
// get list of transactions for availale stock
var readStatusTransaction = new Data.Database.Stock.ReadStatusTransaction(); // ambient uow open above
var statusTransaction = readStatusTransaction.BySku(statusId, skuNumber); // ambient uow open above
// create quantity list
List<int> qtyList = new List<int>();
for (int i = 0; i < statusTransaction.TransactionList.Count; i++)
{
qtyList.Add(statusTransaction.TransactionList[i].Quantity);
}
// tally list
// loop, in reverse, to find credits to tally with debits
for (int iCr = qtyList.Count - 1; iCr > -1; iCr--)
{
if (qtyList[iCr] < 0)
{
// find debits, last in first out (filter by date)
if (statusTransaction.TransactionList[iDr].StockNumber == crStockNumber
&& statusTransaction.TransactionList[iDr].EntryDate <= crDate
&& qtyList[iDr] > 0)
int crStockNumber = statusTransaction.TransactionList[iCr].StockNumber;
DateTime crDate = statusTransaction.TransactionList[iCr].EntryDate;
// loop, in reverse, to find debits
for (int iDr = qtyList.Count - 1; iDr > -1; iDr--)
{
// credit fully assigned
if ((qtyList[iCr] + qtyList[iDr]) >= 0)
// find debits, last in first out (filter by date)
if (statusTransaction.TransactionList[iDr].StockNumber == crStockNumber
&& statusTransaction.TransactionList[iDr].EntryDate <= crDate
&& qtyList[iDr] > 0)
{
qtyList[iDr] = qtyList[iDr] + qtyList[iCr];
qtyList[iCr] = 0;
break;
}
// credit partially assigned
else
{
qtyList[iCr] = qtyList[iDr] + qtyList[iCr];
qtyList[iDr] = 0;
// credit fully assigned
if ((qtyList[iCr] + qtyList[iDr]) >= 0)
{
qtyList[iDr] = qtyList[iDr] + qtyList[iCr];
qtyList[iCr] = 0;
break;
}
// credit partially assigned
else
{
qtyList[iCr] = qtyList[iDr] + qtyList[iCr];
qtyList[iDr] = 0;
}
}
}
}
}
}
// build result list from tally results
var entryList = new List<(DateTime EntryDate, int StockNumber, int Quantity)>();
for (int i = 0; i < qtyList.Count; i++)
{
if (qtyList[i] != 0)
// build result list from tally results
var entryList = new List<(DateTime EntryDate, int StockNumber, int Quantity)>();
for (int i = 0; i < qtyList.Count; i++)
{
(DateTime EntryDate, int StockNumber, int Quantity) entryItem = (
statusTransaction.TransactionList[i].EntryDate,
statusTransaction.TransactionList[i].StockNumber,
qtyList[i]);
entryList.Add(entryItem);
if (qtyList[i] != 0)
{
(DateTime EntryDate, int StockNumber, int Quantity) entryItem = (
statusTransaction.TransactionList[i].EntryDate,
statusTransaction.TransactionList[i].StockNumber,
qtyList[i]);
entryList.Add(entryItem);
}
}
}
if (includeDetails)
{
// get the sku obj
var readSku = new Logic.Sku.GetSkuInfo();
readSku.IncludeConditionInfo = true;
readSku.IncludeProductInfo = true;
readSku.IncludeTaxCodeInfo = true;
var sku = readSku.BySkuNumber(statusTransaction.SkuNumber);
if (includeDetails)
{
// get the sku obj
var readSku = new Logic.Sku.GetSkuInfo(); // ambient uow open above
readSku.IncludeConditionInfo = true;
readSku.IncludeProductInfo = true;
readSku.IncludeTaxCodeInfo = true;
var sku = readSku.BySkuNumber(statusTransaction.SkuNumber);
// get the status obj
var readStatus = new Data.Database.Stock.Status();
readStatus.StatusIds = new List<int> { statusTransaction.StockStatusId };
var status = readStatus.Read()[0];
// get the status obj
var status = uow.StockRepository.ReadStatus(new List<int> { statusTransaction.StockStatusId })[0];
return new Model.Stock.StatusBalance(status, sku, entryList);
}
else
{
return new Model.Stock.StatusBalance(statusTransaction.StockStatusId, statusTransaction.SkuNumber, entryList);
}
return new Model.Stock.StatusBalance(status, sku, entryList);
}
else
{
return new Model.Stock.StatusBalance(statusTransaction.StockStatusId, statusTransaction.SkuNumber, entryList);
}
});
}
/// <summary>

View File

@@ -0,0 +1,76 @@
using bnhtrade.Core.Data.Database.UnitOfWork;
using System;
namespace bnhtrade.Core.Logic
{
public abstract class UnitOfWorkBase
{
private readonly IUnitOfWork _providedUnitOfWork;
private readonly bool _ownsUnitOfWork;
protected UnitOfWorkBase()
{
_ownsUnitOfWork = true;
}
internal UnitOfWorkBase(IUnitOfWork unitOfWork)
{
_providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
_ownsUnitOfWork = false;
}
/// <summary>
/// Executes an action with a managed UnitOfWork, handling disposal and commit if owned.
/// </summary>
internal void WithUnitOfWork(Action<IUnitOfWork> action)
{
IUnitOfWork currentUow = _ownsUnitOfWork ? new UnitOfWork() : _providedUnitOfWork;
try
{
action(currentUow);
}
finally
{
if (_ownsUnitOfWork && currentUow is IDisposable disposable)
disposable.Dispose();
}
}
/// <summary>
/// Executes a function with a managed UnitOfWork, handling disposal and commit if owned.
/// </summary>
internal T WithUnitOfWork<T>(Func<IUnitOfWork, T> func)
{
IUnitOfWork currentUow = _ownsUnitOfWork ? new UnitOfWork() : _providedUnitOfWork;
try
{
return func(currentUow);
}
finally
{
if (_ownsUnitOfWork && currentUow is IDisposable disposable)
disposable.Dispose();
}
}
/// <summary>
/// Executes an action with a managed UnitOfWork, committing if owned.
/// </summary>
/// <param name="uow"></param>
internal void CommitIfOwned(IUnitOfWork uow)
{
if (_ownsUnitOfWork)
uow.Commit();
}
/// <summary>
/// Executes an action with a managed UnitOfWork, rolling back if owned.
/// </summary>
/// <param name="uow"></param>
internal void RollbackIfOwned(IUnitOfWork uow)
{
if (_ownsUnitOfWork)
uow.Rollback();
}
}
}

View File

@@ -1,53 +0,0 @@
using bnhtrade.Core.Data.Database.UnitOfWork;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Logic._BoilerPlate
{
internal class UnitOfWorkPattern
{
private readonly IUnitOfWork _providedUnitOfWork = null;
private readonly bool _ownsUnitOfWork = false;
public UnitOfWorkPattern()
{
_ownsUnitOfWork = true;
}
internal UnitOfWorkPattern(IUnitOfWork unitOfWork)
{
_providedUnitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
_ownsUnitOfWork = false;
}
public void LogicThatUsesService()
{
IUnitOfWork currentUow = null;
if (_ownsUnitOfWork)
{
currentUow = new UnitOfWork();
}
else
{
currentUow = _providedUnitOfWork;
}
using (currentUow != null && _ownsUnitOfWork ? currentUow : null)
{
//
// Perform operations using currentUow, e.g. repository calls
//
if (_ownsUnitOfWork)
{
currentUow.Commit();
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
using bnhtrade.Core.Data.Database.UnitOfWork;
using System;
using System.Collections.Generic;
namespace bnhtrade.Core.Logic._boilerplate
{
public class UnitOfWorkService : UnitOfWorkBase
{
public UnitOfWorkService() : base() { }
internal UnitOfWorkService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
public Model.Account.InvoiceLineItem ReturnExample(string itemCode)
{
return WithUnitOfWork(uow =>
{
// add all code in here that uses the unit of work, this will ensure that the unit of work is disposed of correctly
var item = uow.InvoiceRepository.GetInvoiceLineItem(null, new List<string> { itemCode });
// use the base method to commit, it will check if the unit of work is owned by this service before committing
CommitIfOwned(uow);
// return
return item[0];
});
}
public void VoidExample()
{
WithUnitOfWork(uow =>
{
var items = uow.InvoiceRepository.GetInvoiceLineItem();
// use the base method to commit, it will check if the unit of work is owned by this service before committing
CommitIfOwned(uow);
});
}
}
}