using Amazon.Runtime.Internal.Transform; using bnhtrade.Core.Model.Stock; using FikaAmazonAPI.AmazonSpApiSDK.Models.Restrictions; using System; using System.Collections.Generic; using Microsoft.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 = "<_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 = "<" + 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; } /// /// Imports/Transaposes all required data into sku transaction table for reconcilation. /// public void ImportAll() { string methodName = "'Import all' data to 'Stock SKU Transactions'"; log.LogInformation(methodName + " started..."); try { ImportAmazonFbaLedgerDetail(); ImportAmazonFbaReimbursement(); } catch { log.LogError(methodName + " did not complete."); throw; } log.LogInformation(methodName + " complete."); } /// /// Imports/Transaposes data from the Amazon FBA Reimbursement report table, into the SKU transaction table, ready for reconcilation. /// /// public void ImportAmazonFbaReimbursement() { string methodName = "Import 'Amazon FBA Reimbursement' into 'Stock SKU Transactions'"; //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(methodName + " started..."); int importedCount = 0; int processedCount = 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(); foreach (var item in importList) { processedCount++; 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(methodName + " unable to complete. 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(methodName + " 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(methodName + " 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); importedCount++; } // drop out of loop scope.Complete(); } Console.Write("\r"); log.LogInformation( methodName + " complete. Records transferred/processed " + importedCount + "/" + processedCount ); } catch (Exception ex) { log.LogError( methodName + " aborted due an exception, no records where modified." , ex.ToString() ); throw; } } /// /// Imports/Transaposes data from the Amazon FBA Ledger Detail report table, into the SKU transaction table, ready for reconcilation. /// /// public void ImportAmazonFbaLedgerDetail() { string methodName = "Import 'Amazon FBA Ledger Detail' into 'Stock SKU Transactions'"; log.LogInformation(methodName + " started"); int transferredCount = 0; int processedCount = 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(); var transCodeToJournalTypeId = new Dictionary(); foreach (var item in reportDict) { processedCount++; // 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, remove existing from list and add remaing to db using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Suppress)) { 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); transferredCount++; } scope.Complete(); log.LogInformation( methodName + " complete. Records transferred/processed " + transferredCount + "/" + processedCount ); } catch (Exception ex) { scope.Dispose(); log.LogError( methodName + " aborted, no records modified. See additional info exception details." , ex.ToString() ); throw; } } } } }