SP-API stock reconciliation

Amazon had depreciated a number of reports that were used for stock reconciliation. Application now uses the new fba ledger report to reconcile. It is currently untested, as this requires data from Amazon. Methods that require testing will return a 'NotImplementedException'.

Also, removed the depreciated ILMerge and replaced with ILRepack.

Plus much more tidying up, and improvements.
This commit is contained in:
Bobbie Hodgetts
2024-05-07 08:24:00 +01:00
committed by GitHub
parent 2f919d7b5a
commit 91ef9acc78
1272 changed files with 4944 additions and 2773311 deletions

View File

@@ -0,0 +1,395 @@
using Amazon.Runtime.Internal.Transform;
using bnhtrade.Core.Model.Stock;
using FikaAmazonAPI.AmazonSpApiSDK.Models.Restrictions;
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtrade.Core.Logic.Stock
{
public class SkuTransactionImport
{
private Log.LogEvent log = new Log.LogEvent();
private string ConstructTransactionTypeCode(Model.Import.FbaReimbursementReport record, bool isPositive)
{
if (string.IsNullOrEmpty(record.Reason))
{
return null;
}
string transactionCode = "<AmazonReport><_GET_FBA_REIMBURSEMENTS_DATA_>";
if (isPositive)
{
transactionCode = transactionCode + "<+ve>";
}
else
{
transactionCode = transactionCode + "<-ve>";
}
transactionCode = transactionCode + "<" + record.Reason + ">";
return transactionCode;
}
private string ConstructTransactionTypeCode(Model.Import.AmazonFbaInventoryLedgerDetail record)
{
if (string.IsNullOrEmpty(record.Reason))
{
return null;
}
string transactionCode = "<AmazonReport><GET_LEDGER_DETAIL_VIEW_DATA><" + record.EventType + ">";
if (!string.IsNullOrEmpty(record.Reason))
{
transactionCode = transactionCode + "<" + record.Reason + ">";
}
if (record.Quantity < 0)
{
transactionCode = transactionCode + "<-ve>";
}
else
{
transactionCode = transactionCode + "<+ve>";
}
transactionCode = transactionCode + "<" + record.Disposition + ">";
return transactionCode;
}
/// <summary>
/// Imports/Transaposes all data required for reconcilation, into the sku transaction table.
/// </summary>
public void ImportAll()
{
bool inventoryLedgerDetail = false;
bool reimbursement = false;
while (true)
{
try
{
if (true)
{
if (inventoryLedgerDetail == false) { inventoryLedgerDetail = true; ImportAmazonFbaLedgerDetail(); }
if (reimbursement == false) { reimbursement = true; ImportAmazonFbaReimbursement(); }
}
break;
}
catch (Exception ex)
{
log.LogError(
"Exception caught running Importing amazon reports in the SKU transaction table, see for further details",
ex.ToString()
);
}
}
}
/// <summary>
/// Imports/Transaposes data from the Amazon FBA Reimbursement report table, into the SKU transaction table, ready for reconcilation.
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public void ImportAmazonFbaReimbursement()
{
throw new NotImplementedException("Needs testing");
/*
* Not to be used for stock reconciliation! A single stock item can have multiple reimburesements aginst it.
* Only use to move lost inventory to Amazon ownership (even then, with some caveats!)
*
* generally any lost or damaged stock goes to lost and found (and is hence written off)
* once amazon have reimbursed (confitmation via this report) the stock is then moved to amazon owvership
* and also the 'Cost of goods' amounts moved to the appropreate account id
*/
log.LogInformation("Starting TransposeFbaRemovalOrderReport()");
int transposeCount = 0;
int transposeSkip = 0;
var dbAmznReport = new Data.Database.Import.AmazonFbaReimbursement();
var dbTransType = new Logic.Stock.SkuTransactionTypeCrud();
try
{
var importList = dbAmznReport.Read(false);
// we need to retrive the transaction-types from the database
// run through the returned records and build list of transaction-type codes
var transTypeCodeList = new List<string>();
foreach (var item in importList)
{
transTypeCodeList.Add(ConstructTransactionTypeCode(item, false));
// any that reimburse inventory, will have two entries
if(item.QuantityReimbursedInventory > 0)
{
transTypeCodeList.Add(ConstructTransactionTypeCode(item, true));
}
}
transTypeCodeList = transTypeCodeList.Distinct().ToList();
// get transaction-type objects from db
var transTypeDict = dbTransType.GetByTypeCode(transTypeCodeList);
// new transaction-type check
var foundNewType = false;
foreach (var transTypeCode in transTypeCodeList)
{
// if a transaction-type code did not exist in the db, we need to create it
if (transTypeDict.ContainsKey(transTypeCode) == false)
{
dbTransType.Create(
transTypeCode,
(int)Data.Database.Constants.StockJournalType.SkuReconciliationFbaReimbursement
);
var newItem = dbTransType.GetByTypeCode(transTypeCode);
if (newItem == null)
{
throw new Exception("Createing new transaction-type returned null");
}
transTypeDict.Add(newItem.TypeCode, newItem);
foundNewType = true;
}
// also check existing transaction-type for 'is new'
if (transTypeDict[transTypeCode].IsNewReviewRequired)
{
foundNewType = true;
}
}
// don't go any further until the transaction-type has been reviewed/setup
if (foundNewType)
{
log.LogWarning("Cannot complete ImportAmazonFbaReimbursement, new 'Stock Trnasaction Type' found. Review required/");
return;
}
// we're all setup, time to transpose the report into the transaction table
using (TransactionScope scope = new TransactionScope())
{
var dbTrans = new Logic.Stock.SkuTransactionCrud();
foreach (var item in importList)
{
int? transId = null;
// always added
if (true)
{
string typeCode = ConstructTransactionTypeCode(item, false);
var transType = transTypeDict[typeCode];
if (transType.IsNewReviewRequired)
{
throw new Exception("Fail safe: Buggy code, should not get here!");
}
else if (transType.TransactionImportEnabled)
{
var newTransaction = new Model.Stock.SkuTransactionCreate(
item.ApprovalDate,
typeCode,
item.FbaReimbursementReportID,
item.ReimbursementId,
item.Reason,
item.Sku,
item.QuantityReimbursedInventory
);
transId = dbTrans.Create(newTransaction);
}
}
// double transaction added if true
if (item.QuantityReimbursedInventory > 0)
{
string typeCode = ConstructTransactionTypeCode(item, true);
var transType = transTypeDict[typeCode];
if (transType.IsNewReviewRequired)
{
throw new Exception("Fail safe: Buggy code, should not get here!");
}
else if (transType.TransactionImportEnabled)
{
var newTransaction = new Model.Stock.SkuTransactionCreate(
item.ApprovalDate,
typeCode,
item.FbaReimbursementReportID,
item.ReimbursementId,
item.Reason,
item.Sku,
item.QuantityReimbursedInventory
);
transId = dbTrans.Create(newTransaction);
}
}
// update the amazon report table
dbAmznReport.UpdateIsProcessed(item.FbaReimbursementReportID, true, transId);
transposeCount = transposeCount + 1;
}
// drop out of loop
scope.Complete();
}
Console.Write("\r");
log.LogInformation("ProcessFbaReimbursementData() complete, " + transposeCount + " total records transposed, " + transposeSkip + " records skipped.");
}
catch (Exception ex)
{
log.LogError("Exception catch, aborting ProcessFbaReimbursementData(), see detailed info. "
+ transposeCount + " total records completed, " + transposeSkip + " records skipped.", ex.ToString());
}
}
/// <summary>
/// Imports/Transaposes data from the Amazon FBA Ledger Detail report table, into the SKU transaction table, ready for reconcilation.
/// </summary>
/// <exception cref="NotImplementedException"></exception>
public void ImportAmazonFbaLedgerDetail()
{
// Done but needs testing!!
throw new NotImplementedException("Done but needs testing!!");
log.LogInformation("Starting TransposeFbaAdustmentReport()");
int transposeCount = 0;
int transposeSkip = 0;
using (var scope = new TransactionScope())
{
try
{
// get unprocessed amazon ledger report items
var dbImport = new Data.Database.Import.AmazonFbaInventoryLedgerDetail();
var reportDict = dbImport.Read(null, null, false);
// create transaction list to insert into transaction table
var transactionList = new List<SkuTransactionCreate>();
var transCodeToJournalTypeId = new Dictionary<string, int>();
foreach (var item in reportDict)
{
// test for internal Amazon stuff that we don't care about and mark as processed
if (item.Value.EventType == "WhseTransfers")
{
dbImport.UpdateIsProcessed(item.Key, true);
}
// add the the transaction list
else
{
// build the transaction code
string transactionCode = ConstructTransactionTypeCode(item.Value);
// load the report item into parameter
DateTime transactionDate = item.Value.DateAndTime;
int foreignKey = item.Key;
string reference = null;
if (!string.IsNullOrEmpty(item.Value.ReferenceId))
{ reference = item.Value.ReferenceId; }
string detail = "Fulfillment Center: " + item.Value.FulfillmentCenter;
string skuNumber = item.Value.Msku;
// quanity in the transaction table is always be +ve
int quantity = item.Value.Quantity;
if (quantity < 0)
quantity = quantity * -1;
// create the objet class and add to the list
var transaction = new Model.Stock.SkuTransactionCreate(
transactionDate
, transactionCode
, foreignKey
, reference
, detail
, skuNumber
, quantity
);
transactionList.Add(transaction);
// add transtypecode to dictionary to 'stock journal type' dictionary. Needed if there's a new transaction type
if (transCodeToJournalTypeId.ContainsKey(transactionCode) == false)
{
int journalTypeId = 0;
if (item.Value.EventType == "Receipts")
{ journalTypeId = (int)Data.Database.Constants.StockJournalType.SkuReconciliationFbaReceipt; }
else if (item.Value.EventType == "Shipments")
{ journalTypeId = (int)Data.Database.Constants.StockJournalType.SkuReconciliationFbaShipment; }
else if (item.Value.EventType == "Adjustments")
{ journalTypeId = (int)Data.Database.Constants.StockJournalType.SkuReconciliationFbaAdjustment; }
else if (item.Value.EventType == "CustomerReturns")
{ journalTypeId = (int)Data.Database.Constants.StockJournalType.SkuReconciliationFbaCustomerReturn; }
else if (item.Value.EventType == "VendorReturns")
{ journalTypeId = (int)Data.Database.Constants.StockJournalType.SkuReconciliationFbaVendorReturn; }
else if (item.Value.EventType == "WhseTransfers")
{ journalTypeId = -1; }
else
{ throw new Exception("New event-type " + item.Value.EventType + " in the GET_LEDGER_DETAIL_VIEW_DATA report"); }
transCodeToJournalTypeId.Add(transactionCode, journalTypeId);
}
}
}
// check for any new types codes, and add them if ther are
var dbTransType = new Logic.Stock.SkuTransactionTypeCrud();
var transTypeList = dbTransType.GetByTypeCode(transCodeToJournalTypeId.Keys.ToList());
foreach ( var transType in transTypeList)
{
if (transCodeToJournalTypeId.ContainsKey(transType.Key))
{
transCodeToJournalTypeId.Remove(transType.Key);
}
}
foreach (var newItem in transCodeToJournalTypeId)
{
dbTransType.Create(newItem.Key, newItem.Value);
}
// finally, add the transction list to the table
var dbTransaction = new Logic.Stock.SkuTransactionCrud();
foreach (var item in transactionList)
{
int id = dbTransaction.Create(item);
dbImport.UpdateIsProcessed((int)item.ForeignKey, id);
}
scope.Complete();
log.LogInformation(
"TransposeFbaAdustmentReport() complete, " + transposeCount + " total records transposed, " + transposeSkip + " records skipped."
);
if (transposeSkip > 0)
{
log.LogInformation(
transposeSkip + " number records skipped during TransposeFbaAdustmentReport() operation."
);
}
}
catch (Exception ex)
{
scope.Dispose();
log.LogError(
"Exception catch, aborting TransposeFbaAdustmentReport(), see detailed info. "
+ transposeCount + " total records completed, " + transposeSkip + " records skipped."
, ex.ToString()
);
}
}
return;
}
}
}