mirror of
https://github.com/stokebob/bnhtrade.git
synced 2026-03-19 14:37:16 +00:00
wip
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using bnhtrade.Core.Data.Database._BoilerPlate;
|
using bnhtrade.Core.Data.Database._BoilerPlate;
|
||||||
using bnhtrade.Core.Data.Database.Repository.Interface;
|
using bnhtrade.Core.Data.Database.Repository.Interface;
|
||||||
|
using bnhtrade.Core.Model.Account;
|
||||||
using Microsoft.Data.SqlClient;
|
using Microsoft.Data.SqlClient;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -9,6 +10,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Transactions;
|
using System.Transactions;
|
||||||
using static bnhtrade.Core.Data.Database.Constants;
|
using static bnhtrade.Core.Data.Database.Constants;
|
||||||
|
using static bnhtrade.Core.Model.Account.Journal;
|
||||||
|
|
||||||
namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
||||||
{
|
{
|
||||||
@@ -22,84 +24,13 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
|||||||
// Create
|
// Create
|
||||||
//
|
//
|
||||||
|
|
||||||
// only to be assessable via code, use stock relocate to move stock bewtween status'
|
public int InsertStockJournalHeader(int stockId, int journalTypeId, DateTime entryDate, bool isLocked)
|
||||||
public int StockJournalInsert(int journalTypeId, int stockId, List<(int statusId, int quantity)> journalPosts,
|
|
||||||
DateTime entryDate, bool isNewStock = false)
|
|
||||||
{
|
{
|
||||||
/*
|
if (entryDate.Kind != DateTimeKind.Utc)
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
// build sql string
|
throw new ArgumentException("Entry date must be in UTC format.", nameof(entryDate));
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create journal entry
|
|
||||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
||||||
{
|
{
|
||||||
cmd.Transaction = _transaction as SqlTransaction;
|
cmd.Transaction = _transaction as SqlTransaction;
|
||||||
@@ -111,43 +42,33 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
|||||||
cmd.Parameters.AddWithValue("@stockID", stockId);
|
cmd.Parameters.AddWithValue("@stockID", stockId);
|
||||||
cmd.Parameters.AddWithValue("@journalTypeId", journalTypeId);
|
cmd.Parameters.AddWithValue("@journalTypeId", journalTypeId);
|
||||||
cmd.Parameters.AddWithValue("@EntryDate", entryDate.ToUniversalTime());
|
cmd.Parameters.AddWithValue("@EntryDate", entryDate.ToUniversalTime());
|
||||||
cmd.Parameters.AddWithValue("@isLocked", isNewStock);
|
cmd.Parameters.AddWithValue("@isLocked", isLocked);
|
||||||
|
|
||||||
//execute
|
//execute
|
||||||
stockJournalId = (int)cmd.ExecuteScalar();
|
return (int)cmd.ExecuteScalar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert journal posts into database
|
public int InsertStockJournalPost(int stockJournalId, int stockStatusId, int quantity)
|
||||||
//new Data.Database.Stock
|
{
|
||||||
StockJournalPostInsert(stockId, stockJournalId, journalPosts, isNewStock);
|
if (quantity == 0)
|
||||||
|
{
|
||||||
|
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 );";
|
||||||
|
|
||||||
// consistency check
|
cmd.Parameters.AddWithValue("@StockJournalId", stockJournalId);
|
||||||
bool consistencyResult = true;
|
cmd.Parameters.AddWithValue("@stockStatudId", stockStatusId);
|
||||||
if (consistencyRequired)
|
cmd.Parameters.AddWithValue("@quantity", quantity);
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (consistencyResult)
|
// execute
|
||||||
{
|
return cmd.ExecuteNonQuery();
|
||||||
// commit
|
|
||||||
scope.Complete();
|
|
||||||
return stockJournalId;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new Exception("Unable to insert stock journal entry, consistancy check failed.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +76,32 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
|||||||
// Read
|
// 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)
|
public int ReadJournalTypeIdByStockId(int stockId)
|
||||||
{
|
{
|
||||||
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
|
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
|
// update
|
||||||
//
|
//
|
||||||
@@ -331,454 +326,6 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
|||||||
// Delete
|
// 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 sqlBuilder = new SqlWhereBuilder();
|
||||||
var returnList = new List<Model.Stock.Status>();
|
var returnList = new Dictionary<int, Model.Stock.Status>();
|
||||||
|
|
||||||
//build sql query
|
//build sql query
|
||||||
string sql = @"
|
string sql = @"
|
||||||
@@ -91,7 +91,7 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
|
|||||||
, recordCreated
|
, recordCreated
|
||||||
);
|
);
|
||||||
|
|
||||||
returnList.Add(newItem);
|
returnList.Add(statusId, newItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface
|
|||||||
{
|
{
|
||||||
internal interface IStockJournalRepository
|
internal interface IStockJournalRepository
|
||||||
{
|
{
|
||||||
public int StockJournalInsert(int journalTypeId, int stockId, List<(int statusId, int quantity)> journalPosts,
|
int InsertStockJournalHeader(int stockId, int journalTypeId, DateTime entryDate, bool isLocked);
|
||||||
DateTime entryDate, bool isNewStock = false);
|
int InsertStockJournalPost(int stockJournalId, int stockStatusId, int quantity);
|
||||||
|
(int, DateTime) ReadJournalStockIdAndEntryDate(int stockJournalId);
|
||||||
int ReadJournalTypeIdByStockId(int stockId);
|
int ReadJournalTypeIdByStockId(int stockId);
|
||||||
int ReadStatusBalanceBySku(string sku, int statusId);
|
int ReadStatusBalanceBySku(string sku, int statusId);
|
||||||
int ReadStatusBalanceByStockNumber(int stockNumber, int statusId);
|
int ReadStatusBalanceByStockNumber(int stockNumber, int statusId);
|
||||||
@@ -17,9 +18,6 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface
|
|||||||
int ReadJournalEntryCountByStockId(int stockId);
|
int ReadJournalEntryCountByStockId(int stockId);
|
||||||
int? ReadTypeIdStatusCreditId(int stockJournalTypeId);
|
int? ReadTypeIdStatusCreditId(int stockJournalTypeId);
|
||||||
Dictionary<string, int> ReadStatusBalanceByStatusId(int statusId);
|
Dictionary<string, int> ReadStatusBalanceByStatusId(int statusId);
|
||||||
void StockJournalDelete(int stockJournalId);
|
DateTime? ReadMostRecentEntryDateForStatusDebit(int stockId, List<int> stockStatusIdList);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface
|
|||||||
{
|
{
|
||||||
internal interface IStockStatusRepository
|
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();
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using static System.Formats.Asn1.AsnWriter;
|
||||||
|
|
||||||
namespace bnhtrade.Core.Logic.Inventory
|
namespace bnhtrade.Core.Logic.Inventory
|
||||||
{
|
{
|
||||||
@@ -13,32 +15,507 @@ namespace bnhtrade.Core.Logic.Inventory
|
|||||||
|
|
||||||
internal StockJournalService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
|
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)
|
public void StockJournalDelete(int stockJournalId)
|
||||||
{
|
{
|
||||||
WithUnitOfWork(uow =>
|
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
|
// 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)
|
// (unless the status is enabled to do so)
|
||||||
// set empty list or statusIdEffected to null to check entier stock entries for consistency
|
// 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(int stockId, List<int> statusIdEffected = null)
|
||||||
{
|
{
|
||||||
return WithUnitOfWork(uow =>
|
if (statusIdEffected == null)
|
||||||
{
|
{
|
||||||
if (stockId <= 0)
|
statusIdEffected = new List<int>();
|
||||||
{
|
|
||||||
throw new ArgumentException("Stock ID must be greater than zero", nameof(stockId));
|
|
||||||
}
|
}
|
||||||
return uow.StockJournalRepository.WIP_StockJournalConsistencyCheck(stockId, statusIdEffected);
|
|
||||||
});
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ namespace bnhtrade.Core.Logic.Inventory
|
|||||||
var journalPosts = new List<(int statusId, int quantity)>();
|
var journalPosts = new List<(int statusId, int quantity)>();
|
||||||
journalPosts.Add((statusDebitId, quantity));
|
journalPosts.Add((statusDebitId, quantity));
|
||||||
journalPosts.Add((statusCreditId.Value, (quantity * -1)));
|
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
|
// update the stock table
|
||||||
count = uow.StockRepository.UpdateStockJournalId(stockId, stockJournalId);
|
count = uow.StockRepository.UpdateStockJournalId(stockId, stockJournalId);
|
||||||
@@ -191,7 +191,7 @@ namespace bnhtrade.Core.Logic.Inventory
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delete stock journal entry
|
// delete stock journal entry
|
||||||
uow.StockJournalRepository.StockJournalDelete(stockJournalId);
|
new StockJournalService(uow).StockJournalDelete(stockJournalId);
|
||||||
|
|
||||||
// delete stock table entry
|
// delete stock table entry
|
||||||
count = uow.StockRepository.DeleteStock(stockId);
|
count = uow.StockRepository.DeleteStock(stockId);
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ namespace bnhtrade.Core.Logic.Inventory
|
|||||||
|
|
||||||
internal StockStatusService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
|
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>
|
/// <summary>
|
||||||
/// Return the avaliable balance of a status. Uses a more efficent sql/code. However, balance requests should
|
/// 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
|
/// 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);
|
var sku = readSku.BySkuNumber(statusTransaction.SkuNumber);
|
||||||
|
|
||||||
// get the status obj
|
// 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);
|
return new Model.Stock.StatusBalance(status, sku, entryList);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user