From c28d2c6060c61f8095113be2917627598a8ff5b6 Mon Sep 17 00:00:00 2001 From: Bob Hodgetts Date: Tue, 15 Jul 2025 12:20:55 +0100 Subject: [PATCH] wip --- .../Implementation/StockJournalRepository.cs | 29 ++- .../Data/Database/SqlWhereBuilder.cs | 241 +++++++++++++++--- .../Logic/Inventory/StockJournalService.cs | 5 +- 3 files changed, 232 insertions(+), 43 deletions(-) diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs index 2d4438d..1998cad 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs @@ -78,7 +78,8 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation // public Dictionary ReadStockJournal( - List stockJournalIds = null, List stockIds = null, List stockNumbers = null, DateTime? minEntryDate = null, DateTime? maxEntryDate = null) + List stockJournalIds = null, List stockIds = null, List stockNumbers = null + , DateTime? minEntryDate = null, DateTime? maxEntryDate = null, List stockStatusIds = null) { var returnDict = new Dictionary(); var sqlWhere = new SqlWhereBuilder(); @@ -97,39 +98,41 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation tblStockJournalPost.StockStatusID, tblStockJournalPost.Quantity, FROM tblStockJournal - LEFT OUTER JOIN - tblStock - ON tblStockJournal.StockID = tblStock.StockID - LEFT OUTER JOIN - tblStockJournalPost - ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID + LEFT OUTER JOIN tblStock ON tblStockJournal.StockID = tblStock.StockID + LEFT OUTER JOIN tblStockJournalPost ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID WHERE 1=1 "; // build where clause based on provided filters bool noFilter = true; if (stockJournalIds != null && stockJournalIds.Any()) { - sqlWhere.In("tblStockJournal.StockJournalID", stockJournalIds, "AND"); + sql += sqlWhere.InClause("tblStockJournal.StockJournalID", stockJournalIds, "AND"); noFilter = false; } if (stockIds != null && stockIds.Any()) { - sqlWhere.In("tblStockJournal.StockID", stockIds, "AND"); + sql += sqlWhere.InClause("tblStockJournal.StockID", stockIds, "AND"); noFilter = false; } if (stockNumbers != null && stockNumbers.Any()) { - sqlWhere.In("tblStock.StockNumber", stockNumbers, "AND"); + sql += sqlWhere.InClause("tblStock.StockNumber", stockNumbers, "AND"); noFilter = false; } if (minEntryDate.HasValue) { - sqlWhere.IsEqualToOrGreaterThan("tblStockJournal.EntryDate", minEntryDate.Value.ToUniversalTime(), "AND"); + sql += sqlWhere.IsEqualToOrGreaterThanClause("tblStockJournal.EntryDate", minEntryDate.Value.ToUniversalTime(), "AND"); noFilter = false; } if (maxEntryDate.HasValue) { - sqlWhere.IsEqualToOrLessThan("tblStockJournal.EntryDate", maxEntryDate.Value.ToUniversalTime(), "AND"); + sql += sqlWhere.IsEqualToOrLessThanClause("tblStockJournal.EntryDate", maxEntryDate.Value.ToUniversalTime(), "AND"); + noFilter = false; + } + if (stockStatusIds != null && stockStatusIds.Any()) + { + sql = sql + Environment.NewLine + " AND tblStockJournal.StockJournalID IN (SELECT StockJournalID FROM tblStockJournalPost WHERE " + + sqlWhere.InClause("StockStatusID", stockStatusIds) + " ) "; noFilter = false; } if (noFilter) @@ -137,7 +140,7 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation 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;"; + sql += " ORDER BY tblStockJournal.EntryDate, tblStockJournal.StockJournalID, tblStockJournalPost.StockJournalPostID;"; using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) { diff --git a/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs b/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs index 72833b9..62f8c9f 100644 --- a/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs +++ b/src/bnhtrade.Core/Data/Database/SqlWhereBuilder.cs @@ -10,17 +10,15 @@ using Microsoft.IdentityModel.Tokens; namespace bnhtrade.Core.Data.Database { /// - /// Step 1: Call the methods for each where clause you want to create. This can be done multiple times to create an sql where string. Pay attention - /// to the prefixes that you'll require between each where clause, as each time a method is called the sql statement will be appended to the previous sql - /// string. + /// Sep 1: Call each Where_____ method in this class as needed, the method returns a string that can be appended to your SQL statement and builds + /// a parameter list. /// - /// Step 2: Appened the created sql string to your sql statement, NB the WHERE statemet is not included by default. - /// - /// Step 3: Once you've created your sql command object, add the parameters to it using the method contained within this class. + /// Step 2: Once you've created your sql command object, add the parameters to it using the method contained within this class. /// /// Step 4: exceute your sql commend. /// - public class SqlWhereBuilder + + internal class SqlWhereBuilder { private int parameterCount = 0; @@ -29,8 +27,10 @@ namespace bnhtrade.Core.Data.Database Init(); } + [Obsolete("Deprecated. Use the new methods.")] public string SqlWhereString { get; private set; } + [Obsolete("Deprecated. Use the new methods.")] public bool IsSetSqlWhereString { get @@ -117,6 +117,7 @@ namespace bnhtrade.Core.Data.Database /// Name of the column to used to for the condition statement /// List of phrases to test in condition statement /// Optional prefix that gets added to the sql string result + [Obsolete("Deprecated. Use the new methods.")] public void LikeAnd(string columnReference, IEnumerable phraseList, string wherePrefix = null) { Like(columnReference, phraseList, true, wherePrefix); @@ -128,11 +129,13 @@ namespace bnhtrade.Core.Data.Database /// Name of the column to used to for the condition statement /// List of phrases to test in condition statement /// Optional prefix that gets added to the sql string result + [Obsolete("Deprecated. Use the new methods.")] public void LikeOr(string columnReference, IEnumerable phraseList, string wherePrefix = null) { Like(columnReference, phraseList, false, wherePrefix); } + [Obsolete("Deprecated. Use the new methods.")] private void Like(string columnReference, IEnumerable phraseList, bool isAnd, string wherePrefix = null) { if (phraseList == null || !phraseList.Any()) @@ -216,21 +219,25 @@ namespace bnhtrade.Core.Data.Database // SqlWhereString = SqlWhereString + sqlWhereString; //} + [Obsolete("Deprecated. Use the new methods.")] public void IsEqualTo(string columnReference, object whereArgument, string wherePrefix = null) { IsEqualSub(columnReference, whereArgument, "=", wherePrefix); } + [Obsolete("Deprecated. Use the new methods.")] public void IsEqualToOrLessThan(string columnReference, object whereArgument, string wherePrefix = null) { IsEqualSub(columnReference, whereArgument, "<=", wherePrefix); } + [Obsolete("Deprecated. Use the new methods.")] public void IsEqualToOrGreaterThan(string columnReference, object whereArgument, string wherePrefix = null) { IsEqualSub(columnReference, whereArgument, ">=", wherePrefix); } + [Obsolete("Deprecated. Use the new methods.")] private void IsEqualSub(string columnReference, object whereArgument, string operatorString, string wherePrefix = null) { if (string.IsNullOrEmpty(columnReference)) @@ -262,16 +269,17 @@ namespace bnhtrade.Core.Data.Database /// Append an 'In' statement and parameter list to the class properties /// /// Name of the column to used to for the condition statement - /// List of values to test in condition statement + /// List of values to test in condition statement /// Optional prefix that gets added to the sql string result - public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) + [Obsolete("Deprecated. Use the new methods.")] + public void In(string columnReference, IEnumerable orArgumentList, string wherePrefix = null) { - if (orValueList == null || !orValueList.Any()) + if (orArgumentList == null || !orArgumentList.Any()) { return; } - var distinctList = orValueList.Distinct().ToList(); + var distinctList = orArgumentList.Distinct().ToList(); string sqlWhere = @" "; @@ -302,15 +310,16 @@ namespace bnhtrade.Core.Data.Database /// Append an 'In' statement and parameter list to the class properties /// /// Name of the column to used to for the condition statement - /// List of values to test in condition statement + /// List of values to test in condition statement /// Optional prefix that gets added to the sql string result - public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) + [Obsolete("Deprecated. Use the new methods.")] + public void In(string columnReference, IEnumerable orArgumentList, string wherePrefix = null) { var objectList = new List(); - if (orValueList != null && orValueList.Any()) + if (orArgumentList != null && orArgumentList.Any()) { - foreach (string value in orValueList) + foreach (string value in orArgumentList) { objectList.Add(value.ToString()); } @@ -323,15 +332,16 @@ namespace bnhtrade.Core.Data.Database /// Append an 'In' statement and parameter list to the class properties /// /// Name of the column to used to for the condition statement - /// List of values to test in condition statement + /// List of values to test in condition statement /// Optional prefix that gets added to the sql string result - public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) + [Obsolete("Deprecated. Use the new methods.")] + public void In(string columnReference, IEnumerable orArgumentList, string wherePrefix = null) { var objectList = new List(); - if (orValueList != null && orValueList.Any()) + if (orArgumentList != null && orArgumentList.Any()) { - foreach (var value in orValueList) + foreach (var value in orArgumentList) { objectList.Add(value.ToString()); } @@ -344,15 +354,16 @@ namespace bnhtrade.Core.Data.Database /// Append an 'In' statement and parameter list to the class properties /// /// Name of the column to used to for the condition statement - /// List of values to test in condition statement + /// List of values to test in condition statement /// Optional prefix that gets added to the sql string result - public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) + [Obsolete("Deprecated. Use the new methods.")] + public void In(string columnReference, IEnumerable orArgumentList, string wherePrefix = null) { var objectList = new List(); - if (orValueList != null && orValueList.Any()) + if (orArgumentList != null && orArgumentList.Any()) { - foreach (var value in orValueList) + foreach (var value in orArgumentList) { objectList.Add(value.ToString()); } @@ -361,13 +372,14 @@ namespace bnhtrade.Core.Data.Database In(columnReference, objectList, wherePrefix); } - public void In(string columnReference, IEnumerable orValueList, string wherePrefix = null) + [Obsolete("Deprecated. Use the new methods.")] + public void In(string columnReference, IEnumerable orArgumentList, string wherePrefix = null) { var objectList = new List(); - if (orValueList != null && orValueList.Any()) + if (orArgumentList != null && orArgumentList.Any()) { - foreach(var value in orValueList) + foreach(var value in orArgumentList) { objectList.Add(value.ToString()); } @@ -376,17 +388,188 @@ namespace bnhtrade.Core.Data.Database In(columnReference, objectList, wherePrefix); } + // + // new code below, going to change everything over to these methods, it returns the sql string instead of appending it to the class property + // which is more versatile and more intuitive to use + // + + /// + /// Append an 'Like' statement (with AND between each like string) and parameter list to the class properties + /// + /// Name of the column to used to for the condition statement + /// List of phrases to test in condition statement + /// Optional prefix that gets added to the sql string result + public string LikeAndClause(string columnReference, IEnumerable arguments, string clausePrefix = null, string clausePostfix = null) + { + return LikeClause(columnReference, arguments, true, clausePrefix, clausePostfix); + } + + /// + /// Append an 'Like' statement (with OR between each like string) and parameter list to the class properties + /// + /// Name of the column to used to for the condition statement + /// List of phrases to test in condition statement + /// Optional prefix that gets added to the sql string result + public string LikeOrClause(string columnReference, IEnumerable arguments, string clausePrefix = null, string clausePostfix = null) + { + return LikeClause(columnReference, arguments, false, clausePrefix, clausePostfix); + } + + private string LikeClause(string columnReference, IEnumerable arguments, bool isAnd, string clausePrefix = null, string clausePostfix = null) + { + if (arguments == null || !arguments.Any()) + { + throw new ArgumentException("The arguments cannot be null or empty.", nameof(arguments)); + } + + // ensure no values are repeated + var distinctList = arguments.Distinct().ToList(); + + // clean the list + for (int i = 0; i < distinctList.Count; i++) + { + if (string.IsNullOrEmpty(distinctList[i])) + { + distinctList.RemoveAt(i); + i--; + } + } + + // check again + if (distinctList == null || !distinctList.Any()) + { + throw new ArgumentException("The arguments must contain at least one valid value.", nameof(arguments)); + } + + string sqlWhere = Environment.NewLine + clausePrefix + " "; + + + int listCount = distinctList.Count(); + for (int i = 0; i < listCount; i++) + { + if (i > 0) + { + if (isAnd) + { + sqlWhere += " AND "; + } + else + { + sqlWhere += " OR "; + } + } + + sqlWhere += " ( " + columnReference + " LIKE '%' + "; + + + sqlWhere = sqlWhere + GetSetParameter(distinctList[i]) + " + '%' ) "; + } + return sqlWhere + clausePostfix; + } + + + public string IsEqualToClause(string columnReference, object whereArgument, string clausePrefix = null, string clausePostfix = null) + { + return IsEqualSubClause(columnReference, whereArgument, "=", clausePrefix, clausePostfix); + } + + public string IsEqualToOrLessThanClause(string columnReference, object whereArgument, string clausePrefix = null, string clausePostfix = null) + { + return IsEqualSubClause(columnReference, whereArgument, "<=", clausePrefix, clausePostfix); + } + + public string IsEqualToOrGreaterThanClause(string columnReference, object whereArgument, string clausePrefix = null, string clausePostfix = null) + { + return IsEqualSubClause(columnReference, whereArgument, ">=", clausePrefix, clausePostfix); + } + + private string IsEqualSubClause(string columnReference, object whereArgument, string operatorString, string clausePrefix = null, string clausePostfix = null) + { + if (string.IsNullOrEmpty(columnReference)) + { + throw new Exception(" WhereIsEqual* method requires a valid column reference."); + } + if (whereArgument == null) + { + throw new Exception(" WhereIsEqual* method requires a valid where argument."); + } + if (whereArgument is string && string.IsNullOrEmpty(whereArgument.ToString())) + { + throw new Exception(" WhereIsEqual* method requires a valid where argument."); + } + + // build the SQL WHERE clause and parameter list + string sqlWhere = Environment.NewLine + clausePrefix + " ( " + columnReference + " " + operatorString + " " + GetSetParameter(whereArgument) + " ) "; + + return sqlWhere + clausePostfix; + } + + + /// + /// Constructs a SQL WHERE clause with an IN condition for the specified column and a list of values and adds to the parameter list. + /// + /// The name of the column to be used in the IN condition + /// A collection of values to include in the IN condition. + /// A boolean value indicating whether to prefix the returned SQL clause with a line return + /// A string representing the SQL WHERE clause with the specified column and values in the IN condition. + public string InClause(string columnReference, IEnumerable orArgumentList, string clausePrefix = null, string clausePostfix = null) + { + // new code, going to change everything over to this method, it returns the sql string instead of appending it to the class property + // which is more versatile and more intuitive to use + + if (string.IsNullOrEmpty(columnReference)) + { + throw new ArgumentException("Column reference cannot be null or empty.", nameof(columnReference)); + } + if (orArgumentList == null || !orArgumentList.Any()) + { + throw new ArgumentException("The orArgumentList cannot be null or empty.", nameof(orArgumentList)); + } + + // ensure no values are repeated, null, or empty + var distinctList = orArgumentList + .Where(x => + x is not null && // not null + (!(x.GetType().IsGenericType && x.GetType().GetGenericTypeDefinition() == typeof(Nullable<>)) // not a nullable type + || x.GetType().GetProperty("HasValue")?.GetValue(x) as bool? == true) // or, if nullable, HasValue is true + && (!(x is string s) || !string.IsNullOrEmpty(s)) // not an empty string + ) + .ToList(); + + if (!distinctList.Any()) + { + throw new ArgumentException("The orArgumentList must contain at least one valid value.", nameof(orArgumentList)); + } + + // build the SQL WHERE clause and parameter list + string sqlWhere = Environment.NewLine + clausePrefix + " " + columnReference + " IN ( "; + + int listCount = distinctList.Count(); + for (int i = 0; i < listCount; i++) + { + if (i > 0) + { + sqlWhere += ", "; + } + + sqlWhere = sqlWhere + GetSetParameter(distinctList[i]); + } + sqlWhere += " ) "; + + return sqlWhere + clausePostfix; + } + /// /// Adds a string value to the ParameterList and returns '@parameterN' that is then appended to the SQL statement /// i.e. @parameter1, @parameter2, etc. /// - /// parameter string that is then appended to the SQL statement + /// parameter string that is then appended to the SQL statement /// - private string GetSetParameter(object value) + private string GetSetParameter(object argument) { parameterCount++; string parameterString = "@parameter" + parameterCount; - ParameterList.Add(parameterString, value); + ParameterList.Add(parameterString, argument); return parameterString; } } diff --git a/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs b/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs index c064b7e..ef45802 100644 --- a/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs +++ b/src/bnhtrade.Core/Logic/Inventory/StockJournalService.cs @@ -317,6 +317,10 @@ namespace bnhtrade.Core.Logic.Inventory }); } + /// + /// To be used internally (private). Deletes a stock journal entry and performs a consistency check, does not roll back transaction on fail. + /// + /// consistantcy check result, true=good, false=bad private bool StockJournalDelete(IUnitOfWork uow, int stockJournalId) { // get journal entry @@ -375,7 +379,6 @@ namespace bnhtrade.Core.Logic.Inventory } else { - uow.Rollback(); return false; } }