diff --git a/src/bnhtrade.ComTypeLib/Stock/Stock.cs b/src/bnhtrade.ComTypeLib/Stock/Stock.cs index e9d6fe9..340afbf 100644 --- a/src/bnhtrade.ComTypeLib/Stock/Stock.cs +++ b/src/bnhtrade.ComTypeLib/Stock/Stock.cs @@ -70,7 +70,7 @@ namespace bnhtrade.ComTypeLib public void StockJournalDelete(ConnectionCredential sqlConnCred, int stockJournalId) { - Core.Stock.StockJournal.StockJournalDelete(sqlConnCred.ConnectionString, stockJournalId); + new Core.Logic.Inventory.StockJournalService().StockJournalDelete(stockJournalId); } public object ReconcileStockTransactions(ConnectionCredential sqlConnCred) @@ -128,7 +128,7 @@ namespace bnhtrade.ComTypeLib public bool StockJournalConsistencyCheck(ConnectionCredential sqlConnCred, int stockId) { - return Core.Stock.StockJournal.WIP_StockJournalConsistencyCheck(sqlConnCred.ConnectionString, stockId, null); + return new Core.Logic.Inventory.StockJournalService().WIP_StockJournalConsistencyCheck(stockId, null); } public void SkuTransactionAdd(ConnectionCredential sqlConnCred, int quantity, string skuNumber, string transactionTypeCode, DateTime transactionDate) diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/AccountJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/AccountJournalRepository.cs index ab4c3ca..167d264 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Implementation/AccountJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/AccountJournalRepository.cs @@ -383,12 +383,47 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation return returnList; } + public DateTime ReadJournalEntryDate(int journalId) + { + if (journalId <= 0) + { + throw new ArgumentException("Invalid journal ID provided.", nameof(journalId)); + } + + string sql = @" + SELECT tblAccountJournal.EntryDate + FROM tblAccountJournal + WHERE (((tblAccountJournal.AccountJournalID)=@accountJournalId));"; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + cmd.Parameters.AddWithValue("@accountJournalId", journalId); + + object obj = cmd.ExecuteScalar(); + + if (obj == null) + { + throw new Exception("Journal entry not found for AccountJournalID=" + journalId); + } + + return DateTime.SpecifyKind((DateTime)obj, DateTimeKind.Utc); + } + } + /// /// Test for locked journal entry /// /// False on locked journal entry public bool ReadJournalIsLocked(int journalId) { + if (journalId <= 0) + { + throw new ArgumentException("Invalid journal ID provided.", nameof(journalId)); + } + string sql = @" SELECT tblAccountJournal.IsLocked diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs index 58aa142..15d7c18 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Transactions; +using static bnhtrade.Core.Data.Database.Constants; namespace bnhtrade.Core.Data.Database.Repository.Implementation { @@ -118,7 +119,7 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation // insert journal posts into database //new Data.Database.Stock - Core.Stock.StockJournal.StockJournalPostInsert(conn, stockId, stockJournalId, journalPosts, isNewStock); + StockJournalPostInsert(stockId, stockJournalId, journalPosts, isNewStock); // consistency check bool consistencyResult = true; @@ -158,6 +159,30 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation // Read // + public int ReadJournalTypeIdByStockId(int stockId) + { + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" + SELECT tblStockJournal.StockJournalTypeID + FROM tblStock INNER JOIN + tblStockJournal ON tblStock.StockJournalID = tblStockJournal.StockJournalID + WHERE (tblStock.StockID = @stockId);"; + + cmd.Parameters.AddWithValue("@stockId", stockId); + + object obj = cmd.ExecuteScalar(); + + if (obj == null || obj == DBNull.Value) + { + throw new Exception("No stock journal type found for StockID=" + stockId); + } + + return (int)obj; + } + } + public int ReadStatusBalanceBySku(string sku, int statusId) { int statusBalance = new int(); @@ -255,6 +280,22 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation return statusBalance; } + public int ReadJournalEntryCountByStockId(int stockId) + { + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" + SELECT Count(tblStockJournal.StockJournalID) AS CountOfStockJournalID + FROM tblStockJournal + WHERE (((tblStockJournal.StockID)=@stockId));"; + + cmd.Parameters.AddWithValue("@stockId", stockId); + + return (int)cmd.ExecuteScalar(); + } + } + // // update // diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockRepository.cs index 1014ac8..4fe9bed 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockRepository.cs @@ -1,12 +1,13 @@ -using bnhtrade.Core.Data.Database._BoilerPlate; -using bnhtrade.Core.Data.Database.Repository.Interface; -using Microsoft.Data.SqlClient; +using bnhtrade.Core.Data.Database.Repository.Interface; +using bnhtrade.Core.Data.Database.UnitOfWork; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Transactions; +using static System.Formats.Asn1.AsnWriter; namespace bnhtrade.Core.Data.Database.Repository.Implementation { @@ -16,126 +17,424 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation { } - public List ReadStatus(List statusIds = null, List statusTypeIds = null) + // + // create + // + + public int WIP_StockInsertSub(int productId, int conditionId, int accountTaxCodeId, + int accountJournalId, int stockJournalTypeId, DateTime stockJournalEntryDate, int quantity, int statusDebitId) { - var sqlBuilder = new SqlWhereBuilder(); - var returnList = new List(); + stockJournalEntryDate = DateTime.SpecifyKind(stockJournalEntryDate, DateTimeKind.Utc); - //build sql query - string sql = @" - SELECT [StockStatusID] - ,[StatusCode] - ,[StockStatus] - ,[StockStatusTypeID] - ,[Reference] - ,[ForeignKeyID] - ,[IsCreditOnly] - ,[IsClosed] - ,[RecordCreated] - FROM [e2A].[dbo].[tblStockStatus] - WHERE 1=1 "; + // ensure account journal id hasn't already been added to stock table + int count = 0; - // build the where statments - if (statusIds.Any()) + using (SqlCommand cmd = new SqlCommand(@" + SELECT Count(tblStock.StockID) AS CountOfID + FROM tblStock + WHERE (((tblStock.AccountJournalID)=@accountJouranlId)); + ", conn)) { - sqlBuilder.In("StockStatusID", statusIds, "AND"); - } - if (statusTypeIds.Any()) - { - sqlBuilder.In("StockStatusTypeID", statusTypeIds, "AND"); + cmd.Parameters.AddWithValue("@accountJouranlId", accountJournalId); + + count = (int)cmd.ExecuteScalar(); } - // append where string to the sql - if (sqlBuilder.IsSetSqlWhereString) + if (count == 1) { - sql = sql + sqlBuilder.SqlWhereString; + 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."); } - using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + // 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.CommandText = sql; - cmd.Transaction = _transaction as SqlTransaction; + cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); - if (sqlBuilder.ParameterListIsSet) + if ((int)cmd.ExecuteScalar() < 1) { - sqlBuilder.AddParametersToSqlCommand(cmd); - } - - using (SqlDataReader reader = cmd.ExecuteReader()) - { - var typeDict = new StockRepository(_connection, _transaction).ReadStatusType(); - - while (reader.Read()) - { - int statusId = reader.GetInt32(0); - int? statusCode = null; - if (!reader.IsDBNull(1)) { statusCode = reader.GetInt32(1); } - string stockStatus = reader.GetString(2); - int typeId = reader.GetInt32(3); - string reference = null; - if (!reader.IsDBNull(4)) { reference = reader.GetString(4); } - int? foreignKeyId = null; - if (!reader.IsDBNull(5)) { foreignKeyId = reader.GetInt32(5); } - bool isCreditOnly = reader.GetBoolean(6); - bool isClosed = reader.GetBoolean(7); - DateTime recordCreated = DateTime.SpecifyKind(reader.GetDateTime(8), DateTimeKind.Utc); - - var newItem = new Model.Stock.Status(statusId - , statusCode - , stockStatus - , typeDict[typeId] - , reference - , foreignKeyId - , isCreditOnly - , isClosed - , recordCreated - ); - - returnList.Add(newItem); - } + throw new Exception("Supplied AccountJournal entry must debit an 'Asset' account type."); } } - return returnList; + + // 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; } - public Dictionary ReadStatusType() + public int WIP_StockInsertPurchase(int productId, int conditionId, int accountTaxCodeId, int accountJournalId, int quantity, int statusDebitId) { - var returnDict = new Dictionary(); + DateTime stockJournalEntryDate; + int stockJournalTypeId = 1; - // get all account info before we start - var accountDict = new AccountCodeRepository(_connection, _transaction).ReadAccountCode(); - - using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + using (SqlConnection conn = new SqlConnection(sqlConnectionString)) { - cmd.CommandText = @" - SELECT [StockStatusTypeID] - ,[StatusTypeName] - ,[ForeignKeyType] - ,[ReferenceType] - ,[AccountChartOfID] - FROM [e2A].[dbo].[tblStockStatusType]"; - cmd.Transaction = _transaction as SqlTransaction; + conn.Open(); - using (SqlDataReader reader = cmd.ExecuteReader()) + // retrive info from purchase invoice line/transaction + using (SqlCommand cmd = new SqlCommand(@" + SELECT tblAccountJournal.EntryDate + FROM tblAccountJournal + WHERE (((tblAccountJournal.AccountJournalID)=@accountJournalId)); + ", conn)) { - var accountIdDict = new Dictionary(); + cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); - while (reader.Read()) + 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); + } + + // + // read + // + + // + // update + // + + // + // delete + // + + 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()) { - int statusTypeId = reader.GetInt32(0); - string name = reader.GetString(1); - string foreignKey = null; - if (!reader.IsDBNull(2)) { foreignKey = reader.GetString(2); } - string reference = null; - if (!reader.IsDBNull(3)) { reference = reader.GetString(3); } - uint accountId = (uint)reader.GetInt32(4); + if (reader.Read()) + { + accountJournalType = reader.GetInt32(1); + stockJournalType = reader.GetInt32(0); + } + else + { + throw new Exception("Integrity check failed, cancelling StockDeleteOwnerIntroduced"); + } + } + } - var statusType = new Model.Stock.StatusType(statusTypeId, name, foreignKey, reference, accountDict[(int)accountId]); - returnDict.Add(statusTypeId, statusType); + // 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 + new Core.Logic.Inventory.StockJournalService().StockJournalDelete(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(); } } } - return returnDict; + } + + // 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 void WIP_StockDeletePurchase(int stockId) + { + WIP_StockDeleteSub(sqlConnectionString, stockId); + } + + public void WIP_StockDeleteOwnerIntroduced(int stockId) + { + WIP_StockDelete(sqlConnectionString, stockId); } } } diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockStatusRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockStatusRepository.cs new file mode 100644 index 0000000..246f0ca --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockStatusRepository.cs @@ -0,0 +1,141 @@ +using bnhtrade.Core.Data.Database._BoilerPlate; +using bnhtrade.Core.Data.Database.Repository.Interface; +using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.Repository.Implementation +{ + internal class StockStatusRepository : _Base, IStockStatusRepository + { + public StockStatusRepository(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction) + { + } + + public List ReadStatus(List statusIds = null, List statusTypeIds = null) + { + var sqlBuilder = new SqlWhereBuilder(); + var returnList = new List(); + + //build sql query + string sql = @" + SELECT [StockStatusID] + ,[StatusCode] + ,[StockStatus] + ,[StockStatusTypeID] + ,[Reference] + ,[ForeignKeyID] + ,[IsCreditOnly] + ,[IsClosed] + ,[RecordCreated] + FROM [e2A].[dbo].[tblStockStatus] + WHERE 1=1 "; + + // build the where statments + if (statusIds.Any()) + { + sqlBuilder.In("StockStatusID", statusIds, "AND"); + } + if (statusTypeIds.Any()) + { + sqlBuilder.In("StockStatusTypeID", statusTypeIds, "AND"); + } + + // append where string to the sql + if (sqlBuilder.IsSetSqlWhereString) + { + sql = sql + sqlBuilder.SqlWhereString; + } + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = sql; + cmd.Transaction = _transaction as SqlTransaction; + + if (sqlBuilder.ParameterListIsSet) + { + sqlBuilder.AddParametersToSqlCommand(cmd); + } + + using (SqlDataReader reader = cmd.ExecuteReader()) + { + var typeDict = new StockStatusRepository(_connection, _transaction).ReadStatusType(); + + while (reader.Read()) + { + int statusId = reader.GetInt32(0); + int? statusCode = null; + if (!reader.IsDBNull(1)) { statusCode = reader.GetInt32(1); } + string stockStatus = reader.GetString(2); + int typeId = reader.GetInt32(3); + string reference = null; + if (!reader.IsDBNull(4)) { reference = reader.GetString(4); } + int? foreignKeyId = null; + if (!reader.IsDBNull(5)) { foreignKeyId = reader.GetInt32(5); } + bool isCreditOnly = reader.GetBoolean(6); + bool isClosed = reader.GetBoolean(7); + DateTime recordCreated = DateTime.SpecifyKind(reader.GetDateTime(8), DateTimeKind.Utc); + + var newItem = new Model.Stock.Status(statusId + , statusCode + , stockStatus + , typeDict[typeId] + , reference + , foreignKeyId + , isCreditOnly + , isClosed + , recordCreated + ); + + returnList.Add(newItem); + } + } + } + return returnList; + } + + public Dictionary ReadStatusType() + { + var returnDict = new Dictionary(); + + // get all account info before we start + var accountDict = new AccountCodeRepository(_connection, _transaction).ReadAccountCode(); + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.CommandText = @" + SELECT [StockStatusTypeID] + ,[StatusTypeName] + ,[ForeignKeyType] + ,[ReferenceType] + ,[AccountChartOfID] + FROM [e2A].[dbo].[tblStockStatusType]"; + cmd.Transaction = _transaction as SqlTransaction; + + using (SqlDataReader reader = cmd.ExecuteReader()) + { + var accountIdDict = new Dictionary(); + + while (reader.Read()) + { + int statusTypeId = reader.GetInt32(0); + string name = reader.GetString(1); + string foreignKey = null; + if (!reader.IsDBNull(2)) { foreignKey = reader.GetString(2); } + string reference = null; + if (!reader.IsDBNull(3)) { reference = reader.GetString(3); } + uint accountId = (uint)reader.GetInt32(4); + + var statusType = new Model.Stock.StatusType(statusTypeId, name, foreignKey, reference, accountDict[(int)accountId]); + returnDict.Add(statusTypeId, statusType); + } + } + } + return returnDict; + } + } +} diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IAccountJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IAccountJournalRepository.cs index 6de9735..4d74003 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Interface/IAccountJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IAccountJournalRepository.cs @@ -11,6 +11,7 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface int AccountJournalInsert(int journalTypeId, DateTime entryDate, string currencyCode, decimal amount, int debitAccountId = 0, int creditAccountId = 0, bool lockEntry = false); bool AccountJournalPostInsert(int journalId, DateTime entryDate, string currencyCode, decimal amount, int debitAccountId = 0, int creditAccountId = 0); Dictionary ReadJournal(List journalIdList); + DateTime ReadJournalEntryDate(int journalId); bool ReadJournalIsLocked(int journalId); Dictionary ReadJournalType(List journalTypeIds = null, List typeTitles = null); bool DeleteJournal(int accountJournalId); diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs index 54e4296..b629dbe 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs @@ -8,9 +8,11 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface { internal interface IStockJournalRepository { + int ReadJournalTypeIdByStockId(int stockId); int ReadStatusBalanceBySku(string sku, int statusId); int ReadStatusBalanceByStockNumber(int stockNumber, int statusId); int ReadStatusBalanceByStockId(int stockId, int statusId); + int ReadJournalEntryCountByStockId(int stockId); Dictionary ReadStatusBalanceByStatusId(int statusId); void StockJournalDelete(int stockJournalId); void StockJournalPostInsert(int stockId, int stockJournalId, List<(int statusId, int quantity)> journalPosts, bool isNewStock = false); diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockRepository.cs index 0ff9470..8d4de77 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockRepository.cs @@ -8,7 +8,14 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface { internal interface IStockRepository { - List ReadStatus(List statusIds = null, List statusTypeIds = null); - Dictionary ReadStatusType(); + int WIP_StockInsertSub(int productId, int conditionId, int accountTaxCodeId, + int accountJournalId, int stockJournalTypeId, DateTime stockJournalEntryDate, int quantity, int statusDebitId); + int WIP_StockInsertPurchase(int productId, int conditionId, int accountTaxCodeId, int accountJournalId, int quantity, int statusDebitId); + int WIP_StockInsertOwnerIntroduced(decimal amount, int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId); + void WIP_StockDelete(int stockId); + void WIP_StockDeleteSub(int stockId); + void WIP_StockDeleteSubAccountJournalEntry(int stockId); + void WIP_StockDeletePurchase(int stockId); + void WIP_StockDeleteOwnerIntroduced(int stockId); } } diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockStatusRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockStatusRepository.cs new file mode 100644 index 0000000..7f06bcd --- /dev/null +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockStatusRepository.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace bnhtrade.Core.Data.Database.Repository.Interface +{ + internal interface IStockStatusRepository + { + List ReadStatus(List statusIds = null, List statusTypeIds = null); + Dictionary ReadStatusType(); + } +} diff --git a/src/bnhtrade.Core/Data/Database/UnitOfWork/IUnitOfWork.cs b/src/bnhtrade.Core/Data/Database/UnitOfWork/IUnitOfWork.cs index 4f5dd05..8177f2e 100644 --- a/src/bnhtrade.Core/Data/Database/UnitOfWork/IUnitOfWork.cs +++ b/src/bnhtrade.Core/Data/Database/UnitOfWork/IUnitOfWork.cs @@ -20,8 +20,9 @@ namespace bnhtrade.Core.Data.Database.UnitOfWork IAccountJournalRepository JournalRepository { get; } ISequenceGenerator SequenceGenerator { get; } ISkuRepository SkuRepository { get; } - IStockJournalRepository StockJournalRepository { get; } IStockRepository StockRepository { get; } + IStockJournalRepository StockJournalRepository { get; } + IStockStatusRepository StockStatusRepository { get; } // Methods to manage the transaction void Commit(); diff --git a/src/bnhtrade.Core/Data/Database/UnitOfWork/UnitOfWork.cs b/src/bnhtrade.Core/Data/Database/UnitOfWork/UnitOfWork.cs index 9cbf4cf..0dbdbf6 100644 --- a/src/bnhtrade.Core/Data/Database/UnitOfWork/UnitOfWork.cs +++ b/src/bnhtrade.Core/Data/Database/UnitOfWork/UnitOfWork.cs @@ -28,8 +28,9 @@ namespace bnhtrade.Core.Data.Database.UnitOfWork private IAccountJournalRepository _journalRepository; private ISequenceGenerator _sequenceGenerator; private ISkuRepository _skuRepository; - private IStockJournalRepository _stockJournalRespository; private IStockRepository _stockRepository; + private IStockJournalRepository _stockJournalRespository; + private IStockStatusRepository _stockStatusRepository; internal UnitOfWork() { @@ -158,6 +159,18 @@ namespace bnhtrade.Core.Data.Database.UnitOfWork } } + public IStockRepository StockRepository + { + get + { + if (_stockRepository == null) + { + _stockRepository = new StockRepository(_connection, _transaction); + } + return _stockRepository; + } + } + public IStockJournalRepository StockJournalRepository { get @@ -170,15 +183,15 @@ namespace bnhtrade.Core.Data.Database.UnitOfWork } } - public IStockRepository StockRepository + public IStockStatusRepository StockStatusRepository { get { - if (_stockRepository == null) + if (_stockStatusRepository == null) { - _stockRepository = new StockRepository(_connection, _transaction); + _stockStatusRepository = new StockStatusRepository(_connection, _transaction); } - return _stockRepository; + return _stockStatusRepository; } } diff --git a/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs b/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs index cb974cf..1e34602 100644 --- a/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs +++ b/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs @@ -21,15 +21,14 @@ namespace bnhtrade.Core.Logic.Inventory { throw new ArgumentException("Stock journal ID must be greater than zero", nameof(stockJournalId)); } - bool result = uow.StockJournalRepository.DeleteStockJournal(stockJournalId); + uow.StockJournalRepository.StockJournalDelete(stockJournalId); CommitIfOwned(uow); - if (!result) - { - throw new InvalidOperationException($"Failed to delete stock journal with ID {stockJournalId}"); - } }); } + // can be used before commiting an sql insert, update or delete to the stock journal to ensure a status does not fall below 0 + // (unless the status is enabled to do so) + // set empty list or statusIdEffected to null to check entier stock entries for consistency public bool WIP_StockJournalConsistencyCheck(int stockId, List statusIdEffected = null) { return WithUnitOfWork(uow => diff --git a/src/bnhtrade.Core/Logic/Inventory/StockService.cs b/src/bnhtrade.Core/Logic/Inventory/StockService.cs index c20588f..eb05986 100644 --- a/src/bnhtrade.Core/Logic/Inventory/StockService.cs +++ b/src/bnhtrade.Core/Logic/Inventory/StockService.cs @@ -18,187 +18,30 @@ namespace bnhtrade.Core.Logic.Inventory 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)) + return WithUnitOfWork(uow => { - conn.Open(); - // add account journal entry - int accountJournalId = new Logic.Account.JournalService().JournalInsert(accountJournalType, entryDate, currencyCode, amount); + int accountJournalId = new Logic.Account.JournalService(uow).JournalInsert(accountJournalType, entryDate, currencyCode, amount); // make the stock insert - int stockId = WIP_StockInsertSub(sqlConnectionString, productId, conditionId, accountTaxCodeId, + int stockId = uow.StockRepository.WIP_StockInsertSub(productId, conditionId, accountTaxCodeId, accountJournalId, stockJournalType, entryDate, quantity, debitStatusId); - scope.Complete(); + CommitIfOwned(uow); + 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)) + WithUnitOfWork(uow => { - 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"); - } - } - } + stockJournalType = uow.StockJournalRepository.ReadJournalTypeIdByStockId(stockId); // check stock journal type is not restricted // owner inventory introduced @@ -211,21 +54,12 @@ namespace bnhtrade.Core.Logic.Inventory 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)) + // check there is only one stock journal entry for stock item (i.e. the source entry) + int count = uow.StockJournalRepository.ReadJournalEntryCountByStockId(stockId); + + if (count > 1) { - 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."); - } + throw new Exception("Delete " + count + " stock journal entries (other than source entry), before peforming this operation."); } // remove account journal entry @@ -235,7 +69,23 @@ namespace bnhtrade.Core.Logic.Inventory WIP_StockDeleteSub(sqlConnectionString, stockId); scope.Complete(); - } + + + + + + + + + + + + + + + + + }); } private void WIP_StockDeleteSub(int stockId) @@ -312,7 +162,7 @@ namespace bnhtrade.Core.Logic.Inventory } // delete stock journal entry - Core.Stock.StockJournal.StockJournalDelete(sqlConnectionString, stockJournalId); + new Core.Logic.Inventory.StockJournalService().StockJournalDelete(stockJournalId); // delete stock table entry using (SqlCommand cmd = new SqlCommand(@" @@ -401,23 +251,14 @@ namespace bnhtrade.Core.Logic.Inventory DateTime stockJournalEntryDate; int stockJournalTypeId = 1; - using (SqlConnection conn = new SqlConnection(sqlConnectionString)) + return WithUnitOfWork(uow => { - 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); + stockJournalEntryDate = uow.JournalRepository.ReadJournalEntryDate(accountJournalId); + int result = uow.StockRepository.WIP_StockInsertSub( + productId, conditionId, accountTaxCodeId, accountJournalId, stockJournalTypeId, stockJournalEntryDate, quantity, statusDebitId); + CommitIfOwned(uow); + return result; + }); } public int WIP_StockInsertOwnerIntroduced(decimal amount, int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId) diff --git a/src/bnhtrade.Core/Logic/Stock/StatusBalance.cs b/src/bnhtrade.Core/Logic/Stock/StatusBalance.cs index 34c7330..8d41852 100644 --- a/src/bnhtrade.Core/Logic/Stock/StatusBalance.cs +++ b/src/bnhtrade.Core/Logic/Stock/StatusBalance.cs @@ -100,7 +100,7 @@ namespace bnhtrade.Core.Logic.Stock var sku = readSku.BySkuNumber(statusTransaction.SkuNumber); // get the status obj - var status = uow.StockRepository.ReadStatus(new List { statusTransaction.StockStatusId })[0]; + var status = uow.StockStatusRepository.ReadStatus(new List { statusTransaction.StockStatusId })[0]; return new Model.Stock.StatusBalance(status, sku, entryList); } diff --git a/src/bnhtrade.Core/Program.cs b/src/bnhtrade.Core/Program.cs index 60f95b5..b6960bf 100644 --- a/src/bnhtrade.Core/Program.cs +++ b/src/bnhtrade.Core/Program.cs @@ -233,532 +233,6 @@ namespace bnhtrade.Core } } - namespace Stock - { - public class StockJournal : UnitOfWorkBase - { - public static void StockJournalDelete(string sqlConnectionString, int stockJournalId) - { - using (TransactionScope scope = new TransactionScope()) - using (SqlConnection conn = new SqlConnection(sqlConnectionString)) - { - conn.Open(); - - // get date for journal entry - DateTime entryDate; - int stockId; - using (SqlCommand cmd = new SqlCommand(@" - SELECT tblStockJournal.EntryDate, StockID - FROM tblStockJournal - WHERE (((tblStockJournal.StockJournalID)=@stockJournalId)); - ", conn)) - { - // add parameters - cmd.Parameters.AddWithValue("@stockJournalId", stockJournalId); - - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - { - entryDate = DateTime.SpecifyKind(reader.GetDateTime(0), DateTimeKind.Utc); - stockId = reader.GetInt32(1); - } - else - { - throw new Exception("StockJournalID=" + stockJournalId + " does not exist!"); - } - } - - } - - // is consistancy check required - bool consistancyCheck; - // build list of debits that are to be deleted - var debitList = new List(); - using (SqlCommand cmd = new SqlCommand(@" - SELECT tblStockJournalPost.StockStatusID - FROM tblStockJournalPost - WHERE (((tblStockJournalPost.StockJournalID)=@stockJournalId) AND ((tblStockJournalPost.Quantity)>0)); - ", conn)) - { - // add parameters - cmd.Parameters.AddWithValue("@stockJournalId", stockJournalId); - - using (var reader = cmd.ExecuteReader()) - { - if (reader.HasRows) - { - while (reader.Read()) - { - debitList.Add(reader.GetInt32(0)); - } - } - else - { - throw new Exception("StockJournalID=" + stockJournalId + " has no debits with quantity greater than zero!"); - } - } - } - - // check no credits for stockId & debit combination have been made since delete entry - string stringSql = @" - SELECT - tblStockJournal.EntryDate - FROM - tblStockJournal - INNER JOIN tblStockJournalPost - ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID - WHERE - tblStockJournal.StockID=@stockId - AND tblStockJournalPost.Quantity<0 - AND EntryDate>=@entryDate - AND ("; - - bool firstDone = false; - foreach (var item in debitList) - { - if (firstDone) - { - stringSql = stringSql + " OR "; - } - stringSql = stringSql + "tblStockJournalPost.StockStatusID=" + item; - firstDone = true; - } - stringSql = stringSql + ");"; - - using (SqlCommand cmd = new SqlCommand(stringSql, conn)) - { - cmd.Parameters.AddWithValue("@stockId", stockId); - cmd.Parameters.AddWithValue("@entryDate", entryDate.ToUniversalTime()); - - using (var reader = cmd.ExecuteReader()) - { - if (reader.HasRows) - { - consistancyCheck = true; - } - else - { - consistancyCheck = false; - } - } - } - - // delete the posts - StockJournalPostDelete(conn, stockJournalId); - - // delete journal entry - using (SqlCommand cmd = new SqlCommand(@" - DELETE FROM tblStockJournal - WHERE StockJournalID=@stockJournalId; - ", conn)) - { - // add parameters - cmd.Parameters.AddWithValue("@stockJournalId", stockJournalId); - - int count = cmd.ExecuteNonQuery(); - - if (count != 1) - { - throw new Exception("Failed to delete stock journal header."); - } - } - - // consistanct check - bool consistencyResult = true; - if (consistancyCheck) - { - // run check - consistencyResult = Stock.StockJournal.WIP_StockJournalConsistencyCheck(sqlConnectionString, stockId, debitList); - } - - if (consistencyResult) - { - // commit - scope.Complete(); - } - else - { - throw new Exception("Unable to delete stock journal entry, consistancy check failed."); - } - } - } - - private static void StockJournalPostInsert(SqlConnection conn, int stockId, int stockJournalId, - List<(int statusId, int quantity)> journalPosts, bool isNewStock = false) - { - //checks - if (journalPosts.Count > 2) - { - // I have purposely made the code to accept split transaction incase of future requirements, however, db design is simpler this way. - throw new Exception("Stock journal does not currently support split transactions (greater than two posts)." + journalPosts.Count + " number posts attempted."); - } - else if (journalPosts.Count < 2) - { - // list not long enough - throw new Exception("Stock journal entry requires minium of two posts, entry of " + journalPosts.Count + " number posts attempted."); - } - - if (journalPosts.Sum(item => item.quantity) != 0) - { - // credits and debits do not match - throw new Exception("Sum of credits and debits do not resolve to zero."); - } - - // group credits and debits by status - var dicStatusQty = new Dictionary(); - foreach (var post in journalPosts) - { - if (dicStatusQty.ContainsKey(post.statusId) == false) - { - dicStatusQty.Add(post.statusId, post.quantity); - } - else - { - dicStatusQty[post.statusId] = dicStatusQty[post.statusId] + post.quantity; - } - } - - // get isCreditOnly for each status - var dicStatusIsCreditOnly = new Dictionary(); - foreach (var item in dicStatusQty) - { - using (SqlCommand cmd = new SqlCommand(@" - SELECT IsCreditOnly FROM tblStockStatus WHERE StockStatusID=@statusId; - ", conn)) - { - cmd.Parameters.AddWithValue("@statusId", item.Key); - - dicStatusIsCreditOnly.Add(item.Key, (bool)cmd.ExecuteScalar()); - } - } - - // check there is only one IsCreditOnly in list and it is allowed in this instance - int isCreditOnlyCount = 0; - foreach (var item in dicStatusIsCreditOnly) - { - if (item.Value) - { - isCreditOnlyCount = isCreditOnlyCount + 1; - } - } - if (isNewStock == false && isCreditOnlyCount > 0) - { - throw new Exception("Attempted credit or debit to 'Is Credit Only' status not allowed, in this instance."); - } - if (isNewStock == true && isCreditOnlyCount != 1) - { - throw new Exception("StockID=" + stockId + ", each stock line must have only have one IsCreditOnly=True status assigned to it."); - } - - // ensure debit (or zero credit) isn't made to credit only status - // need to do this check via original post list (i.e. journalPosts) - foreach (var post in journalPosts) - { - // debit check - if (post.quantity >= 0) - { - if (dicStatusIsCreditOnly[post.statusId] == true) - { - throw new Exception("Cannot make debit, or zero quantity credit, to credit only status. StatusID=" + post.statusId); - } - } - } - - // balance check for any credits (that are not isCreditOnly=true) - foreach (var item in dicStatusQty) - { - if (item.Value < 0 && dicStatusIsCreditOnly[item.Key] == false) - { - int quantity = new Data.Database.Stock.ReadStatusBalance().ReadStatusBalanceByStockId(stockId, item.Key); - - if (quantity + item.Value < 0) - { - throw new Exception("Credit status balance check failed. Available balance " + quantity + ", attempted credit " + item.Value * -1 + "."); - } - } - } - - // get this far... - // insert journal posts into database - foreach (var post in journalPosts) - { - using (SqlCommand cmd = new SqlCommand(@" - INSERT INTO tblStockJournalPost ( StockJournalID, StockStatusID, Quantity ) - VALUES ( @StockJournalId, @stockStatudId, @quantity ); - ", conn)) - { - cmd.Parameters.AddWithValue("@StockJournalId", stockJournalId); - cmd.Parameters.AddWithValue("@stockStatudId", post.statusId); - cmd.Parameters.AddWithValue("@quantity", post.quantity); - - // execute - cmd.ExecuteNonQuery(); - } - } - } - - private static void StockJournalPostDelete(SqlConnection conn, int stockJournalId) - { - using (SqlCommand cmd = new SqlCommand(@" - DELETE FROM tblStockJournalPost - WHERE StockJournalID=@stockJournalId - ", conn)) - { - cmd.Parameters.AddWithValue("@StockJournalId", stockJournalId); - - // execute - cmd.ExecuteNonQuery(); - - // the calling method must compete any transaction-scope on the connection - } - } - - // can be used before commiting an sql insert, update or delete to the stock journal to ensure a status does not fall below 0 - // (unless the status is enabled to do so) - // set empty list or statusIdEffected to null to check entier stock entries for consistency - public static bool WIP_StockJournalConsistencyCheck(string sqlConnectionString, int stockId, List statusIdEffected = null) - { - if (statusIdEffected == null) - { - statusIdEffected = new List(); - } - - - using (SqlConnection conn = new SqlConnection(sqlConnectionString)) - { - conn.Open(); - - // if no list supplied, build list of all used status' for stockId - if (statusIdEffected.Count == 0) - { - using (SqlCommand cmd = new SqlCommand(@" - SELECT - tblStockJournalPost.StockStatusID - FROM - tblStockJournal - INNER JOIN tblStockJournalPost - ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID - WHERE - tblStockJournal.StockID=@stockId - GROUP BY - tblStockJournalPost.StockStatusID; - ", conn)) - { - cmd.Parameters.AddWithValue("@stockId", stockId); - - using (var reader = cmd.ExecuteReader()) - { - if (reader.HasRows) - { - while (reader.Read()) - { - statusIdEffected.Add(reader.GetInt32(0)); - } - } - else - { - throw new Exception("No stock journal entries exist for StockID=" + stockId); - } - } - } - } - - // build the sql string to build creditCreate bool - var dicStatusCreditOnly = new Dictionary(); - string sqlString = @" - SELECT - tblStockStatus.StockStatusID, tblStockStatus.IsCreditOnly - FROM - tblStockStatus "; - - for (var i = 0; i < statusIdEffected.Count; i++) - { - if (i == 0) - { - sqlString = sqlString + " WHERE tblStockStatus.StockStatusID=" + statusIdEffected[i]; - } - else - { - sqlString = sqlString + " OR tblStockStatus.StockStatusID=" + statusIdEffected[i]; - } - - //if (i == (statusIdEffected.Count - 1)) - //{ - // sqlString = sqlString + ";"; - //} - } - sqlString = sqlString + " GROUP BY tblStockStatus.StockStatusID, tblStockStatus.IsCreditOnly;"; - - // run query & build dictionaries - using (SqlCommand cmd = new SqlCommand(sqlString, conn)) - { - cmd.Parameters.AddWithValue("@stockId", stockId); - - using (SqlDataReader reader = cmd.ExecuteReader()) - { - if (reader.Read()) - { - dicStatusCreditOnly.Add(reader.GetInt32(0), reader.GetBoolean(1)); - - while (reader.Read()) - { - dicStatusCreditOnly.Add(reader.GetInt32(0), reader.GetBoolean(1)); - } - } - else - { - throw new Exception("Error, no journal entries found for StockID=" + stockId); - } - } - } - - // check integrity of supplied statusIds - foreach (int statusId in statusIdEffected) - { - if (!dicStatusCreditOnly.ContainsKey(statusId)) - { - throw new Exception("Supplied StatusId (" + statusId + ") doesn't exist for StockId=" + stockId); - } - } - - // loop through each statudId and check integrity, if createdEnabled=false - foreach (int statudId in statusIdEffected) - { - if (dicStatusCreditOnly[statudId] == false) - { - using (SqlCommand cmd = new SqlCommand(@" - SELECT - tblStockJournal.EntryDate, tblStockJournalPost.Quantity - FROM - tblStockJournal - INNER JOIN tblStockJournalPost - ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID - WHERE - tblStockJournal.StockID=@stockId - AND tblStockJournalPost.StockStatusID=@statudId - ORDER BY - tblStockJournal.EntryDate; - ", conn)) - { - cmd.Parameters.AddWithValue("@stockId", stockId); - cmd.Parameters.AddWithValue("@statudId", statudId); - - using (SqlDataReader reader = cmd.ExecuteReader()) - { - // read first line into variables - reader.Read(); - int quantity = reader.GetInt32(1); - DateTime entryDate = DateTime.SpecifyKind(reader.GetDateTime(0), DateTimeKind.Utc); - - while (true) - { - // compare to next values - if (reader.Read()) - { - DateTime nextEntryDate = DateTime.SpecifyKind(reader.GetDateTime(0), DateTimeKind.Utc); - // don't check quantites for transactions on same date - if (entryDate == nextEntryDate) - { - entryDate = nextEntryDate; - quantity = quantity + reader.GetInt32(1); - } - // check if dates are different - else - { - if (quantity < 0) - { - return false; - } - else - { - entryDate = nextEntryDate; - quantity = quantity + reader.GetInt32(1); - } - } - } - // end if no records, check quantity - else - { - if (quantity < 0) - { - return false; - } - break; - } - } - } - } - } - } - } - // get this far, all good - return true; - } - - public static int StockTransactionTypeIdInsert(string sqlConnectionString, int stockJournalTypeId, string typeCode, bool transScopeSuppress = true) - { - int transactionTypeId; - var scopeOption = new TransactionScopeOption(); - if (transScopeSuppress) - { - scopeOption = TransactionScopeOption.Suppress; - } - else - { - scopeOption = TransactionScopeOption.Required; - } - - using (TransactionScope scope = new TransactionScope(scopeOption)) - using (SqlConnection conn = new SqlConnection(sqlConnectionString)) - { - conn.Open(); - //check to see if type already exists, return id of that if it does - using (SqlCommand cmd = new SqlCommand(@" - SELECT - tblStockSkuTransactionType.StockSkuTransactionTypeID - FROM - tblStockSkuTransactionType - WHERE - tblStockSkuTransactionType.StockJournalTypeID=@stockJournalTypeId - AND tblStockSkuTransactionType.TypeCode=@typeCode; - ", conn)) - { - cmd.Parameters.AddWithValue("@typeCode", typeCode); - cmd.Parameters.AddWithValue("@stockJournalTypeId", stockJournalTypeId); - - object obj = cmd.ExecuteScalar(); - - if (!(obj == null || obj == DBNull.Value)) - { - return (int)obj; - } - } - - // insert new and retrive new value - using (SqlCommand cmd = new SqlCommand(@" - INSERT INTO tblStockSkuTransactionType - ( TypeName, StockJournalTypeID, TypeCode ) - OUTPUT - INSERTED.StockSkuTransactionTypeID - VALUES - ( @typeName, @stockJournalTypeId, @typeCode ); - ", conn)) - { - cmd.Parameters.AddWithValue("@typeName", typeCode); - cmd.Parameters.AddWithValue("@typeCode", typeCode); - cmd.Parameters.AddWithValue("@stockJournalTypeId", stockJournalTypeId); - - transactionTypeId = (int)cmd.ExecuteScalar(); - } - - scope.Complete(); - } - return transactionTypeId; - } - } - } - public class MiscFunction { public static string TraceMessage(