using bnhtrade.Core.Data.Database.UnitOfWork; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Transactions; using static System.Formats.Asn1.AsnWriter; namespace bnhtrade.Core.Logic.Inventory { public class StockService : UnitOfWorkBase { public StockService() : base() { } internal StockService(IUnitOfWork unitOfWork) : base(unitOfWork) { } private int WIP_StockInsert(int accountJournalType, int stockJournalType, string currencyCode, decimal amount, int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId) { using (TransactionScope scope = new TransactionScope()) using (SqlConnection conn = new SqlConnection(sqlConnectionString)) { conn.Open(); // add account journal entry int accountJournalId = new Logic.Account.JournalService().JournalInsert(accountJournalType, entryDate, currencyCode, amount); // make the stock insert int stockId = WIP_StockInsertSub(sqlConnectionString, productId, conditionId, accountTaxCodeId, accountJournalId, stockJournalType, entryDate, quantity, debitStatusId); scope.Complete(); return stockId; } } private int WIP_StockInsertSub(int productId, int conditionId, int accountTaxCodeId, int accountJournalId, int stockJournalTypeId, DateTime stockJournalEntryDate, int quantity, int statusDebitId) { stockJournalEntryDate = DateTime.SpecifyKind(stockJournalEntryDate, DateTimeKind.Utc); // ensure account journal id hasn't already been added to stock table int count = 0; using (SqlCommand cmd = new SqlCommand(@" SELECT Count(tblStock.StockID) AS CountOfID FROM tblStock WHERE (((tblStock.AccountJournalID)=@accountJouranlId)); ", conn)) { cmd.Parameters.AddWithValue("@accountJouranlId", accountJournalId); count = (int)cmd.ExecuteScalar(); } if (count == 1) { throw new Exception("Add account journal entry already assigned to stock line."); } else if (count > 1) { throw new Exception("Houston we have a problem! An account journal entry is assigned to " + count + " stock lines."); } // ensure the debit for the account journal transaction is to an 'Asset' account type using (SqlCommand cmd = new SqlCommand(@" SELECT Count(tblAccountJournalPost.AccountJournalPostID) AS CountOfAccountJournalPostID FROM (tblAccountJournalPost INNER JOIN tblAccountChartOf ON tblAccountJournalPost.AccountChartOfID = tblAccountChartOf.AccountChartOfID) INNER JOIN tblAccountChartOfType ON tblAccountChartOf.AccountChartOfTypeID = tblAccountChartOfType.AccountChartOfTypeID WHERE tblAccountJournalPost.AmountGbp>=0 AND tblAccountChartOfType.BasicType='Asset' AND tblAccountJournalPost.AccountJournalID=@accountJournalId; ", conn)) { cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); if ((int)cmd.ExecuteScalar() < 1) { throw new Exception("Supplied AccountJournal entry must debit an 'Asset' account type."); } } // get statusCreditId for stock journal type int statusCreditId; using (SqlCommand cmd = new SqlCommand(@" SELECT tblStockJournalType.StockStatusID_Credit FROM tblStockJournalType WHERE tblStockJournalType.StockJournalTypeID=@stockJournalTypeId AND tblStockJournalType.StockStatusID_Credit Is Not Null; ", conn)) { cmd.Parameters.AddWithValue("@stockJournalTypeId", stockJournalTypeId); object obj = cmd.ExecuteScalar(); if (obj == null) { throw new Exception("Default credit status not set for StockJournalTypeID=" + stockJournalTypeId); } else { statusCreditId = (int)obj; } } // get/set an skuId int skuId = new Logic.Inventory.SkuService().GetSkuId(productId, conditionId, accountTaxCodeId, true); // add the entry to the stock table (minus stockJournalId) int stockId = 0; using (SqlCommand cmd = new SqlCommand(@" INSERT INTO tblStock (SkuID, AccountJournalID) OUTPUT INSERTED.StockID VALUES (@skuId, @accountJournalId); ", conn)) { cmd.Parameters.AddWithValue("@skuId", skuId); cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); stockId = (int)cmd.ExecuteScalar(); } // insert stock journal entry var journalPosts = new List<(int statusId, int quantity)>(); journalPosts.Add((statusDebitId, quantity)); journalPosts.Add((statusCreditId, (quantity * -1))); int stockJournalId = Stock.StockJournal.StockJournalInsert(sqlConnectionString, stockJournalTypeId, stockId, journalPosts, stockJournalEntryDate, true); // update the stock table using (SqlCommand cmd = new SqlCommand(@" UPDATE tblStock SET StockJournalID=@stockJournalId WHERE StockID=@stockId; ", conn)) { cmd.Parameters.AddWithValue("@stockJournalId", stockJournalId); cmd.Parameters.AddWithValue("@stockId", stockId); count = cmd.ExecuteNonQuery(); if (count < 1) { throw new Exception("New stock insert cancelled, failed to update StockJournalID"); } } scope.Complete(); return stockId; } private void WIP_StockDelete(int stockId) { int accountJournalType = 0; int stockJournalType = 0; // get stock and account types using (TransactionScope scope = new TransactionScope()) using (SqlConnection conn = new SqlConnection(sqlConnectionString)) { conn.Open(); // ensure stockId is owner-introduced using (SqlCommand cmd = new SqlCommand(@" SELECT tblStockJournal.StockJournalTypeID, tblAccountJournal.AccountJournalTypeID FROM (tblStock INNER JOIN tblAccountJournal ON tblStock.AccountJournalID = tblAccountJournal.AccountJournalID) INNER JOIN tblStockJournal ON tblStock.StockJournalID = tblStockJournal.StockJournalID WHERE (((tblStock.StockID)=@stockId)); ", conn)) { cmd.Parameters.AddWithValue("@stockId", stockId); using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { accountJournalType = reader.GetInt32(1); stockJournalType = reader.GetInt32(0); } else { throw new Exception("Integrity check failed, cancelling StockDeleteOwnerIntroduced"); } } } // check stock journal type is not restricted // owner inventory introduced if (stockJournalType == 2) { // no dramas } else { throw new Exception("Manual delete of this stock type is not supported, use the method that created it!"); } // check there is only one stock journal entry for stock item using (SqlCommand cmd = new SqlCommand(@" SELECT Count(tblStockJournal.StockJournalID) AS CountOfStockJournalID FROM tblStockJournal WHERE (((tblStockJournal.StockID)=@stockId)); ", conn)) { cmd.Parameters.AddWithValue("@stockId", stockId); int count = (int)cmd.ExecuteScalar(); if (count > 1) { throw new Exception("Delete " + count + " stock journal entries (other than source entry), before peforming this operation."); } } // remove account journal entry WIP_StockDeleteSubAccountJournalEntry(sqlConnectionString, stockId); // remove stock WIP_StockDeleteSub(sqlConnectionString, stockId); scope.Complete(); } } private void WIP_StockDeleteSub(int stockId) { using (TransactionScope scope = new TransactionScope()) using (SqlConnection conn = new SqlConnection(sqlConnectionString)) { conn.Open(); // check for accountJournalId on stock table using (SqlCommand cmd = new SqlCommand(@" SELECT AccountJournalID FROM tblStock WHERE StockID=@stockId; ", conn)) { cmd.Parameters.AddWithValue("@stockId", stockId); object obj = cmd.ExecuteScalar(); if (obj == null) { throw new Exception("StockID=" + stockId + " does not exist."); } else if (obj == DBNull.Value) { // nothing to do all is good } else { int id = (int)obj; if (id > 0) { throw new Exception("StockID=" + stockId + " remove account journal entry using method that created it first."); } } } // get stockJournalId int stockJournalId; using (SqlCommand cmd = new SqlCommand(@" SELECT StockJournalID FROM tblStock WHERE StockID=@stockId; ", conn)) { cmd.Parameters.AddWithValue("@stockId", stockId); try { stockJournalId = (int)cmd.ExecuteScalar(); } catch { throw new Exception("Could not retrive StockJournalID for StockID=" + stockId); } } // remove stockJournalId from stock table using (SqlCommand cmd = new SqlCommand(@" UPDATE tblStock SET StockJournalID=NULL WHERE StockID=@stockId; ", conn)) { cmd.Parameters.AddWithValue("@stockId", stockId); int count = cmd.ExecuteNonQuery(); if (count != 2) // we need to count the LastUpdated trigger! { throw new Exception("Failed to remove StockJournalID from stock table StockID=" + stockId); } } // delete stock journal entry Core.Stock.StockJournal.StockJournalDelete(sqlConnectionString, stockJournalId); // delete stock table entry using (SqlCommand cmd = new SqlCommand(@" DELETE FROM tblStock WHERE StockID=@stockId; ", conn)) { cmd.Parameters.AddWithValue("@stockId", stockId); int count = cmd.ExecuteNonQuery(); if (count != 1) { throw new Exception("StockID = " + stockId + " delete failed"); } else { scope.Complete(); } } } } // to be used by other methods within a transaction scope private void WIP_StockDeleteSubAccountJournalEntry(int stockId) { using (SqlConnection conn = new SqlConnection(sqlConnectionString)) { conn.Open(); var trans = conn.BeginTransaction(); // get the account journal id int accountJournalId = 0; using (SqlCommand cmd = new SqlCommand(@" SELECT AccountJournalID FROM tblStock WHERE StockID=@stockId; ", conn)) { cmd.Transaction = trans; cmd.Parameters.AddWithValue("@stockId", stockId); object obj = cmd.ExecuteScalar(); if (obj == null) { throw new Exception("StockID=" + stockId + " does not exist."); } else if (obj == DBNull.Value) { throw new Exception("AccountJournalID not found for StockID=" + stockId); } else { accountJournalId = (int)obj; } } // remove entry from stock table using (SqlCommand cmd = new SqlCommand(@" UPDATE tblStock SET AccountJournalID=NULL WHERE StockID=@stockId; ", conn)) { cmd.Transaction = trans; cmd.Parameters.AddWithValue("@stockId", stockId); int count = cmd.ExecuteNonQuery(); if (count != 2) // must include last modified trigger { throw new Exception("Failed to set AccountJournalID to NULL on stock table for StockID=" + stockId); } } // delete account journal entry new Data.Database.Repository.Implementation.AccountJournalRepository(conn, trans).DeleteJournal(accountJournalId); trans.Commit(); } } public int WIP_StockInsertPurchase(int productId, int conditionId, int accountTaxCodeId, int accountJournalId, int quantity, int statusDebitId) { DateTime stockJournalEntryDate; int stockJournalTypeId = 1; using (SqlConnection conn = new SqlConnection(sqlConnectionString)) { conn.Open(); // retrive info from purchase invoice line/transaction using (SqlCommand cmd = new SqlCommand(@" SELECT tblAccountJournal.EntryDate FROM tblAccountJournal WHERE (((tblAccountJournal.AccountJournalID)=@accountJournalId)); ", conn)) { cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); stockJournalEntryDate = DateTime.SpecifyKind((DateTime)cmd.ExecuteScalar(), DateTimeKind.Utc); } } return WIP_StockInsertSub(productId, conditionId, accountTaxCodeId, accountJournalId, stockJournalTypeId, stockJournalEntryDate, quantity, statusDebitId); } public int WIP_StockInsertOwnerIntroduced(decimal amount, int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId) { int accountJournalType = 3; int stockJournalType = 2; string currencyCode = "GBP"; return WIP_StockInsert(accountJournalType, stockJournalType, currencyCode, amount, quantity, productId, conditionId, accountTaxCodeId, entryDate, debitStatusId); } public void WIP_StockDeletePurchase(int stockId) { WIP_StockDeleteSub(sqlConnectionString, stockId); } public void WIP_StockDeleteOwnerIntroduced(int stockId) { WIP_StockDelete(sqlConnectionString, stockId); } } }