mirror of
https://github.com/stokebob/bnhtrade.git
synced 2026-03-19 06:27:15 +00:00
wip
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using bnhtrade.Core.Data.Database._BoilerPlate;
|
||||
using bnhtrade.Core.Data.Database.Repository.Interface;
|
||||
using bnhtrade.Core.Model.Account;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -9,6 +10,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Transactions;
|
||||
using static bnhtrade.Core.Data.Database.Constants;
|
||||
using static bnhtrade.Core.Model.Account.Journal;
|
||||
|
||||
namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
||||
{
|
||||
@@ -22,84 +24,13 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
||||
// Create
|
||||
//
|
||||
|
||||
// only to be assessable via code, use stock relocate to move stock bewtween status'
|
||||
public int StockJournalInsert(int journalTypeId, int stockId, List<(int statusId, int quantity)> journalPosts,
|
||||
DateTime entryDate, bool isNewStock = false)
|
||||
public int InsertStockJournalHeader(int stockId, int journalTypeId, DateTime entryDate, bool isLocked)
|
||||
{
|
||||
/*
|
||||
* TODO: currently the consistancy check checks the journal after the entry has been inserted to the db, if the check fails
|
||||
* the transaction scope is disposed, and the ne journal entries roll back. However, if this is done within a higher
|
||||
* level transaction scope, this nested dispose() also rolls back the all transacopes scopes it is a child of.
|
||||
*
|
||||
* Therefore, a consistancy check needs to be simulated in code to negate the need to rollback/dispose of a db transaction.
|
||||
* This would also have some slight performance benefits.
|
||||
*
|
||||
* Once you've done this, fix the SkuTransactionReconcile class: Currently it's transactionscope only covers updates.
|
||||
* Need to set the scope to cover the intial table read (to lock the records). The issue above restricts this.
|
||||
*/
|
||||
|
||||
|
||||
// balance and status IsCredit checks made by post insert function
|
||||
|
||||
// create the journal entry
|
||||
int stockJournalId;
|
||||
//consitancy check is required?
|
||||
bool consistencyRequired = true;
|
||||
// get date of most recent debit for status' that I will be crediting
|
||||
if (isNewStock == false)
|
||||
if (entryDate.Kind != DateTimeKind.Utc)
|
||||
{
|
||||
// build sql string
|
||||
string stringSql = @"
|
||||
SELECT
|
||||
tblStockJournal.EntryDate
|
||||
FROM
|
||||
tblStockJournal
|
||||
INNER JOIN tblStockJournalPost
|
||||
ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID
|
||||
WHERE
|
||||
tblStockJournal.StockID=@stockId
|
||||
AND EntryDate>=@entryDate
|
||||
AND tblStockJournalPost.Quantity>0
|
||||
AND (";
|
||||
|
||||
bool firstDone = false;
|
||||
foreach (var item in journalPosts)
|
||||
{
|
||||
if (item.quantity < 0)
|
||||
{
|
||||
if (firstDone)
|
||||
{
|
||||
stringSql = stringSql + " OR ";
|
||||
}
|
||||
stringSql = stringSql + "tblStockJournalPost.StockStatusID=" + item.statusId;
|
||||
firstDone = true;
|
||||
}
|
||||
}
|
||||
stringSql = stringSql + ");";
|
||||
|
||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.CommandText = stringSql;
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
|
||||
cmd.Parameters.AddWithValue("@stockId", stockId);
|
||||
cmd.Parameters.AddWithValue("@entryDate", entryDate.ToUniversalTime());
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
if (reader.HasRows)
|
||||
{
|
||||
consistencyRequired = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
consistencyRequired = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new ArgumentException("Entry date must be in UTC format.", nameof(entryDate));
|
||||
}
|
||||
|
||||
// create journal entry
|
||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
@@ -111,43 +42,33 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
||||
cmd.Parameters.AddWithValue("@stockID", stockId);
|
||||
cmd.Parameters.AddWithValue("@journalTypeId", journalTypeId);
|
||||
cmd.Parameters.AddWithValue("@EntryDate", entryDate.ToUniversalTime());
|
||||
cmd.Parameters.AddWithValue("@isLocked", isNewStock);
|
||||
cmd.Parameters.AddWithValue("@isLocked", isLocked);
|
||||
|
||||
//execute
|
||||
stockJournalId = (int)cmd.ExecuteScalar();
|
||||
return (int)cmd.ExecuteScalar();
|
||||
}
|
||||
}
|
||||
|
||||
// insert journal posts into database
|
||||
//new Data.Database.Stock
|
||||
StockJournalPostInsert(stockId, stockJournalId, journalPosts, isNewStock);
|
||||
|
||||
// consistency check
|
||||
bool consistencyResult = true;
|
||||
if (consistencyRequired)
|
||||
public int InsertStockJournalPost(int stockJournalId, int stockStatusId, int quantity)
|
||||
{
|
||||
if (quantity == 0)
|
||||
{
|
||||
consistencyResult = false;
|
||||
// build list of effected status'
|
||||
var statusIdEffected = new List<int>();
|
||||
foreach (var item in journalPosts)
|
||||
{
|
||||
if (item.quantity < 0)
|
||||
{
|
||||
statusIdEffected.Add(item.statusId);
|
||||
}
|
||||
}
|
||||
// run check
|
||||
consistencyResult = Stock.StockJournal.WIP_StockJournalConsistencyCheck(sqlConnectionString, stockId, statusIdEffected);
|
||||
throw new ArgumentException("Quantity must be non-zero.", nameof(quantity));
|
||||
}
|
||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
cmd.CommandText = @"
|
||||
INSERT INTO tblStockJournalPost ( StockJournalID, StockStatusID, Quantity )
|
||||
OUTPUT INSERTED.StockJournalPostID
|
||||
VALUES ( @StockJournalId, @stockStatudId, @quantity );";
|
||||
|
||||
if (consistencyResult)
|
||||
{
|
||||
// commit
|
||||
scope.Complete();
|
||||
return stockJournalId;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unable to insert stock journal entry, consistancy check failed.");
|
||||
cmd.Parameters.AddWithValue("@StockJournalId", stockJournalId);
|
||||
cmd.Parameters.AddWithValue("@stockStatudId", stockStatusId);
|
||||
cmd.Parameters.AddWithValue("@quantity", quantity);
|
||||
|
||||
// execute
|
||||
return cmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +76,32 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
||||
// Read
|
||||
//
|
||||
|
||||
public (int, DateTime) ReadJournalStockIdAndEntryDate(int stockJournalId)
|
||||
{
|
||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
cmd.CommandText = @"
|
||||
SELECT tblStockJournal.EntryDate, StockID
|
||||
FROM tblStockJournal
|
||||
WHERE (((tblStockJournal.StockJournalID)=@stockJournalId));";
|
||||
|
||||
cmd.Parameters.AddWithValue("@stockJournalId", stockJournalId);
|
||||
|
||||
using (var reader = cmd.ExecuteReader())
|
||||
{
|
||||
if (reader.Read())
|
||||
{
|
||||
return (reader.GetInt32(1), DateTime.SpecifyKind(reader.GetDateTime(0), DateTimeKind.Utc));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("StockJournalID=" + stockJournalId + " does not exist!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int ReadJournalTypeIdByStockId(int stockId)
|
||||
{
|
||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
||||
@@ -322,6 +269,54 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
||||
|
||||
}
|
||||
|
||||
public DateTime? ReadMostRecentEntryDateForStatusDebit(int stockId, List<int> stockStatusIdList)
|
||||
{
|
||||
if (stockId <= 0)
|
||||
{
|
||||
throw new ArgumentException("Stock ID must be greater than zero.", nameof(stockId));
|
||||
}
|
||||
if (stockStatusIdList == null || stockStatusIdList.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Stock status ID list cannot be null or empty.", nameof(stockStatusIdList));
|
||||
}
|
||||
|
||||
var sqlWhere = new SqlWhereBuilder();
|
||||
|
||||
// build sql string
|
||||
string stringSql = @"
|
||||
MAX (tblStockJournal.EntryDate) AS MostRecentEntryDate
|
||||
FROM
|
||||
tblStockJournal
|
||||
INNER JOIN tblStockJournalPost
|
||||
ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID
|
||||
WHERE
|
||||
tblStockJournal.StockID=@stockId
|
||||
AND tblStockJournalPost.Quantity>0 ";
|
||||
|
||||
sqlWhere.In("tblStockJournalPost.StockStatusID", stockStatusIdList, "AND");
|
||||
|
||||
stringSql = stringSql + sqlWhere.SqlWhereString;
|
||||
|
||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.CommandText = stringSql;
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
|
||||
cmd.Parameters.AddWithValue("@stockId", stockId);
|
||||
sqlWhere.AddParametersToSqlCommand(cmd);
|
||||
|
||||
object obj = cmd.ExecuteScalar();
|
||||
if (obj == null || obj == DBNull.Value)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DateTime.SpecifyKind((DateTime)obj, DateTimeKind.Utc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// update
|
||||
//
|
||||
@@ -331,454 +326,6 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
||||
// Delete
|
||||
//
|
||||
|
||||
public void StockJournalDelete(int stockJournalId)
|
||||
{
|
||||
// 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<int>();
|
||||
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 void StockJournalPostInsert(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<int, int>();
|
||||
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<int, bool>();
|
||||
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 void StockJournalPostDelete(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
|
||||
}
|
||||
}
|
||||
|
||||
public bool WIP_StockJournalConsistencyCheck( int stockId, List<int> statusIdEffected = null)
|
||||
{
|
||||
if (statusIdEffected == null)
|
||||
{
|
||||
statusIdEffected = new List<int>();
|
||||
}
|
||||
|
||||
// if no list supplied, build list of all used status' for stockId
|
||||
if (statusIdEffected.Count == 0)
|
||||
{
|
||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
cmd.CommandText = @"
|
||||
SELECT
|
||||
tblStockJournalPost.StockStatusID
|
||||
FROM
|
||||
tblStockJournal
|
||||
INNER JOIN tblStockJournalPost
|
||||
ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID
|
||||
WHERE
|
||||
tblStockJournal.StockID=@stockId
|
||||
GROUP BY
|
||||
tblStockJournalPost.StockStatusID;";
|
||||
|
||||
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<int, bool>();
|
||||
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 = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
cmd.CommandText = sqlString;
|
||||
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 = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
cmd.CommandText = @"
|
||||
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;";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
||||
{
|
||||
}
|
||||
|
||||
public List<Model.Stock.Status> ReadStatus(List<int> statusIds = null, List<int> statusTypeIds = null)
|
||||
public Dictionary<int, Model.Stock.Status> ReadStatus(List<int> statusIds = null, List<int> statusTypeIds = null)
|
||||
{
|
||||
var sqlBuilder = new SqlWhereBuilder();
|
||||
var returnList = new List<Model.Stock.Status>();
|
||||
var returnList = new Dictionary<int, Model.Stock.Status>();
|
||||
|
||||
//build sql query
|
||||
string sql = @"
|
||||
@@ -91,7 +91,7 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
||||
, recordCreated
|
||||
);
|
||||
|
||||
returnList.Add(newItem);
|
||||
returnList.Add(statusId, newItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface
|
||||
{
|
||||
internal interface IStockJournalRepository
|
||||
{
|
||||
public int StockJournalInsert(int journalTypeId, int stockId, List<(int statusId, int quantity)> journalPosts,
|
||||
DateTime entryDate, bool isNewStock = false);
|
||||
int InsertStockJournalHeader(int stockId, int journalTypeId, DateTime entryDate, bool isLocked);
|
||||
int InsertStockJournalPost(int stockJournalId, int stockStatusId, int quantity);
|
||||
(int, DateTime) ReadJournalStockIdAndEntryDate(int stockJournalId);
|
||||
int ReadJournalTypeIdByStockId(int stockId);
|
||||
int ReadStatusBalanceBySku(string sku, int statusId);
|
||||
int ReadStatusBalanceByStockNumber(int stockNumber, int statusId);
|
||||
@@ -17,9 +18,6 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface
|
||||
int ReadJournalEntryCountByStockId(int stockId);
|
||||
int? ReadTypeIdStatusCreditId(int stockJournalTypeId);
|
||||
Dictionary<string, int> ReadStatusBalanceByStatusId(int statusId);
|
||||
void StockJournalDelete(int stockJournalId);
|
||||
void StockJournalPostInsert(int stockId, int stockJournalId, List<(int statusId, int quantity)> journalPosts, bool isNewStock = false);
|
||||
void StockJournalPostDelete(int stockJournalId);
|
||||
bool WIP_StockJournalConsistencyCheck(int stockId, List<int> statusIdEffected = null);
|
||||
DateTime? ReadMostRecentEntryDateForStatusDebit(int stockId, List<int> stockStatusIdList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface
|
||||
{
|
||||
internal interface IStockStatusRepository
|
||||
{
|
||||
List<Model.Stock.Status> ReadStatus(List<int> statusIds = null, List<int> statusTypeIds = null);
|
||||
Dictionary<int, Model.Stock.Status> ReadStatus(List<int> statusIds = null, List<int> statusTypeIds = null);
|
||||
Dictionary<int, Model.Stock.StatusType> ReadStatusType();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using bnhtrade.Core.Data.Database.UnitOfWork;
|
||||
using Amazon.Runtime.Internal.Transform;
|
||||
using bnhtrade.Core.Data.Database.UnitOfWork;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Formats.Asn1.AsnWriter;
|
||||
|
||||
namespace bnhtrade.Core.Logic.Inventory
|
||||
{
|
||||
@@ -13,32 +15,507 @@ namespace bnhtrade.Core.Logic.Inventory
|
||||
|
||||
internal StockJournalService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
|
||||
|
||||
public int StockJournalInsert(int journalTypeId, int stockId, List<(int statusId, int quantity)> journalPosts,
|
||||
DateTime entryDate, bool isNewStock = false)
|
||||
{
|
||||
/*
|
||||
* TODO: currently the consistancy check checks the journal after the entry has been inserted to the db, if the check fails
|
||||
* the transaction scope is disposed, and the ne journal entries roll back. However, if this is done within a higher
|
||||
* level transaction scope, this nested dispose() also rolls back the all transacopes scopes it is a child of.
|
||||
*
|
||||
* Therefore, a consistancy check needs to be simulated in code to negate the need to rollback/dispose of a db transaction.
|
||||
* This would also have some slight performance benefits.
|
||||
*
|
||||
* Once you've done this, fix the SkuTransactionReconcile class: Currently it's transactionscope only covers updates.
|
||||
* Need to set the scope to cover the intial table read (to lock the records). The issue above restricts this.
|
||||
*
|
||||
* Future me here, refactored the code to use a UnitOfWork pattern, wil need to check that this is still the case.
|
||||
*/
|
||||
|
||||
|
||||
// balance and status IsCredit checks made by post insert function
|
||||
|
||||
//consitancy check is required?
|
||||
bool consistencyRequired = true;
|
||||
|
||||
return WithUnitOfWork(uow =>
|
||||
{
|
||||
// get date of most recent debit for status' that I will be crediting
|
||||
if (isNewStock == false)
|
||||
{
|
||||
var debitStatusIds = new List<int>();
|
||||
foreach (var post in journalPosts)
|
||||
{
|
||||
if (post.quantity > 0)
|
||||
{
|
||||
debitStatusIds.Add(post.statusId);
|
||||
}
|
||||
}
|
||||
var mostRecentDebitDate = uow.StockJournalRepository.ReadMostRecentEntryDateForStatusDebit(stockId, debitStatusIds);
|
||||
if (mostRecentDebitDate.HasValue && mostRecentDebitDate >= entryDate)
|
||||
{
|
||||
consistencyRequired = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
consistencyRequired = false;
|
||||
}
|
||||
}
|
||||
|
||||
// create journal entry
|
||||
int stockJournalId = uow.StockJournalRepository.InsertStockJournalHeader(stockId, journalTypeId, entryDate, isNewStock);
|
||||
|
||||
// insert journal posts into database
|
||||
//new Data.Database.Stock
|
||||
var insertCount = StockJournalPostInsert(uow, stockId, stockJournalId, journalPosts, isNewStock);
|
||||
|
||||
// consistency check
|
||||
bool consistencyResult = true;
|
||||
if (consistencyRequired)
|
||||
{
|
||||
consistencyResult = false;
|
||||
// build list of effected status'
|
||||
var statusIdEffected = new List<int>();
|
||||
foreach (var item in journalPosts)
|
||||
{
|
||||
if (item.quantity < 0)
|
||||
{
|
||||
statusIdEffected.Add(item.statusId);
|
||||
}
|
||||
}
|
||||
// run check
|
||||
consistencyResult = WIP_StockJournalConsistencyCheck(stockId, statusIdEffected);
|
||||
}
|
||||
|
||||
if (consistencyResult)
|
||||
{
|
||||
// commit
|
||||
CommitIfOwned(uow);
|
||||
return stockJournalId;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unable to insert stock journal entry, consistancy check failed.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int StockJournalPostInsert(IUnitOfWork uow, 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 for now, it's 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<int, int>();
|
||||
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 statusList = new StockStatusService(uow).GetStatus(dicStatusQty.Keys.ToList());
|
||||
var dicStatusIsCreditOnly = new Dictionary<int, bool>();
|
||||
foreach (var status in statusList.Values.ToList())
|
||||
{
|
||||
dicStatusIsCreditOnly.Add(status.StatusId, status.IsCreditOnly);
|
||||
}
|
||||
|
||||
// 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
|
||||
var postIdList = new List<int>()
|
||||
foreach (var post in journalPosts)
|
||||
{
|
||||
postIdList.Add(uow.StockJournalRepository.InsertStockJournalPost(stockJournalId, post.statusId, post.quantity));
|
||||
}
|
||||
return postIdList.Count();
|
||||
}
|
||||
|
||||
public void StockJournalDelete(int stockJournalId)
|
||||
{
|
||||
WithUnitOfWork(uow =>
|
||||
{
|
||||
if (stockJournalId <= 0)
|
||||
// get date for journal entry
|
||||
var idAndDate = uow.StockJournalRepository.ReadJournalStockIdAndEntryDate(stockJournalId);
|
||||
DateTime entryDate = idAndDate.Item2;
|
||||
int stockId = idAndDate.Item1;
|
||||
|
||||
// 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))
|
||||
{
|
||||
throw new ArgumentException("Stock journal ID must be greater than zero", nameof(stockJournalId));
|
||||
// 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.");
|
||||
}
|
||||
uow.StockJournalRepository.StockJournalDelete(stockJournalId);
|
||||
CommitIfOwned(uow);
|
||||
});
|
||||
}
|
||||
|
||||
private void StockJournalPostDelete(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 bool WIP_StockJournalConsistencyCheck(int stockId, List<int> statusIdEffected = null)
|
||||
{
|
||||
return WithUnitOfWork(uow =>
|
||||
if (statusIdEffected == null)
|
||||
{
|
||||
if (stockId <= 0)
|
||||
statusIdEffected = new List<int>();
|
||||
}
|
||||
|
||||
// if no list supplied, build list of all used status' for stockId
|
||||
if (statusIdEffected.Count == 0)
|
||||
{
|
||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
throw new ArgumentException("Stock ID must be greater than zero", nameof(stockId));
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
cmd.CommandText = @"
|
||||
SELECT
|
||||
tblStockJournalPost.StockStatusID
|
||||
FROM
|
||||
tblStockJournal
|
||||
INNER JOIN tblStockJournalPost
|
||||
ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID
|
||||
WHERE
|
||||
tblStockJournal.StockID=@stockId
|
||||
GROUP BY
|
||||
tblStockJournalPost.StockStatusID;";
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
return uow.StockJournalRepository.WIP_StockJournalConsistencyCheck(stockId, statusIdEffected);
|
||||
});
|
||||
|
||||
// build the sql string to build creditCreate bool
|
||||
var dicStatusCreditOnly = new Dictionary<int, bool>();
|
||||
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 = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
cmd.CommandText = sqlString;
|
||||
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 = _connection.CreateCommand() as SqlCommand)
|
||||
{
|
||||
cmd.Transaction = _transaction as SqlTransaction;
|
||||
cmd.CommandText = @"
|
||||
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;";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace bnhtrade.Core.Logic.Inventory
|
||||
var journalPosts = new List<(int statusId, int quantity)>();
|
||||
journalPosts.Add((statusDebitId, quantity));
|
||||
journalPosts.Add((statusCreditId.Value, (quantity * -1)));
|
||||
int stockJournalId = uow.StockJournalRepository.StockJournalInsert(stockJournalTypeId, stockId, journalPosts, stockJournalEntryDate, true);
|
||||
int stockJournalId = new StockJournalService(uow).StockJournalInsert(stockJournalTypeId, stockId, journalPosts, stockJournalEntryDate, true);
|
||||
|
||||
// update the stock table
|
||||
count = uow.StockRepository.UpdateStockJournalId(stockId, stockJournalId);
|
||||
@@ -191,7 +191,7 @@ namespace bnhtrade.Core.Logic.Inventory
|
||||
}
|
||||
|
||||
// delete stock journal entry
|
||||
uow.StockJournalRepository.StockJournalDelete(stockJournalId);
|
||||
new StockJournalService(uow).StockJournalDelete(stockJournalId);
|
||||
|
||||
// delete stock table entry
|
||||
count = uow.StockRepository.DeleteStock(stockId);
|
||||
|
||||
@@ -13,6 +13,14 @@ namespace bnhtrade.Core.Logic.Inventory
|
||||
|
||||
internal StockStatusService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
|
||||
|
||||
public Dictionary<int, Model.Stock.Status> GetStatus(List<int> statusIds = null, List<int> statusTypeIds = null)
|
||||
{
|
||||
return WithUnitOfWork(uow =>
|
||||
{
|
||||
return uow.StockStatusRepository.ReadStatus(statusIds, statusTypeIds);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the avaliable balance of a status. Uses a more efficent sql/code. However, balance requests should
|
||||
/// generally also involve a date/time (i.e. the system does allow a stock transaction in the future, therefore
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace bnhtrade.Core.Logic.Stock
|
||||
var sku = readSku.BySkuNumber(statusTransaction.SkuNumber);
|
||||
|
||||
// get the status obj
|
||||
var status = uow.StockStatusRepository.ReadStatus(new List<int> { statusTransaction.StockStatusId })[0];
|
||||
var status = new Logic.Inventory.StockStatusService(uow).GetStatus(new List<int> { statusTransaction.StockStatusId }).Values.First();
|
||||
|
||||
return new Model.Stock.StatusBalance(status, sku, entryList);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user