mirror of
https://github.com/stokebob/bnhtrade.git
synced 2026-03-19 06:27:15 +00:00
Various bug fixs and improvements to stock SKU reconciliation
This commit is contained in:
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Transactions;
|
||||
|
||||
namespace bnhtrade.Core.Logic.Stock
|
||||
{
|
||||
public class Reallocate
|
||||
{
|
||||
private string sqlConnectionString;
|
||||
|
||||
public Reallocate(string sqlConnectionString)
|
||||
{
|
||||
this.sqlConnectionString = sqlConnectionString;
|
||||
}
|
||||
|
||||
public int StockReallocateByStockId(int journalTypeId, int stockId, int quantity, int debitStatusId, int creditStatusId,
|
||||
DateTime entryDate = default(DateTime))
|
||||
{
|
||||
if (entryDate == default(DateTime))
|
||||
{
|
||||
entryDate = DateTime.Now;
|
||||
}
|
||||
|
||||
// create the list
|
||||
var posts = new List<(int statusId, int quantity)>();
|
||||
posts.Add((debitStatusId, quantity));
|
||||
posts.Add((creditStatusId, (quantity * -1)));
|
||||
|
||||
// execute
|
||||
return Core.Stock.StockJournal.StockJournalInsert(sqlConnectionString, journalTypeId, stockId, posts, entryDate, false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Feed an skuId and quantity into function and the stock will be reallocated
|
||||
/// </summary>
|
||||
public List<(int StockJournalId, int Quantity)> StockReallocateBySkuNumber(int journalTypeId, string skuNumber, int quantity, int debitStatusId, int creditStatusId,
|
||||
bool firstInFirstOut = true, DateTime entryDate = default(DateTime), bool reallocatePartialQuantity = false)
|
||||
{
|
||||
var returnList = new List<(int StockJournalId, int Quantity)>();
|
||||
|
||||
List<Tuple<int, DateTime, int>> list = Core.Stock.StockJournal.GetStockStatusBalanceBySkuNumber(sqlConnectionString, skuNumber, creditStatusId, entryDate, firstInFirstOut);
|
||||
|
||||
if (list == null || !list.Any())
|
||||
{
|
||||
return returnList;
|
||||
}
|
||||
|
||||
// quantity check
|
||||
int avaiableQuantity = 0;
|
||||
foreach (Tuple<int, DateTime, int> item in list)
|
||||
{
|
||||
avaiableQuantity = avaiableQuantity + item.Item3;
|
||||
}
|
||||
if (avaiableQuantity < quantity && reallocatePartialQuantity == false)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// make the changes
|
||||
using (TransactionScope scope = new TransactionScope())
|
||||
{
|
||||
foreach (Tuple<int, DateTime, int> item in list)
|
||||
{
|
||||
if (quantity > item.Item3)
|
||||
{
|
||||
int tempInt = StockReallocateByStockId(journalTypeId, item.Item1, item.Item3, debitStatusId, creditStatusId, entryDate);
|
||||
quantity = quantity - item.Item3;
|
||||
returnList.Add((tempInt, item.Item3));
|
||||
}
|
||||
else
|
||||
{
|
||||
int tempInt = StockReallocateByStockId(journalTypeId, item.Item1, quantity, debitStatusId, creditStatusId, entryDate);
|
||||
returnList.Add((tempInt, quantity));
|
||||
break;
|
||||
}
|
||||
}
|
||||
scope.Complete();
|
||||
return returnList;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,26 @@ namespace bnhtrade.Core.Logic.Stock
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrive SKU Transaction by ID
|
||||
/// </summary>
|
||||
/// <param name="SkuTransactionId">SKU Transaction ID</param>
|
||||
/// <param name="retriveTransactionTypeInfo"></param>
|
||||
/// <returns></returns>
|
||||
public List<Model.Stock.SkuTransaction> Read(List<int> SkuTransactionId, bool retriveTransactionTypeInfo = true)
|
||||
{
|
||||
var dbRead = new Data.Database.Stock.ReadSkuTransaction(sqlConnectionString);
|
||||
var resultList = dbRead.Read(SkuTransactionId);
|
||||
|
||||
if (retriveTransactionTypeInfo)
|
||||
{
|
||||
var dbReadType = new Logic.Stock.SkuTransactionTypePersistance(sqlConnectionString);
|
||||
dbReadType.GetBySkuTransaction(resultList);
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates and then updates a Stock SKU Tranaction
|
||||
/// </summary>
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace bnhtrade.Core.Logic.Stock
|
||||
private Logic.Stock.SkuTransactionPersistance dbSkuTransaction;
|
||||
private Logic.Stock.SkuTransactionTypePersistance dbSkuTransactionType;
|
||||
private Logic.Validate.SkuTransaction validateSkuTrans;
|
||||
private Logic.Stock.Reallocate stockReallocate;
|
||||
private Logic.Stock.StatusReallocate stockReallocate;
|
||||
private Logic.Log.LogEvent logEvent;
|
||||
private string err = "Reconcile Sku Transaction Exception: ";
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace bnhtrade.Core.Logic.Stock
|
||||
dbSkuTransactionType = new SkuTransactionTypePersistance(sqlConnectionString);
|
||||
readShipmentInfo = new Data.Database.AmazonShipment.ReadShipmentInfo(sqlConnectionString);
|
||||
validateSkuTrans = new Validate.SkuTransaction();
|
||||
stockReallocate = new Logic.Stock.Reallocate(sqlConnectionString);
|
||||
stockReallocate = new Logic.Stock.StatusReallocate(sqlConnectionString);
|
||||
logEvent = new Log.LogEvent();
|
||||
}
|
||||
|
||||
@@ -194,50 +194,52 @@ namespace bnhtrade.Core.Logic.Stock
|
||||
}
|
||||
|
||||
// make the journal entries
|
||||
var list = new List<(int StockJournalId, int Quantity)>();
|
||||
var journalList = new List<(int StockJournalId, int Quantity)>();
|
||||
if (transList[i].SkuTransactionType.FilterStockOnDateTime)
|
||||
{
|
||||
list = stockReallocate.StockReallocateBySkuNumber(
|
||||
journalList = stockReallocate.BySkuNumber(
|
||||
transList[i].TransactionDate,
|
||||
transList[i].SkuTransactionType.StockJournalTypeId,
|
||||
transList[i].SkuNumber,
|
||||
transList[i].Quantity,
|
||||
transList[i].SkuTransactionType.DebitStockStatusId.GetValueOrDefault(),
|
||||
transList[i].SkuTransactionType.CreditStockStatusId.GetValueOrDefault(),
|
||||
transList[i].SkuTransactionType.FirstInFirstOut,
|
||||
transList[i].TransactionDate,
|
||||
false);
|
||||
true);
|
||||
}
|
||||
else
|
||||
{
|
||||
list = stockReallocate.StockReallocateBySkuNumber(
|
||||
journalList = stockReallocate.BySkuNumber(
|
||||
DateTime.UtcNow,
|
||||
transList[i].SkuTransactionType.StockJournalTypeId,
|
||||
transList[i].SkuNumber,
|
||||
transList[i].Quantity,
|
||||
transList[i].SkuTransactionType.DebitStockStatusId.GetValueOrDefault(),
|
||||
transList[i].SkuTransactionType.CreditStockStatusId.GetValueOrDefault(),
|
||||
transList[i].SkuTransactionType.FirstInFirstOut,
|
||||
DateTime.UtcNow,
|
||||
false);
|
||||
true);
|
||||
}
|
||||
|
||||
// insufficient balance available
|
||||
if (list == null || !list.Any())
|
||||
if (!journalList.Any())
|
||||
{
|
||||
// in special case (found inventory), continue
|
||||
if (transList[i].SkuTransactionType.TypeCode.Contains("<AmazonReport><_GET_FBA_FULFILLMENT_INVENTORY_ADJUSTMENTS_DATA_><F>"))
|
||||
{
|
||||
ItemsCompleted++;
|
||||
ItemsRemaining--;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgressMessage = "Insurficent status/location balance to relocate stock";
|
||||
ProgressMessage = "Insurficent quantity at status/location to relocate stock";
|
||||
recordSkip = recordSkip + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// fail safe
|
||||
int qtyAllocated = list.Sum(c => c.Quantity);
|
||||
int qtyAllocated = journalList.Sum(c => c.Quantity);
|
||||
if (qtyAllocated > transList[i].Quantity)
|
||||
{
|
||||
throw new Exception(
|
||||
@@ -248,40 +250,41 @@ namespace bnhtrade.Core.Logic.Stock
|
||||
// update sku transaction table
|
||||
int qtyRemain = qtyAllocated;
|
||||
var newRecordList = new List<Model.Stock.SkuTransaction>();
|
||||
for (int j = 0; j <= list.Count; j++)
|
||||
for (int j = 0; j < journalList.Count; j++)
|
||||
{
|
||||
// update existing record
|
||||
if (j == 0)
|
||||
{
|
||||
transList[i].Quantity = (short)list[j].Quantity;
|
||||
transList[i].StockJournalId = list[j].StockJournalId;
|
||||
transList[i].Quantity = (short)journalList[j].Quantity;
|
||||
transList[i].StockJournalId = journalList[j].StockJournalId;
|
||||
transList[i].IsProcessed = true;
|
||||
|
||||
dbSkuTransaction.Update(transList[i]);
|
||||
}
|
||||
// new record
|
||||
else if (j < list.Count)
|
||||
else
|
||||
{
|
||||
var newRecord = transList[i].Clone();
|
||||
newRecord.Quantity = (short)list[j].Quantity;
|
||||
newRecord.Quantity = (short)journalList[j].Quantity;
|
||||
newRecord.IsProcessed = true;
|
||||
newRecord.StockJournalId = list[j].StockJournalId;
|
||||
newRecordList.Add(newRecord);
|
||||
}
|
||||
// new record, unallocated quantity
|
||||
else if (qtyRemain > 0)
|
||||
{
|
||||
var newRecord = transList[i].Clone();
|
||||
newRecord.Quantity = (short)qtyRemain;
|
||||
newRecord.IsProcessed = false;
|
||||
newRecord.StockJournalId = journalList[j].StockJournalId;
|
||||
|
||||
newRecordList.Add(newRecord);
|
||||
}
|
||||
|
||||
if (j < list.Count)
|
||||
{
|
||||
qtyRemain = qtyRemain - list[j].Quantity;
|
||||
}
|
||||
qtyRemain = qtyRemain - journalList[j].Quantity;
|
||||
}
|
||||
|
||||
// new record for unallocated quantity
|
||||
if (qtyRemain > 0)
|
||||
{
|
||||
var newRecord = transList[i].Clone();
|
||||
newRecord.Quantity = (short)qtyRemain;
|
||||
newRecord.IsProcessed = false;
|
||||
|
||||
newRecordList.Add(newRecord);
|
||||
}
|
||||
|
||||
// add new transactions to table
|
||||
for (int j = 0; j < newRecordList.Count; j++)
|
||||
{
|
||||
@@ -448,7 +451,18 @@ namespace bnhtrade.Core.Logic.Stock
|
||||
|
||||
public void UnReconcileTransaction(int skuTransactionId)
|
||||
{
|
||||
dbSkuTransaction.DeleteJournalEntry(skuTransactionId);
|
||||
var trans = dbSkuTransaction.Read(new List<int> { skuTransactionId }, false).FirstOrDefault();
|
||||
if (trans == null) { return; }
|
||||
|
||||
// test if journal entry needs deleting, or just set to isprocessed = false
|
||||
if (trans.IsProcessed == true && trans.IsSetStockJournalId)
|
||||
{
|
||||
dbSkuTransaction.DeleteJournalEntry(skuTransactionId);
|
||||
}
|
||||
else if (trans.IsProcessed == true)
|
||||
{
|
||||
new Data.Database.Stock.UpdateSkuTransaction(sqlConnectionString).Update(skuTransactionId, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
111
src/bnhtrade.Core/Logic/Stock/StatusBalance.cs
Normal file
111
src/bnhtrade.Core/Logic/Stock/StatusBalance.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bnhtrade.Core.Logic.Stock
|
||||
{
|
||||
public class StatusBalance
|
||||
{
|
||||
private string sqlConnectionString;
|
||||
|
||||
public StatusBalance(string sqlConnectionString)
|
||||
{
|
||||
this.sqlConnectionString = sqlConnectionString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the avaliable balance of a status. Uses a more efficent sql/code. However, balance requests should
|
||||
/// generally also involve a date/time (i.e. the system does allow a stock transaction in the future, therefore
|
||||
/// this method may give an available quantity, but transfers before that date wouod not be possible).
|
||||
/// </summary>
|
||||
/// <param name="sku">SKU number</param>
|
||||
/// <param name="statusId">Status ID</param>
|
||||
/// <returns>Balance as quantity</returns>
|
||||
private int GetAvailableBalanceBySku(string sku, int statusId)
|
||||
{
|
||||
return new Data.Database.Stock.ReadStatusBalance(sqlConnectionString).BySku(sku, statusId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the avaliable balance of a status at a specified date and time. Useful for checking availability before
|
||||
/// moving stock/sku retrospectivly
|
||||
/// </summary>
|
||||
/// <param name="sku">SKU number</param>
|
||||
/// <param name="statusId">Status ID</param>
|
||||
/// <param name="atDate">Date and time you would like to know the balance at</param>
|
||||
/// <returns></returns>
|
||||
public Model.Stock.StatusBalance GetBySku(string sku, int statusId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sku))
|
||||
{
|
||||
throw new Exception("SKU number is null, empty, or whitespace");
|
||||
}
|
||||
|
||||
// get list of transactions for availale stock
|
||||
var stockTransaction = new Data.Database.Stock.ReadStatusTransaction(sqlConnectionString);
|
||||
var transList = stockTransaction.BySku(statusId, sku);
|
||||
|
||||
// create quantity list
|
||||
List<int> qtyList = new List<int>();
|
||||
for (int i = 0; i < transList.TransactionList.Count; i++)
|
||||
{
|
||||
qtyList.Add(transList.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)
|
||||
{
|
||||
int crStockNumber = transList.TransactionList[iCr].StockNumber;
|
||||
DateTime crDate = transList.TransactionList[iCr].EntryDate;
|
||||
|
||||
// loop, in reverse, to find debits
|
||||
for (int iDr = qtyList.Count - 1; iDr > -1; iDr--)
|
||||
{
|
||||
// find debits, last in first out (filter by date)
|
||||
if (transList.TransactionList[iDr].StockNumber == crStockNumber
|
||||
&& transList.TransactionList[iDr].EntryDate <= crDate
|
||||
&& 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 result = new Model.Stock.StatusBalance();
|
||||
result.Sku = transList.Sku;
|
||||
result.StockStatusId = transList.StockStatusId;
|
||||
|
||||
for (int i = 0; i < qtyList.Count; i++)
|
||||
{
|
||||
if (qtyList[i] != 0)
|
||||
{
|
||||
result.AddBalanceTransaction(
|
||||
transList.TransactionList[i].EntryDate,
|
||||
transList.TransactionList[i].StockNumber,
|
||||
qtyList[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
109
src/bnhtrade.Core/Logic/Stock/StatusReallocate.cs
Normal file
109
src/bnhtrade.Core/Logic/Stock/StatusReallocate.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Transactions;
|
||||
|
||||
namespace bnhtrade.Core.Logic.Stock
|
||||
{
|
||||
public class StatusReallocate
|
||||
{
|
||||
private string sqlConnectionString;
|
||||
|
||||
public StatusReallocate(string sqlConnectionString)
|
||||
{
|
||||
this.sqlConnectionString = sqlConnectionString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reallocates stock between status' by Stock Id
|
||||
/// </summary>
|
||||
/// <param name="journalTypeId"></param>
|
||||
/// <param name="stockId"></param>
|
||||
/// <param name="quantity"></param>
|
||||
/// <param name="debitStatusId"></param>
|
||||
/// <param name="creditStatusId"></param>
|
||||
/// <param name="entryDate"></param>
|
||||
/// <returns>Return newly created stock journal Id</returns>
|
||||
public int ByStockId(DateTime entryDate, int journalTypeId, int stockId, int quantity, int debitStatusId, int creditStatusId)
|
||||
{
|
||||
if (entryDate == default(DateTime))
|
||||
{
|
||||
entryDate = DateTime.Now;
|
||||
}
|
||||
|
||||
// create the list
|
||||
var posts = new List<(int statusId, int quantity)>();
|
||||
posts.Add((debitStatusId, quantity));
|
||||
posts.Add((creditStatusId, (quantity * -1)));
|
||||
|
||||
// execute
|
||||
return Core.Stock.StockJournal.StockJournalInsert(sqlConnectionString, journalTypeId, stockId, posts, entryDate, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Feed an SKU number and quantity into function and the stock will be reallocated
|
||||
/// </summary>
|
||||
/// <param name="entryDate">Date and time of the transaction</param>
|
||||
/// <param name="journalTypeId">Journal Type ID</param>
|
||||
/// <param name="skuNumber">Sku Number to reallocate</param>
|
||||
/// <param name="quantity">Quantity to reallocate</param>
|
||||
/// <param name="debitStatusId">Status to move SKU to</param>
|
||||
/// <param name="creditStatusId">Status to move SKU from</param>
|
||||
/// <param name="firstInFirstOut">Move stock on first in first out basis</param>
|
||||
/// <param name="reallocatePartialQuantity">Reallocate patial quantity if the full quantity is not available</param>
|
||||
/// <returns></returns>
|
||||
public List<(int StockJournalId, int Quantity)> BySkuNumber(DateTime entryDate, int journalTypeId, string skuNumber, int quantity, int debitStatusId, int creditStatusId,
|
||||
bool firstInFirstOut = true, bool reallocatePartialQuantity = false)
|
||||
{
|
||||
var returnList = new List<(int StockJournalId, int Quantity)>();
|
||||
|
||||
// get balance of status and check for avaliable quantity
|
||||
var statusBalance = new Logic.Stock.StatusBalance(sqlConnectionString).GetBySku(skuNumber, creditStatusId);
|
||||
|
||||
if (statusBalance.GetAvaliableQuantity(entryDate) <= 0
|
||||
|| (statusBalance.CheckAvaliableQuantity(quantity, entryDate) == false && reallocatePartialQuantity == false
|
||||
))
|
||||
{
|
||||
return returnList;
|
||||
}
|
||||
|
||||
// temp code start
|
||||
// until use of stockId is designed out of application
|
||||
var getStockId = new Data.Database.Stock.ReadStockId(sqlConnectionString);
|
||||
var stockIdDictionary = new Dictionary<int, int>();
|
||||
foreach (var item in statusBalance.ByDateList)
|
||||
{
|
||||
if (!stockIdDictionary.ContainsKey(item.StockNumber))
|
||||
{
|
||||
stockIdDictionary.Add(item.StockNumber, 0);
|
||||
}
|
||||
}
|
||||
stockIdDictionary = getStockId.ByStockNumber(stockIdDictionary.Keys.ToList());
|
||||
// temp code finish
|
||||
|
||||
//make the changes
|
||||
using (TransactionScope scope = new TransactionScope())
|
||||
{
|
||||
foreach (var item in statusBalance.ByDateList)
|
||||
{
|
||||
if (quantity > item.Quantity)
|
||||
{
|
||||
int journalId = ByStockId(entryDate, journalTypeId, stockIdDictionary[item.StockNumber], item.Quantity, debitStatusId, creditStatusId);
|
||||
quantity = quantity - item.Quantity;
|
||||
returnList.Add((journalId, item.Quantity));
|
||||
}
|
||||
else
|
||||
{
|
||||
int journalId = ByStockId(entryDate, journalTypeId, stockIdDictionary[item.StockNumber], quantity, debitStatusId, creditStatusId);
|
||||
returnList.Add((journalId, quantity));
|
||||
break;
|
||||
}
|
||||
}
|
||||
scope.Complete();
|
||||
return returnList;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user