This commit is contained in:
2025-07-14 20:28:40 +01:00
parent 2d751ebf80
commit ecf48ba8b4
6 changed files with 274 additions and 181 deletions

View File

@@ -77,13 +77,9 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
// Read
//
public Dictionary<int, Model.Stock.StockJournalBuilder> ReadStockJournal(List<int> stockJournalIdList)
public Dictionary<int, Model.Stock.StockJournalBuilder> ReadStockJournal(
List<int> stockJournalIds = null, List<int> stockIds = null, List<int> 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<int, Model.Stock.StockJournalBuilder>();
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<int> ReadStockJournalIdByStatus(int stockId, List<int> 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<int, Model.Stock.StockJournalBuilder>();
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<int>();
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<int, (int id, string title, int? stockStatusIdDebit, int? stockStatusIdCredit)> ReadStockJournalType(List<int> stockJournalTypeIdList)
{
var returnDict = new Dictionary<int, (int id, string title, int? stockStatusIdDebit, int? stockStatusIdCredit)>();
@@ -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.");
}
}
}
}
}

View File

@@ -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<int, Model.Stock.StockJournalBuilder> ReadStockJournal(List<int> stockJournalIdList);
Dictionary<int, Model.Stock.StockJournalBuilder> ReadStockJournal(List<int> stockJournalIds = null, List<int> stockIds = null, List<int> stockNumbers = null, DateTime? minEntryDate = null, DateTime? maxEntryDate = null);
List<int> ReadStockJournalIdByStatus(int stockId, List<int> stockStatusIds = null);
Dictionary<int, (int id, string title, int? stockStatusIdDebit, int? stockStatusIdCredit)> ReadStockJournalType(List<int> 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<int> stockStatusIdList);
void StockJournalDelete(int stockJournalId);
}
}

View File

@@ -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;
}
/// <summary>
/// Append an 'In' statement and parameter list to the class properties
/// </summary>
@@ -361,7 +382,7 @@ namespace bnhtrade.Core.Data.Database
/// </summary>
/// <param name="value">parameter string that is then appended to the SQL statement</param>
/// <returns></returns>
private string GetSetParameter(string value)
private string GetSetParameter(object value)
{
parameterCount++;
string parameterString = "@parameter" + parameterCount;

View File

@@ -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<int>();
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<int> { 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<int>();
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<int> { 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<int> statusIdEffected = null)
public bool WIP_StockJournalConsistencyCheck(IUnitOfWork uow, int stockId, List<int> statusIdsEffected = null)
{
if (statusIdEffected == null)
if (statusIdsEffected == null)
{
statusIdEffected = new List<int>();
statusIdsEffected = new List<int>();
}
// 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)
{

View File

@@ -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;
}
}

View File

@@ -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<int, StockJournalType> stockJournalTypeDict, Dictionary<int, Status> stockStatusDict)