Feature repricing min max (#10)

amazon settlement import/export improvements
This commit is contained in:
2020-05-01 09:08:23 +01:00
committed by GitHub
parent 56647c7648
commit 43d61c2ef8
118 changed files with 7930 additions and 3021 deletions

View File

@@ -0,0 +1,86 @@
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;
}
}
}
}

View File

@@ -0,0 +1,167 @@
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 SkuTransactionPersistance
{
private string err = "Sku Transaction Persistance Error; ";
private string sqlConnectionString;
private Data.Database.Stock.DeleteSkuTransaction dbSkuTransDelete;
private Data.Database.Stock.CreateSkuTransaction dbSkuTransCreate;
private Data.Database.Stock.ReadSkuTransaction dbSkuTransRead;
private Data.Database.Stock.UpdateSkuTransaction dbSkuTransUpdate;
private Logic.Validate.SkuTransaction validateSkuTrans;
private Logic.Log.LogEvent log;
public SkuTransactionPersistance(string sqlConnectionString)
{
this.sqlConnectionString = sqlConnectionString;
log = new Log.LogEvent();
}
private Data.Database.Stock.DeleteSkuTransaction DatabaseSkuTransDelete(bool forceNew = false)
{
if (dbSkuTransDelete == null || forceNew)
{
dbSkuTransDelete = new Data.Database.Stock.DeleteSkuTransaction(sqlConnectionString);
}
return dbSkuTransDelete;
}
private Data.Database.Stock.CreateSkuTransaction DatabaseSkuTransInsert(bool forceNew = false)
{
if (dbSkuTransCreate == null || forceNew)
{
dbSkuTransCreate = new Data.Database.Stock.CreateSkuTransaction(sqlConnectionString);
}
return dbSkuTransCreate;
}
private Data.Database.Stock.ReadSkuTransaction DatabaseSkuTransRead(bool forceNew = false)
{
if (dbSkuTransRead == null || forceNew)
{
dbSkuTransRead = new Data.Database.Stock.ReadSkuTransaction(sqlConnectionString);
}
return dbSkuTransRead;
}
private Data.Database.Stock.UpdateSkuTransaction DatabaseSkuTransUpdate(bool forceNew = false)
{
if (dbSkuTransUpdate == null || forceNew)
{
dbSkuTransUpdate = new Data.Database.Stock.UpdateSkuTransaction(sqlConnectionString);
}
return dbSkuTransUpdate;
}
private Logic.Validate.SkuTransaction Validate()
{
if (validateSkuTrans == null)
{
validateSkuTrans = new Validate.SkuTransaction();
}
return validateSkuTrans;
}
public void Delete(int skuTransactionId)
{
using (var scope = new TransactionScope())
{
try
{
// is there a journal entry attached?
int? journalId = DatabaseSkuTransRead().GetJournalId(skuTransactionId);
if (journalId != null)
{
Core.Stock.StockJournal.StockJournalDelete(sqlConnectionString, (int)journalId);
}
DatabaseSkuTransDelete().ByTransactionId(skuTransactionId);
scope.Complete();
}
catch (Exception ex)
{
scope.Dispose();
throw ex;
}
}
}
public void DeleteJournalEntry(int skuTransactionId)
{
using (var scope = new TransactionScope())
{
try
{
// is there a journal entry attached?
int? journalId = DatabaseSkuTransRead().GetJournalId(skuTransactionId);
if (journalId != null)
{
DatabaseSkuTransUpdate().Update(skuTransactionId, null);
Core.Stock.StockJournal.StockJournalDelete(sqlConnectionString, (int)journalId);
}
scope.Complete();
}
catch (Exception ex)
{
scope.Dispose();
throw ex;
}
}
}
public void Create(Model.Stock.SkuTransaction skuTransaction)
{
if (skuTransaction == null)
{
throw new Exception(err + "Object was null");
}
Validate().Innit();
if (!Validate().DatabaseInsert(skuTransaction))
{
log.LogWarning(err + "Validation failed", Validate().ValidationResultListToString());
throw new Exception(err + "Validation failed");
}
// write to database
DatabaseSkuTransInsert().Create(skuTransaction);
}
public void Update(Model.Stock.SkuTransaction skuTransaction)
{
if (skuTransaction == null)
{
throw new Exception(err + "Object was null");
}
Validate().Innit();
if (!Validate().DatabaseUpdate(skuTransaction))
{
log.LogWarning(err + "Validation failed", Validate().ValidationResultListToString());
throw new Exception(err + "Validation failed");
}
using (var scope = new TransactionScope())
{
// is there an existing journal id that is changing
int? journalId = DatabaseSkuTransRead().GetJournalId(skuTransaction.SkuTransactionId);
if (journalId != null && skuTransaction.IsSetStockJournalId)
{
if (journalId != skuTransaction.StockJournalId)
{
DeleteJournalEntry(skuTransaction.SkuTransactionId);
}
}
dbSkuTransUpdate.Update(skuTransaction);
scope.Complete();
}
}
}
}

View File

@@ -0,0 +1,398 @@
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 SkuTransactionReconcile
{
private string sqlConnectionString;
private Data.Database.AmazonShipment.ReadShipmentInfo readShipmentInfo;
private Logic.Stock.SkuTransactionPersistance dbSkuTransaction;
private Logic.Stock.SkuTransactionTypePersistance dbSkuTransactionType;
private Logic.Validate.SkuTransaction validateSkuTrans;
private Logic.Stock.Reallocate stockReallocate;
private Logic.Log.LogEvent logEvent;
private string err = "Reconcile Sku Transaction Exception: ";
public SkuTransactionReconcile(string sqlConnectionString)
{
Innit();
this.sqlConnectionString = sqlConnectionString;
dbSkuTransaction = new SkuTransactionPersistance(sqlConnectionString);
dbSkuTransactionType = new SkuTransactionTypePersistance(sqlConnectionString);
readShipmentInfo = new Data.Database.AmazonShipment.ReadShipmentInfo(sqlConnectionString);
validateSkuTrans = new Validate.SkuTransaction();
stockReallocate = new Logic.Stock.Reallocate(sqlConnectionString);
logEvent = new Log.LogEvent();
}
public int ItemsCompleted { get; private set; }
public int ItemsRemaining { get; private set; }
public DateTime LastItemDateTime { get; private set; }
public string ProgressMessage { get; private set; }
public bool ReconciliationComplete { get; private set; }
public int CurrentTransactionId { get; private set; }
public int CurrentTransactionTypeId { get; private set; }
public Model.Stock.SkuTransaction CurrentSkuTransaction { get; private set; }
private void Innit()
{
ItemsCompleted = 0;
ItemsRemaining = 0;
LastItemDateTime = default(DateTime);
ProgressMessage = null;
ReconciliationComplete = false;
CurrentTransactionId = 0;
CurrentTransactionTypeId = 0;
}
/// <summary>
/// Iterates through the stock transaction table and inserts stock journal entries, where applicable
/// N.B. This function does not make allowances for status' that can create stock (i.e. if a status does not have stock available, this function will halt processing rows)
/// </summary>
/// <param name="updateTransactions">Download and process Amazon reports before starting process</param>
/// <returns></returns>
public void ReconcileStockTransactions(bool updateTransactions)
{
Innit();
string currentMethodName = nameof(ReconcileStockTransactions);
// ensure import table have been processed into transaction table without exception
if (updateTransactions == true)
{
try
{
var preCheck = new bnhtrade.Core.Stock.StockReconciliation();
preCheck.ProcessFbaStockImportData(sqlConnectionString);
}
catch (Exception ex)
{
ProgressMessage = "Precheck failed: " + ex.Message;
return;
}
}
logEvent.LogInformation("Starting ReconcileStockTransactions()");
int recordSkip = 0;
ProcessLostAndFound();
// get list of sku transactions to reconcile
var transList = new Data.Database.Stock.ReadSkuTransaction(sqlConnectionString).GetUnreconciled();
ItemsRemaining = transList.Count;
ItemsCompleted = 0;
try
{
// loop through transaction list
for (int i = 0; i < transList.Count; i++)
{
using (var scope = new TransactionScope())
{
Console.Write("\rProcessing record: {0} ({1} skipped)", (i + 1 + recordSkip), recordSkip);
// setup return values
CurrentSkuTransaction = transList[i];
CurrentTransactionId = transList[i].SkuTransactionId;
CurrentTransactionTypeId = transList[i].SkuTransactionTypeId;
LastItemDateTime = transList[i].TransactionDate;
// load type into variable
var transType = dbSkuTransactionType.GetByTypeName(transList[i].SkuTransactionTypeName);
// stop if a new transactiontype is encountered
if (transType.IsNewReviewRequired)
{
ProgressMessage = "New 'Transaction-Type' encountered";
//Console.Write("\r");
//MiscFunction.EventLogInsert(errMessage, 1);
goto Stop;
}
// set debit/credit status' for special cases (i.e. NULL or 0 debit/credit ids in stockTransactionType table)
if (transType.StockJournalEntryEnabled == true && (transType.DebitStockStatusId == 0 || transType.CreditStockStatusId == 0))
{
// FBA Shipment Receipt +ve
if (transType.TypeCode == "<AmazonReport><_GET_FBA_FULFILLMENT_INVENTORY_RECEIPTS_DATA_><+ve>"
|| transType.TypeCode == "<AmazonReport><_GET_FBA_FULFILLMENT_INVENTORY_RECEIPTS_DATA_><-ve>")
{
var shipmentInfo = readShipmentInfo.HeaderByFbaShipmentId(transList[i].Reference);
if (shipmentInfo.IsSetShipmentStockStatusId())
{
// +ve shipment receipt
if (transType.CreditStockStatusId == 0 && transType.DebitStockStatusId > 0)
{ transType.CreditStockStatusId = shipmentInfo.ShipmentStockStatusId; }
// -ve shipment receipt
else if (transType.DebitStockStatusId == 0 && transType.CreditStockStatusId > 0)
{ transType.DebitStockStatusId = shipmentInfo.ShipmentStockStatusId; }
// something went wrong, raise error
else
{
ProgressMessage = "Unable to retrive FBA shipment location/status from tblAmazonShipment for Amazon shipment '" + shipmentInfo.FbaShipmentId + "'.";
recordSkip = recordSkip + 1;
goto Stop;
}
}
}
// something went wrong, raise error
else
{
ProgressMessage = "Coding required. Unhandled special case Transaction-Type encountered (Transaction-Type debit or credit is set to 0).";
recordSkip = recordSkip + 1;
goto Stop;
}
}
// make the changes
if (transType.StockJournalEntryEnabled == false)
{
transList[i].IsProcessed = true;
dbSkuTransaction.Update(transList[i]);
}
else
{
var list = new List<(int StockJournalId, int Quantity)>();
if (transType.FilterStockOnDateTime)
{
list = stockReallocate.StockReallocateBySkuNumber(
transType.StockJournalTypeId,
transList[i].SkuNumber,
transList[i].Quantity,
transType.DebitStockStatusId,
transType.CreditStockStatusId,
transType.FirstInFirstOut,
transList[i].TransactionDate,
false);
}
else
{
list = stockReallocate.StockReallocateBySkuNumber(
transType.StockJournalTypeId,
transList[i].SkuNumber,
transList[i].Quantity,
transType.DebitStockStatusId,
transType.CreditStockStatusId,
transType.FirstInFirstOut,
DateTime.UtcNow,
false);
}
// insufficient balance available
if (list == null || !list.Any())
{
// in special case (found inventory), continue
if (transType.TypeCode.Contains("<AmazonReport><_GET_FBA_FULFILLMENT_INVENTORY_ADJUSTMENTS_DATA_><F>"))
{
continue;// <--------------------------------------------------------------------------------------------------- is this the soruce of the bug?
}
else
{
ProgressMessage = "Insurficent status/location balance to relocate stock";
recordSkip = recordSkip + 1;
goto Stop;
}
}
// fail safe
int qtyAllocated = list.Sum(c => c.Quantity);
if (qtyAllocated > transList[i].Quantity)
{
throw new Exception(
currentMethodName + ": StockReallocateBySkuId() returned greater quantity than passed to function"
+ transList[i].SkuTransactionId);
}
// update sku transaction table
int qtyRemain = qtyAllocated;
var newRecordList = new List<Model.Stock.SkuTransaction>();
for (int j = 0; j <= list.Count; j++)
{
// update existing record
if (j == 0)
{
transList[i].Quantity = (short)list[j].Quantity;
transList[i].StockJournalId = list[j].StockJournalId;
transList[i].IsProcessed = true;
dbSkuTransaction.Update(transList[i]);
}
// new record
else if (j < list.Count)
{
var newRecord = transList[i].Clone();
newRecord.Quantity = (short)list[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;
newRecordList.Add(newRecord);
}
if (j < list.Count)
{
qtyRemain = qtyRemain - list[j].Quantity;
}
}
// add new transactions to table
for (int j = 0; j < newRecordList.Count; j++)
{
dbSkuTransaction.Create(newRecordList[j]);
}
}
ItemsCompleted++;
ItemsRemaining++;
scope.Complete();
}
// end of scope
}
// end of loop
}
catch (Exception ex)
{
Console.Write("\r");
ProgressMessage = ex.Message;
return;
}
Stop:
Console.Write("\r");
MiscFunction.EventLogInsert("ProcessStockTransactions() compete. " + ItemsCompleted + " total records processed, " + recordSkip + " rows uncompllete due to insurficent stock.");
MiscFunction.EventLogInsert("ProcessStockTransactions(), " + recordSkip + " rows skipped due to insurficent stock.", 2);
ReconciliationComplete = true;
ProgressMessage = "Operation complete.";
return;
}
/// <summary>
///
/// </summary>
public void ProcessLostAndFound()
{
using (var scope = new TransactionScope())
{
// get list of sku transactions to reconcile
var transList = new Data.Database.Stock.ReadSkuTransaction(sqlConnectionString).GetUnreconciled();
ItemsRemaining = transList.Count;
ItemsCompleted = 0;
// need to loop though table and cancel out any found before they are lost (in reality they were never
// lost, therefore should not be entered into journal as lost)
string lost = "<AmazonReport><_GET_FBA_FULFILLMENT_INVENTORY_ADJUSTMENTS_DATA_><M><-ve><InventoryMisplaced><SELLABLE>";
string found = "<AmazonReport><_GET_FBA_FULFILLMENT_INVENTORY_ADJUSTMENTS_DATA_><F><+ve><InventoryFound><SELLABLE>";
for (int i = 0; i < transList.Count; i++)
{
var transType = dbSkuTransactionType.GetByTypeName(transList[i].SkuTransactionTypeName);
if (transType.TypeCode == found && !transList[i].IsProcessed)
{
string sku = transList[i].SkuNumber;
int foundQty = transList[i].Quantity;
int foundQtyUnAllocated = foundQty;
//loop though list and find matching missing
for (int j = 0; j < transList.Count; j++)
{
// we have a match
if (transList[j].SkuNumber == sku && !transList[j].IsProcessed && transType.TypeCode == lost)
{
// split transaction and break
if (foundQtyUnAllocated - transList[j].Quantity < 0)
{
// create and validate clone
var clone = transList[j].Clone();
clone.Quantity = (short)foundQtyUnAllocated;
clone.IsProcessed = true;
// modifiy and validate existing record
transList[j].IsProcessed = false;
transList[j].Quantity = (short)(transList[j].Quantity - foundQtyUnAllocated);
foundQtyUnAllocated = 0;
// submitt to database
dbSkuTransaction.Create(clone);
dbSkuTransaction.Update(transList[j]);
}
// set as isprocessed and continue
else
{
foundQtyUnAllocated = foundQtyUnAllocated - transList[j].Quantity;
transList[j].IsProcessed = true;
dbSkuTransaction.Update(transList[j]);
}
// break?
if (foundQtyUnAllocated == 0)
{
break;
}
}
}
// update the found record
if (foundQty != foundQtyUnAllocated)
{
// set isprocess = true
if (foundQtyUnAllocated == 0)
{
transList[i].IsProcessed = true;
dbSkuTransaction.Update(transList[i]);
}
// split record
else if (foundQtyUnAllocated > 0)
{
throw new NotImplementedException();
// create clone
var clone = transList[i].Clone();
clone.Quantity -= (short)foundQtyUnAllocated;
clone.IsProcessed = true;
// modifiy and validate existing record
transList[i].IsProcessed = false;
transList[i].Quantity -= (short)foundQtyUnAllocated;
foundQtyUnAllocated = 0;
// submitt to database
dbSkuTransaction.Create(clone);
dbSkuTransaction.Update(transList[i]);
}
// this shouldn't happen
else
{
throw new Exception("Quantity unallocated is negative number");
}
}
}
}
scope.Complete();
}
}
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Logic.Stock
{
public class SkuTransactionTypePersistance
{
private string sqlConnectionString;
private List<Model.Stock.SkuTransactionType> cache;
private Data.Database.Stock.ReadSkuTransactionType dbRead;
public SkuTransactionTypePersistance(string sqlConnectionString)
{
this.sqlConnectionString = sqlConnectionString;
dbRead = new Data.Database.Stock.ReadSkuTransactionType(sqlConnectionString);
InnitCache();
}
public void InnitCache()
{
cache = new List<Model.Stock.SkuTransactionType>();
}
public Model.Stock.SkuTransactionType GetByTypeCode(string typeCode, bool clearCache = false)
{
if (string.IsNullOrWhiteSpace(typeCode))
{
return null;
}
if (clearCache)
{
InnitCache();
}
else
{
for (int i = 0; i < cache.Count; i++)
{
if (cache[i].TypeCode == typeCode)
{
return cache[i];
}
}
}
var result = dbRead.ByTypeCode(new List<string> { typeCode });
if (result.Any())
{
cache.Add(result[0]);
return result[0];
}
else
{
return null;
}
}
public Model.Stock.SkuTransactionType GetByTypeName(string typeName, bool clearCache = false)
{
if (string.IsNullOrWhiteSpace(typeName))
{
return null;
}
if (clearCache)
{
InnitCache();
}
else
{
for (int i = 0; i < cache.Count; i++)
{
if (cache[i].TypeName == typeName)
{
return cache[i];
}
}
}
var result = dbRead.ByTypeName(new List<string> { typeName });
if (result.Any())
{
cache.Add(result[0]);
return result[0];
}
else
{
return null;
}
}
}
}