mirror of
https://github.com/stokebob/bnhtrade.git
synced 2026-03-19 14:37:16 +00:00
Feature repricing min max (#10)
amazon settlement import/export improvements
This commit is contained in:
398
src/bnhtrade.Core/Logic/Stock/SkuTransactionReconcile.cs
Normal file
398
src/bnhtrade.Core/Logic/Stock/SkuTransactionReconcile.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user