diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs index de547d6..2d4438d 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs @@ -77,13 +77,9 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation // Read // - public Dictionary ReadStockJournal(List stockJournalIdList) + public Dictionary ReadStockJournal( + List stockJournalIds = null, List stockIds = null, List stockNumbers = null, DateTime? minEntryDate = null, DateTime? maxEntryDate = null) { - if (stockJournalIdList == null || !stockJournalIdList.Any()) - { - throw new ArgumentException("Stock journal ID list cannot be null or empty.", nameof(stockJournalIdList)); - } - var returnDict = new Dictionary(); var sqlWhere = new SqlWhereBuilder(); @@ -109,9 +105,36 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID WHERE 1=1 "; - if (stockJournalIdList != null && stockJournalIdList.Any()) + // build where clause based on provided filters + bool noFilter = true; + if (stockJournalIds != null && stockJournalIds.Any()) { - sqlWhere.In("tblStockJournal.StockJournalID", stockJournalIdList, "AND"); + sqlWhere.In("tblStockJournal.StockJournalID", stockJournalIds, "AND"); + noFilter = false; + } + if (stockIds != null && stockIds.Any()) + { + sqlWhere.In("tblStockJournal.StockID", stockIds, "AND"); + noFilter = false; + } + if (stockNumbers != null && stockNumbers.Any()) + { + sqlWhere.In("tblStock.StockNumber", stockNumbers, "AND"); + noFilter = false; + } + if (minEntryDate.HasValue) + { + sqlWhere.IsEqualToOrGreaterThan("tblStockJournal.EntryDate", minEntryDate.Value.ToUniversalTime(), "AND"); + noFilter = false; + } + if (maxEntryDate.HasValue) + { + sqlWhere.IsEqualToOrLessThan("tblStockJournal.EntryDate", maxEntryDate.Value.ToUniversalTime(), "AND"); + noFilter = false; + } + if (noFilter) + { + throw new ArgumentException("At least one filter must be provided for stock journal retrieval."); } sql += sqlWhere.SqlWhereString + " ORDER BY tblStockJournal.EntryDate, tblStockJournal.StockJournalID, tblStockJournalPost.StockJournalPostID;"; @@ -166,6 +189,60 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation } } + public List ReadStockJournalIdByStatus(int stockId, List stockStatusIds = null) + { + if (stockId <= 0) + { + throw new ArgumentException("Stock ID must be greater than zero.", nameof(stockId)); + } + if (stockStatusIds != null && stockStatusIds.Any() && stockStatusIds.Any(id => id <= 0)) + { + throw new ArgumentException("Stock status IDs must be greater than zero.", nameof(stockStatusIds)); + } + + var returnDict = new Dictionary(); + var sqlWhere = new SqlWhereBuilder(); + + string sql = @" + SELECT tblStockJournal.StockJournalID, + FROM tblStockJournal + LEFT OUTER JOIN + tblStockJournalPost + ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID + WHERE 1=1 "; + + sqlWhere.IsEqualTo("tblStockJournal.StockID", stockId, "AND"); + + if (stockStatusIds != null && stockStatusIds.Any()) + { + sqlWhere.In("StockStatusID", stockStatusIds, "AND"); + } + + sql += sqlWhere.SqlWhereString + ";"; + + var idList = new List(); + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = sql; + + if (sqlWhere.ParameterListIsSet) + { + sqlWhere.AddParametersToSqlCommand(cmd); + } + + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + idList.Add(reader.GetInt32(0)); + } + } + } + + return idList; + } + public Dictionary ReadStockJournalType(List stockJournalTypeIdList) { var returnDict = new Dictionary(); @@ -460,6 +537,40 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation // Delete // + public void StockJournalDelete(int stockJournalId) + { + // delete posts + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" + DELETE FROM tblStockJournalPost + WHERE StockJournalID=@stockJournalId;"; + cmd.Parameters.AddWithValue("@StockJournalId", stockJournalId); + + // execute + cmd.ExecuteNonQuery(); + } + + // delete journal entry + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" + DELETE FROM tblStockJournal + WHERE StockJournalID=@stockJournalId;"; + + // add parameters + cmd.Parameters.AddWithValue("@stockJournalId", stockJournalId); + + int count = cmd.ExecuteNonQuery(); + + if (count != 1) + { + throw new Exception("Failed to delete stock journal header."); + } + } + } } } diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs index dc5a27d..02b26e0 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs @@ -10,7 +10,8 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface { int InsertStockJournalHeader(int stockId, int journalTypeId, DateTime entryDate, bool isLocked); int InsertStockJournalPost(int stockJournalId, int stockStatusId, int quantity); - Dictionary ReadStockJournal(List stockJournalIdList); + Dictionary ReadStockJournal(List stockJournalIds = null, List stockIds = null, List stockNumbers = null, DateTime? minEntryDate = null, DateTime? maxEntryDate = null); + List ReadStockJournalIdByStatus(int stockId, List stockStatusIds = null); Dictionary ReadStockJournalType(List stockJournalTypeIdList); (int, DateTime) ReadJournalStockIdAndEntryDate(int stockJournalId); int ReadJournalTypeIdByStockId(int stockId); @@ -20,5 +21,6 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface int ReadJournalEntryCountByStockId(int stockId); int? ReadTypeIdStatusCreditId(int stockJournalTypeId); DateTime? ReadMostRecentEntryDateForStatusDebit(int stockId, List stockStatusIdList); + void StockJournalDelete(int stockJournalId); } } diff --git a/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs b/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs index ea498f0..72833b9 100644 --- a/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs +++ b/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs @@ -192,35 +192,58 @@ namespace bnhtrade.Core.Data.Database } - public void IsEqualTo(string columnReference, bool booleanValue, string wherePrefix = null) + //public void IsEqualTo(string columnReference, bool booleanValue, string wherePrefix = null) + //{ + // string sqlWhereString = @" + // "; + + // if (wherePrefix != null) + // { + // sqlWhereString += wherePrefix; + // } + + // sqlWhereString += " ( " + columnReference + " = "; + + // if (booleanValue) + // { + // sqlWhereString += "1 ) "; + // } + // else + // { + // sqlWhereString += "0 ) "; + // } + + // SqlWhereString = SqlWhereString + sqlWhereString; + //} + + public void IsEqualTo(string columnReference, object whereArgument, string wherePrefix = null) { - string sqlWhereString = @" - "; - - if (wherePrefix != null) - { - sqlWhereString += wherePrefix; - } - - sqlWhereString += " ( " + columnReference + " = "; - - if (booleanValue) - { - sqlWhereString += "1 ) "; - } - else - { - sqlWhereString += "0 ) "; - } - - SqlWhereString = SqlWhereString + sqlWhereString; + IsEqualSub(columnReference, whereArgument, "=", wherePrefix); } - public void IsEqualTo(string columnReference, string stringValue, string wherePrefix = null) + public void IsEqualToOrLessThan(string columnReference, object whereArgument, string wherePrefix = null) { - if (string.IsNullOrEmpty(stringValue) || string.IsNullOrEmpty(columnReference)) + IsEqualSub(columnReference, whereArgument, "<=", wherePrefix); + } + + public void IsEqualToOrGreaterThan(string columnReference, object whereArgument, string wherePrefix = null) + { + IsEqualSub(columnReference, whereArgument, ">=", wherePrefix); + } + + private void IsEqualSub(string columnReference, object whereArgument, string operatorString, string wherePrefix = null) + { + if (string.IsNullOrEmpty(columnReference)) { - throw new Exception(wherePrefix + " IsEqualTo method requires a valid column reference and string value."); + throw new Exception(wherePrefix + " IsEqual method requires a valid column reference."); + } + if (whereArgument == null) + { + throw new Exception(wherePrefix + " IsEqual method requires a valid where argument."); + } + if (whereArgument is string && string.IsNullOrEmpty(whereArgument.ToString())) + { + throw new Exception(wherePrefix + " IsEqual method requires a valid where argument."); } string sqlWhereString = @" @@ -230,13 +253,11 @@ namespace bnhtrade.Core.Data.Database { sqlWhereString += wherePrefix; } - - sqlWhereString += " ( " + columnReference + " = " + GetSetParameter(stringValue) + " ) "; + sqlWhereString += " ( " + columnReference + " " + operatorString + " " + GetSetParameter(whereArgument) + " ) "; SqlWhereString = SqlWhereString + sqlWhereString; } - /// /// Append an 'In' statement and parameter list to the class properties /// @@ -361,7 +382,7 @@ namespace bnhtrade.Core.Data.Database /// /// parameter string that is then appended to the SQL statement /// - private string GetSetParameter(string value) + private string GetSetParameter(object value) { parameterCount++; string parameterString = "@parameter" + parameterCount; diff --git a/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs b/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs index 9a09ec8..c064b7e 100644 --- a/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs +++ b/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs @@ -1,6 +1,7 @@ using Amazon.Runtime.Internal.Transform; using bnhtrade.Core.Data.Database.UnitOfWork; using bnhtrade.Core.Logic.Account; +using bnhtrade.Core.Model.Account; using System; using System.Collections.Generic; using System.Linq; @@ -185,7 +186,7 @@ namespace bnhtrade.Core.Logic.Inventory } } // run check - consistencyResult = WIP_StockJournalConsistencyCheck(stockId, statusIdEffected); + consistencyResult = WIP_StockJournalConsistencyCheck(uow, stockId, statusIdEffected); } if (consistencyResult) @@ -298,157 +299,99 @@ namespace bnhtrade.Core.Logic.Inventory return postIdList.Count(); } - public void StockJournalDelete(int stockJournalId) + public bool StockJournalDelete(int stockJournalId) { - WithUnitOfWork(uow => + return WithUnitOfWork(uow => { - // get date for journal entry - var idAndDate = uow.StockJournalRepository.ReadJournalStockIdAndEntryDate(stockJournalId); - DateTime entryDate = idAndDate.Item2; - int stockId = idAndDate.Item1; + var result = StockJournalDelete(uow, stockJournalId); - // 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)) + if (result) { - // 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(); + CommitIfOwned(uow); + return true; } else { - throw new Exception("Unable to delete stock journal entry, consistancy check failed."); + return false; } }); } - private void StockJournalPostDelete(int stockJournalId) + private bool StockJournalDelete(IUnitOfWork uow, int stockJournalId) { - using (SqlCommand cmd = new SqlCommand(@" - DELETE FROM tblStockJournalPost - WHERE StockJournalID=@stockJournalId - ", conn)) + // get journal entry + var stockJournalDict = uow.StockJournalRepository.ReadStockJournal(new List { stockJournalId }); + if (stockJournalDict.Count == 0) { - cmd.Parameters.AddWithValue("@StockJournalId", stockJournalId); + throw new Exception("StockJournalID=" + stockJournalId + " does not exist!"); + } + var stockJournal = stockJournalDict[stockJournalId]; - // execute - cmd.ExecuteNonQuery(); + // build list of debits to check + var debitStatusIds = new List(); + foreach (var post in stockJournal.StockJournalBuilderPosts) + { + if (post.IsDebit) + { + debitStatusIds.Add(post.StatusId); + } + } - // the calling method must compete any transaction-scope on the connection + // get all journal entries for stockId and debit status combination where entryDate >= stockJournal.EntryDate + var journalList = uow.StockJournalRepository.ReadStockJournal( + uow.StockJournalRepository.ReadStockJournalIdByStatus(stockJournal.StockId, debitStatusIds) + , new List { stockJournal.StockId } + , null + , stockJournal.EntryDate + ); + + // check no credits for stockId & debit status combination have been made since delete entry + bool consistancyCheckRequired = false; + foreach (var item in journalList.Values) + { + foreach (var post in item.StockJournalBuilderPosts) + { + if (post.Quantity < 0 && debitStatusIds.Contains(post.StatusId)) + { + consistancyCheckRequired = true; + } + } + } + + // make the delete + uow.StockJournalRepository.StockJournalDelete(stockJournalId); + + // consistanct check + bool consistencyResult = true; + if (consistancyCheckRequired) + { + // run check + consistencyResult = WIP_StockJournalConsistencyCheck(uow, stockJournal.StockId, debitStatusIds); + } + + if (consistencyResult) + { + return true; + } + else + { + uow.Rollback(); + return false; } } - // 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) + public bool WIP_StockJournalConsistencyCheck(IUnitOfWork uow, int stockId, List statusIdsEffected = null) { - if (statusIdEffected == null) + if (statusIdsEffected == null) { - statusIdEffected = new List(); + statusIdsEffected = new List(); } // if no list supplied, build list of all used status' for stockId - if (statusIdEffected.Count == 0) + if (statusIdsEffected.Count == 0) { using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) { @@ -473,7 +416,7 @@ namespace bnhtrade.Core.Logic.Inventory { while (reader.Read()) { - statusIdEffected.Add(reader.GetInt32(0)); + statusIdsEffected.Add(reader.GetInt32(0)); } } else @@ -491,15 +434,15 @@ namespace bnhtrade.Core.Logic.Inventory FROM tblStockStatus "; - for (var i = 0; i < statusIdEffected.Count; i++) + for (var i = 0; i < statusIdsEffected.Count; i++) { if (i == 0) { - sqlString = sqlString + " WHERE tblStockStatus.StockStatusID=" + statusIdEffected[i]; + sqlString = sqlString + " WHERE tblStockStatus.StockStatusID=" + statusIdsEffected[i]; } else { - sqlString = sqlString + " OR tblStockStatus.StockStatusID=" + statusIdEffected[i]; + sqlString = sqlString + " OR tblStockStatus.StockStatusID=" + statusIdsEffected[i]; } //if (i == (statusIdEffected.Count - 1)) @@ -535,7 +478,7 @@ namespace bnhtrade.Core.Logic.Inventory } // check integrity of supplied statusIds - foreach (int statusId in statusIdEffected) + foreach (int statusId in statusIdsEffected) { if (!dicStatusCreditOnly.ContainsKey(statusId)) { @@ -544,7 +487,7 @@ namespace bnhtrade.Core.Logic.Inventory } // loop through each statudId and check integrity, if createdEnabled=false - foreach (int statudId in statusIdEffected) + foreach (int statudId in statusIdsEffected) { if (dicStatusCreditOnly[statudId] == false) { diff --git a/src/bnhtrade.Core/Model/Stock/StockJournal.cs b/src/bnhtrade.Core/Model/Stock/StockJournal.cs index d64ce43..3831e53 100644 --- a/src/bnhtrade.Core/Model/Stock/StockJournal.cs +++ b/src/bnhtrade.Core/Model/Stock/StockJournal.cs @@ -87,18 +87,7 @@ namespace bnhtrade.Core.Model.Stock { get { - if (Quantity > 0) - { - return true; - } - else if (Quantity < 0) - { - return false; - } - else - { - throw new InvalidOperationException("Quantity cannot be zero for a journal post."); - } + return !IsCredit; } } diff --git a/src/bnhtrade.Core/Model/Stock/StockJournalBuilder.cs b/src/bnhtrade.Core/Model/Stock/StockJournalBuilder.cs index ffe4cb7..1d8756a 100644 --- a/src/bnhtrade.Core/Model/Stock/StockJournalBuilder.cs +++ b/src/bnhtrade.Core/Model/Stock/StockJournalBuilder.cs @@ -38,6 +38,33 @@ namespace bnhtrade.Core.Model.Stock public int StatusId { get; set; } public int Quantity { get; set; } + + public bool IsCredit + { + get + { + if (Quantity < 0) + { + return true; // Negative quantity indicates a credit post + } + else if (Quantity > 0) + { + return false; // Positive quantity indicates a debit post + } + else + { + throw new InvalidOperationException("Quantity cannot be zero for a stock journal post."); + } + } + } + + public bool IsDebit + { + get + { + return !IsCredit; // Debit is the opposite of credit + } + } } public StockJournal Build(Dictionary stockJournalTypeDict, Dictionary stockStatusDict)