15 Commits

Author SHA1 Message Date
bobbie 6151652b9f Merge master into branch and resolve conflicts 2026-04-30 10:24:12 +01:00
bobbie b2bcb72469 wip 2025-11-14 09:46:05 +00:00
bobbie 80dfc2a9d7 wip 2025-07-15 15:13:08 +01:00
bobbie c28d2c6060 wip 2025-07-15 12:20:55 +01:00
bobbie ecf48ba8b4 wip 2025-07-14 20:28:40 +01:00
bobbie 2d751ebf80 wip 2025-07-14 14:29:56 +01:00
bobbie afd4bae10e wip 2025-07-11 11:52:31 +01:00
bobbie be9944f066 wip 2025-07-10 20:26:11 +01:00
bobbie 7a28e2cc66 wip 2025-07-10 19:07:09 +01:00
bobbie 97b945e0cb wip 2025-07-10 16:49:39 +01:00
bobbie ee487db21f wip 2025-07-10 14:33:13 +01:00
bobbie d4170d2b80 wip 2025-07-09 20:19:09 +01:00
bobbie e2af1e4f99 wip 2025-07-09 16:47:37 +01:00
bobbie eb959dd6e2 wip 2025-07-09 15:05:10 +01:00
bobbie c0f7f1a476 wip, this turned into a maaaaaahooosive job 2025-07-09 13:28:17 +01:00
45 changed files with 3723 additions and 2371 deletions
+2 -2
View File
@@ -35,13 +35,13 @@ namespace bnhtrade.ComTypeLib
public int AccountJournalInsert(ConnectionCredential sqlConnCred, int journalTypeId, DateTime entryDate, public int AccountJournalInsert(ConnectionCredential sqlConnCred, int journalTypeId, DateTime entryDate,
string currencyCode, [MarshalAs(UnmanagedType.Currency)] decimal amount, int debitAccountId = 0, int creditAccountId = 0, bool lockEntry = false) string currencyCode, [MarshalAs(UnmanagedType.Currency)] decimal amount, int debitAccountId = 0, int creditAccountId = 0, bool lockEntry = false)
{ {
return new Core.Logic.Account.Journal().AccountJournalInsert(journalTypeId, entryDate, return new Core.Logic.Account.AccountJournalService().JournalInsert(journalTypeId, entryDate,
currencyCode, amount, debitAccountId, creditAccountId, lockEntry); currencyCode, amount, debitAccountId, creditAccountId, lockEntry);
} }
public bool AccountJournalDelete(ConnectionCredential sqlConnCred, int accountJournalId) public bool AccountJournalDelete(ConnectionCredential sqlConnCred, int accountJournalId)
{ {
return new Core.Logic.Account.Journal().AccountJournalDelete(accountJournalId); return new Core.Logic.Account.AccountJournalService().DeleteJournal(accountJournalId);
} }
[return: MarshalAs(UnmanagedType.Currency)] [return: MarshalAs(UnmanagedType.Currency)]
@@ -35,32 +35,30 @@ namespace bnhtrade.ComTypeLib.Purchase
string currencyCode, [MarshalAs(UnmanagedType.Currency)] decimal amountNet, string currencyCode, [MarshalAs(UnmanagedType.Currency)] decimal amountNet,
DateTime entryDate) DateTime entryDate)
{ {
Core.Purchase.PurchaseQuery.WIP_PurchaseLineTransactionNetInsert(sqlConnCred.ConnectionString, new Core.Logic.Purchase.PurchaseService().WIP_PurchaseLineTransactionNetInsert(purchaseLineId, currencyCode, amountNet, entryDate);
purchaseLineId, currencyCode, amountNet, entryDate);
} }
public void PurchaseLineTransactionNetUpdate(ConnectionCredential sqlConnCred, int accountJouranlId, public void PurchaseLineTransactionNetUpdate(ConnectionCredential sqlConnCred, int accountJouranlId,
string currencyCode, [MarshalAs(UnmanagedType.Currency)] decimal amountNet, int debitAccountId) string currencyCode, [MarshalAs(UnmanagedType.Currency)] decimal amountNet, int debitAccountId)
{ {
Core.Purchase.PurchaseQuery.WIP_PurchaseLineTransactionNetUpdate(sqlConnCred.ConnectionString, new Core.Logic.Purchase.PurchaseService().WIP_PurchaseLineTransactionNetUpdate(accountJouranlId, currencyCode, amountNet, debitAccountId);
accountJouranlId, currencyCode, amountNet, debitAccountId);
} }
public void PurchaseLineTransactionDelete(ConnectionCredential sqlConnCred, int purchaseLineId, int accountJournalId) public void PurchaseLineTransactionDelete(ConnectionCredential sqlConnCred, int purchaseLineId, int accountJournalId)
{ {
Core.Purchase.PurchaseQuery.WIP_PurchaseLineTransactionDelete(sqlConnCred.ConnectionString, purchaseLineId, accountJournalId); new Core.Logic.Purchase.PurchaseService().WIP_PurchaseLineTransactionDelete(purchaseLineId, accountJournalId);
} }
public int PurchaseLineTransactionStockInsert(ConnectionCredential sqlConnCred, int accountJournalId, public int PurchaseLineTransactionStockInsert(ConnectionCredential sqlConnCred, int accountJournalId,
string currencyCode, [MarshalAs(UnmanagedType.Currency)] decimal amount, int quantity, int productId, int conditionId, string currencyCode, [MarshalAs(UnmanagedType.Currency)] decimal amount, int quantity, int productId, int conditionId,
int accountTaxCodeId, int stockDebitStatusId) int accountTaxCodeId, int stockDebitStatusId)
{ {
return Core.Stock.StockCreate.WIP_StockInsertPurchase(sqlConnCred.ConnectionString, productId, conditionId, accountTaxCodeId, accountJournalId, quantity, stockDebitStatusId); return new Core.Logic.Inventory.StockService().WIP_StockInsertPurchase(productId, conditionId, accountTaxCodeId, accountJournalId, quantity, stockDebitStatusId);
} }
public void PurchaseLineTransactionStockDelete(ConnectionCredential sqlConnCred, int stockId) public void PurchaseLineTransactionStockDelete(ConnectionCredential sqlConnCred, int stockId)
{ {
Core.Stock.StockCreate.WIP_StockDeletePurchase(sqlConnCred.ConnectionString, stockId); new Core.Logic.Inventory.StockService().WIP_StockDeletePurchase(stockId);
} }
} }
} }
+6 -6
View File
@@ -43,22 +43,22 @@ namespace bnhtrade.ComTypeLib
{ {
public int StockInsertPurchase(ConnectionCredential sqlConnCred, int productId, int conditionId, int accountTaxCodeId, int accountJournalId, int quantity, int statusDebitId) public int StockInsertPurchase(ConnectionCredential sqlConnCred, int productId, int conditionId, int accountTaxCodeId, int accountJournalId, int quantity, int statusDebitId)
{ {
return Core.Stock.StockCreate.WIP_StockInsertPurchase(sqlConnCred.ConnectionString, productId, conditionId, accountTaxCodeId, accountJournalId, quantity, statusDebitId); return new Core.Logic.Inventory.StockService().WIP_StockInsertPurchase(productId, conditionId, accountTaxCodeId, accountJournalId, quantity, statusDebitId);
} }
public int StockInsertOwnerIntroduced(ConnectionCredential sqlConnCred, [MarshalAs(UnmanagedType.Currency)] decimal amount, int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId) public int StockInsertOwnerIntroduced(ConnectionCredential sqlConnCred, [MarshalAs(UnmanagedType.Currency)] decimal amount, int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId)
{ {
return Core.Stock.StockCreate.WIP_StockInsertOwnerIntroduced(sqlConnCred.ConnectionString, amount, quantity, productId, conditionId, accountTaxCodeId, entryDate, debitStatusId); return new Core.Logic.Inventory.StockService().WIP_StockInsertOwnerIntroduced(amount, quantity, productId, conditionId, accountTaxCodeId, entryDate, debitStatusId);
} }
public void StockDeletePurchase(ConnectionCredential sqlConnCred, int stockId) public void StockDeletePurchase(ConnectionCredential sqlConnCred, int stockId)
{ {
Core.Stock.StockCreate.WIP_StockDeletePurchase(sqlConnCred.ConnectionString, stockId); new Core.Logic.Inventory.StockService().WIP_StockDeletePurchase(stockId);
} }
public void StockDeleteOwnerIntroduced(ConnectionCredential sqlConnCred, int stockId) public void StockDeleteOwnerIntroduced(ConnectionCredential sqlConnCred, int stockId)
{ {
Core.Stock.StockCreate.WIP_StockDeleteOwnerIntroduced(sqlConnCred.ConnectionString, stockId); new Core.Logic.Inventory.StockService().WIP_StockDeleteOwnerIntroduced(stockId);
} }
public int StockReallocate(ConnectionCredential sqlConnCred, int stockId, int quantity, int debitStatusId, int creditStatusId, DateTime entryDate) public int StockReallocate(ConnectionCredential sqlConnCred, int stockId, int quantity, int debitStatusId, int creditStatusId, DateTime entryDate)
@@ -70,7 +70,7 @@ namespace bnhtrade.ComTypeLib
public void StockJournalDelete(ConnectionCredential sqlConnCred, int stockJournalId) public void StockJournalDelete(ConnectionCredential sqlConnCred, int stockJournalId)
{ {
Core.Stock.StockJournal.StockJournalDelete(sqlConnCred.ConnectionString, stockJournalId); new Core.Logic.Inventory.StockJournalService().StockJournalDelete(stockJournalId);
} }
public object ReconcileStockTransactions(ConnectionCredential sqlConnCred) public object ReconcileStockTransactions(ConnectionCredential sqlConnCred)
@@ -128,7 +128,7 @@ namespace bnhtrade.ComTypeLib
public bool StockJournalConsistencyCheck(ConnectionCredential sqlConnCred, int stockId) public bool StockJournalConsistencyCheck(ConnectionCredential sqlConnCred, int stockId)
{ {
return Core.Stock.StockJournal.WIP_StockJournalConsistencyCheck(sqlConnCred.ConnectionString, stockId, null); return new Core.Logic.Inventory.StockJournalService().WIP_StockJournalConsistencyCheck(stockId, null);
} }
public void SkuTransactionAdd(ConnectionCredential sqlConnCred, int quantity, string skuNumber, string transactionTypeCode, DateTime transactionDate) public void SkuTransactionAdd(ConnectionCredential sqlConnCred, int quantity, string skuNumber, string transactionTypeCode, DateTime transactionDate)
@@ -1,208 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtrade.Core.Data.Database.Account
{
internal class CreateJournal : Connection
{
/// <summary>
/// Old code needs sorting
/// </summary>
public int AccountJournalInsert(int journalTypeId, DateTime entryDate, string currencyCode,
decimal amount, int debitAccountId = 0, int creditAccountId = 0, bool lockEntry = false)
{
int defaultDebit = 0;
int defaultCredit = 0;
// ensure date is UTC
entryDate = DateTime.SpecifyKind(entryDate, DateTimeKind.Utc);
// debit and credit locks are checked in journal post method
using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = new SqlConnection(SqlConnectionString))
{
conn.Open();
// insert the journal entry
int journalId;
using (SqlCommand cmd = new SqlCommand(@"
INSERT INTO tblAccountJournal
(AccountJournalTypeID, EntryDate, IsLocked)
OUTPUT INSERTED.AccountJournalID
VALUES
(@journalTypeId, @entryDate, @lockEntry)
", conn))
{
// add parameters
cmd.Parameters.AddWithValue("@journalTypeId", journalTypeId);
cmd.Parameters.AddWithValue("@entryDate", entryDate.ToUniversalTime());
cmd.Parameters.AddWithValue("@lockEntry", lockEntry);
//execute
journalId = (int)cmd.ExecuteScalar();
}
// insert journal entries
//bool postResult = AccountJournalPostInsert(sqlConnectionString, journalId, entryDate, currencyCode, amount, debitAccountId, creditAccountId);
bool postResult = AccountJournalPostInsert(journalId, entryDate, currencyCode, amount, debitAccountId, creditAccountId);
scope.Complete();
return journalId;
}
}
/// <summary>
/// Old code needs sorting
/// </summary>
internal bool AccountJournalPostInsert(int journalId, DateTime entryDate,
string currencyCode, decimal amount, int debitAccountId = 0, int creditAccountId = 0)
{
int defaultDebit;
int defaultCredit;
entryDate = DateTime.SpecifyKind(entryDate, DateTimeKind.Utc);
using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = new SqlConnection(SqlConnectionString))
{
conn.Open();
// ensure their are no other entries
using (SqlCommand cmd = new SqlCommand(@"
SELECT
Count(tblAccountJournalPost.AccountJournalPostID) AS CountOfAccountJournalPostID
FROM
tblAccountJournalPost
WHERE
(((tblAccountJournalPost.AccountJournalID)=@AccountJournalID));
", conn))
{
cmd.Parameters.AddWithValue("@AccountJournalID", journalId);
int count = (int)cmd.ExecuteScalar();
if (count > 0)
{
throw new Exception("Unable the insert journal posts, post already present AccountJournalID=" + journalId);
}
}
//checks
using (SqlCommand cmd = new SqlCommand(@"
SELECT
tblAccountJournalType.ChartOfAccountID_Debit, tblAccountJournalType.ChartOfAccountID_Credit
FROM
tblAccountJournal
INNER JOIN tblAccountJournalType
ON tblAccountJournal.AccountJournalTypeID = tblAccountJournalType.AccountJournalTypeID
WHERE
(((tblAccountJournal.AccountJournalID)=@journalId));
", conn))
{
cmd.Parameters.AddWithValue("@journalId", journalId);
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
// debit check
if (reader.IsDBNull(0))
{
if (debitAccountId == 0)
{
throw new Exception("Debit Account ID required, default not set for journal type");
}
}
else
{
defaultDebit = reader.GetInt32(0);
if (debitAccountId == 0)
{
debitAccountId = defaultDebit;
}
else if (debitAccountId != defaultDebit)
{
throw new Exception("Debit Account ID supplied does not match default set for journal type");
}
}
// credit check
if (reader.IsDBNull(1))
{
if (creditAccountId == 0)
{
throw new Exception("Credit Account ID required, default not set for journal type");
}
}
else
{
defaultCredit = reader.GetInt32(1);
if (creditAccountId == 0)
{
creditAccountId = defaultCredit;
}
else if (creditAccountId != defaultCredit)
{
throw new Exception("Credit Account ID supplied does not match default set for journal type");
}
}
}
else
{
throw new Exception("AccountJournalID '" + journalId + "' does not exist.");
}
}
}
// currency conversion
if (currencyCode != "GBP")
{
amount = new Logic.Account.CurrencyService().CurrencyConvertToGbp(currencyCode, amount, entryDate);
}
// ensure decimal is rounded
amount = Math.Round(amount, 2);
// insert debit post
using (SqlCommand cmd = new SqlCommand(@"
INSERT INTO tblAccountJournalPost
(AccountJournalID, AccountChartOfID, AmountGbp)
VALUES
(@AccountJournalId, @AccountChartOfId, @AmountGbp)
", conn))
{
// add parameters
cmd.Parameters.AddWithValue("@AccountJournalId", journalId);
cmd.Parameters.AddWithValue("@AccountChartOfId", debitAccountId);
cmd.Parameters.AddWithValue("@AmountGbp", amount);
cmd.ExecuteNonQuery();
}
// insert credit post
using (SqlCommand cmd = new SqlCommand(@"
INSERT INTO tblAccountJournalPost
(AccountJournalID, AccountChartOfID, AmountGbp)
VALUES
(@AccountJournalId, @AccountChartOfId, @AmountGbp)
", conn))
{
// add parameters
cmd.Parameters.AddWithValue("@AccountJournalId", journalId);
cmd.Parameters.AddWithValue("@AccountChartOfId", creditAccountId);
cmd.Parameters.AddWithValue("@AmountGbp", (amount * -1));
cmd.ExecuteNonQuery();
}
scope.Complete();
return true;
}
}
}
}
@@ -1,75 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtrade.Core.Data.Database.Account
{
internal class UpdateJournal : Connection
{
public bool AccountJournalPostUpdate(int journalId, string currencyCode, decimal amount, int debitAccountId = 0, int creditAccountId = 0)
{
using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = new SqlConnection(SqlConnectionString))
{
conn.Open();
// retrive journal entry date
DateTime entryDate;
using (SqlCommand cmd = new SqlCommand(@"
SELECT
tblAccountJournal.EntryDate
FROM
tblAccountJournal
WHERE
(((tblAccountJournal.AccountJournalID)=@accountJournalId));
", conn))
{
cmd.Parameters.AddWithValue("@accountJournalId", journalId);
entryDate = DateTime.SpecifyKind((DateTime)cmd.ExecuteScalar(), DateTimeKind.Utc);
}
// delete the original posts
using (SqlCommand cmd = new SqlCommand(@"
DELETE FROM
tblAccountJournalPost
WHERE
(((tblAccountJournalPost.AccountJournalID)=@accountJournalId));
", conn))
{
cmd.Parameters.AddWithValue("@accountJournalId", journalId);
cmd.ExecuteNonQuery();
}
//insert new posts
//bool postResult = AccountJournalPostInsert(sqlConnectionString, journalId, entryDate, currencyCode, amount, debitAccountId, creditAccountId);
bool postResult = new Data.Database.Account.CreateJournal().AccountJournalPostInsert(journalId, entryDate, currencyCode, amount, debitAccountId, creditAccountId);
// update modified date on journal
using (SqlCommand cmd = new SqlCommand(@"
UPDATE
tblAccountJournal
SET
tblAccountJournal.LastModified=@utcNow
WHERE
(((tblAccountJournal.AccountJournalID)=@accountJournalId));
", conn))
{
cmd.Parameters.AddWithValue("@accountJournalId", journalId);
cmd.Parameters.AddWithValue("@utcNow", DateTime.UtcNow);
cmd.ExecuteNonQuery();
}
scope.Complete();
}
return true;
}
}
}
@@ -0,0 +1,488 @@
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;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtrade.Core.Data.Database.Repository.Implementation
{
internal class AccountJournalRepository : _Base, IAccountJournalRepository
{
public AccountJournalRepository(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction)
{
}
//
// create
//
public int InsertJournalHeader(int accountJournalTypeId, DateTime entryDate, bool lockEntry = false)
{
// ensure date is UTC
if (entryDate.Kind != DateTimeKind.Utc)
{
throw new Exception("DateTime kind must be utc");
}
// debit and credit locks are checked in journal post method
// insert the journal entry
int journalId;
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
INSERT INTO tblAccountJournal
(AccountJournalTypeID, EntryDate, IsLocked)
OUTPUT INSERTED.AccountJournalID
VALUES
(@journalTypeId, @entryDate, @lockEntry);";
// add parameters
cmd.Parameters.AddWithValue("@journalTypeId", accountJournalTypeId);
cmd.Parameters.AddWithValue("@entryDate", entryDate.ToUniversalTime());
cmd.Parameters.AddWithValue("@lockEntry", lockEntry);
//execute
journalId = (int)cmd.ExecuteScalar();
}
return journalId;
}
public int InsertJournalPost(int accountJournalId, int accountChartOfId, decimal amountGbp)
{
if (accountJournalId <= 0)
{
throw new ArgumentException("Invalid journal ID provided.", nameof(accountJournalId));
}
if (accountChartOfId <= 0)
{
throw new ArgumentException("Invalid account chart of ID provided.", nameof(accountChartOfId));
}
if (amountGbp == 0)
{
throw new ArgumentException("Amount cannot be zero.", nameof(amountGbp));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
INSERT INTO tblAccountJournalPost
(AccountJournalID, AccountChartOfID, AmountGbp)
OUTPUT INSERTED.AccountJournalPostID
VALUES
(@accountJournalId, @accountChartOfId, @amountGbp);";
// add parameters
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
cmd.Parameters.AddWithValue("@accountChartOfId", accountChartOfId);
cmd.Parameters.AddWithValue("@amountGbp", amountGbp);
// execute
return (int)cmd.ExecuteScalar();
}
}
//
// read
//
public Dictionary<int, Model.Account.JournalBuilder> ReadJournalBuilder(List<int> journalIdList)
{
var sqlBuilder = new SqlWhereBuilder();
var returnDict = new Dictionary<int, Model.Account.JournalBuilder>();
//build sql query
string sql = @"
SELECT tblAccountJournal.AccountJournalID
,tblAccountJournal.AccountJournalTypeID
,tblAccountJournal.EntryDate
,tblAccountJournal.PostDate
,tblAccountJournal.LastModified
,tblAccountJournal.IsLocked
,tblAccountJournalPost.AccountJournalPostID
,tblAccountJournalPost.AccountChartOfID
,tblAccountJournalPost.AmountGbp
FROM tblAccountJournal
INNER JOIN tblAccountJournalPost ON tblAccountJournal.AccountJournalID = tblAccountJournalPost.AccountJournalID
WHERE 1 = 1 ";
// build the where statments
if (journalIdList.Any())
{
sqlBuilder.In("tblAccountJournal.AccountJournalID", journalIdList, "AND");
}
// append where string to the sql
if (sqlBuilder.IsSetSqlWhereString)
{
sql = sql + sqlBuilder.SqlWhereString;
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction;
if (sqlBuilder.ParameterListIsSet)
{
sqlBuilder.AddParametersToSqlCommand(cmd);
}
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
int journalId = reader.GetInt32(0);
if (returnDict.ContainsKey(journalId) == false)
{
var journalBuilder = new Model.Account.JournalBuilder();
journalBuilder.JournalId = journalId;
journalBuilder.JournalTypeId = reader.GetInt32(1);
journalBuilder.EntryDate = DateTime.SpecifyKind(reader.GetDateTime(2), DateTimeKind.Utc);
journalBuilder.PostDate = DateTime.SpecifyKind(reader.GetDateTime(3), DateTimeKind.Utc);
journalBuilder.LastModified = DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc);
journalBuilder.IsLocked = reader.GetBoolean(5);
journalBuilder.JournalBuilderPosts = new List<Model.Account.JournalBuilder.JournalPostBuilder>();
returnDict.Add(journalId, journalBuilder);
}
var journalBuilderLine = new Model.Account.JournalBuilder.JournalPostBuilder();
journalBuilderLine.PostId = reader.GetInt32(6);
journalBuilderLine.AccountId = reader.GetInt32(7);
journalBuilderLine.AmountGbp = reader.GetDecimal(8);
returnDict[journalId].JournalBuilderPosts.Add(journalBuilderLine);
}
}
}
return returnDict;
}
public DateTime ReadJournalEntryDate(int journalId)
{
if (journalId <= 0)
{
throw new ArgumentException("Invalid journal ID provided.", nameof(journalId));
}
string sql = @"
SELECT tblAccountJournal.EntryDate
FROM tblAccountJournal
WHERE (((tblAccountJournal.AccountJournalID)=@accountJournalId));";
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction;
cmd.Parameters.AddWithValue("@accountJournalId", journalId);
object obj = cmd.ExecuteScalar();
if (obj == null)
{
throw new Exception("Journal entry not found for AccountJournalID=" + journalId);
}
return DateTime.SpecifyKind((DateTime)obj, DateTimeKind.Utc);
}
}
public int CountJournalPosts(int accountJournalId)
{
if (accountJournalId <= 0)
{
throw new ArgumentException("Invalid journal ID provided.", nameof(accountJournalId));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT
Count(tblAccountJournalPost.AccountJournalPostID) AS CountOfAccountJournalPostID
FROM
tblAccountJournalPost
WHERE
(((tblAccountJournalPost.AccountJournalID)=@AccountJournalID));";
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
return (int)cmd.ExecuteScalar();
}
}
public bool IsJournalDebitAssetType(int accountJournalId)
{
if (accountJournalId <= 0)
{
throw new ArgumentException("Invalid journal ID provided.", nameof(accountJournalId));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT
Count(tblAccountJournalPost.AccountJournalPostID) AS CountOfAccountJournalPostID
FROM
(tblAccountJournalPost
INNER JOIN tblAccountChartOf
ON tblAccountJournalPost.AccountChartOfID = tblAccountChartOf.AccountChartOfID)
INNER JOIN tblAccountChartOfType
ON tblAccountChartOf.AccountChartOfTypeID = tblAccountChartOfType.AccountChartOfTypeID
WHERE
tblAccountJournalPost.AmountGbp>=0
AND tblAccountChartOfType.BasicType='Asset'
AND tblAccountJournalPost.AccountJournalID=@accountJournalId;";
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
if ((int)cmd.ExecuteScalar() < 1)
{
return false; // no debit posts of type Asset found
}
return true;
}
}
/// <summary>
/// Test for locked journal entry
/// </summary>
/// <returns>True/False or null on no records found</returns>
public bool? IsJournalLocked(int accountJournalId)
{
if (accountJournalId <= 0)
{
throw new ArgumentException("Invalid journal ID provided.", nameof(accountJournalId));
}
string sql = @"
SELECT
tblAccountJournal.IsLocked
FROM
tblAccountJournal
WHERE
tblAccountJournal.AccountJournalID=@accountJournalId;";
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction;
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
object obj = cmd.ExecuteScalar();
if (obj == null || obj == DBNull.Value)
{
return null;
}
else
{
return (bool)obj;
}
}
}
public List<(int, string, int?, int?)> ReadJournalType(List<int> accountJournalTypeIds = null, List<string> typeTitles = null)
{
var returnList = new List<(int, string, int?, int?)>();
var sqlBuilder = new SqlWhereBuilder();
//build sql query
string sql = @"
SELECT [AccountJournalTypeID]
,[TypeTitle]
,[ChartOfAccountID_Debit]
,[ChartOfAccountID_Credit]
FROM [e2A].[dbo].[tblAccountJournalType]
WHERE 1 = 1 ";
// build the where statments
if (accountJournalTypeIds.Any())
{
sqlBuilder.In("AccountJournalTypeID", accountJournalTypeIds, "AND");
}
if (typeTitles.Any())
{
sqlBuilder.In("TypeTitle", typeTitles, "AND");
}
// append where string to the sql
if (sqlBuilder.IsSetSqlWhereString)
{
sql = sql + sqlBuilder.SqlWhereString;
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction;
if (sqlBuilder.ParameterListIsSet)
{
sqlBuilder.AddParametersToSqlCommand(cmd);
}
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
// read from db and create object
int journalTypeId = reader.GetInt32(0);
string title = reader.GetString(1);
int? debitAccountId = null;
if (!reader.IsDBNull(2))
debitAccountId = reader.GetInt32(2);
int? creditAccountId = null;
if (!reader.IsDBNull(3))
creditAccountId = reader.GetInt32(3);
returnList.Add((journalTypeId, title, debitAccountId, creditAccountId));
}
}
}
return returnList;
}
/// <summary>
/// Retrieves the default debit and credit account IDs associated with a specific account journal.
/// </summary>
/// <remarks>This method queries the database to retrieve the default debit and credit account IDs
/// for the specified account journal. If the account journal does not exist, an exception is thrown. Ensure
/// that the provided <paramref name="accountJournalId"/> is valid.</remarks>
/// <param name="accountJournalId">The unique identifier of the account journal for which to retrieve the default debit and credit account IDs.</param>
/// <returns>A tuple containing two nullable integers: <list type="bullet"> <item><description>The first value represents
/// the default debit account ID, or <see langword="null"/> if no debit account is set.</description></item>
/// <item><description>The second value represents the default credit account ID, or <see langword="null"/> if
/// no credit account is set.</description></item> </list></returns>
/// <exception cref="Exception">Thrown if the specified <paramref name="accountJournalId"/> does not exist in the database.</exception>
public (int?, int?) ReadJournalTypeDefaultDebitCredit(int accountJournalId)
{
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT
tblAccountJournalType.ChartOfAccountID_Debit, tblAccountJournalType.ChartOfAccountID_Credit
FROM
tblAccountJournal
INNER JOIN tblAccountJournalType
ON tblAccountJournal.AccountJournalTypeID = tblAccountJournalType.AccountJournalTypeID
WHERE
(((tblAccountJournal.AccountJournalID)=@journalId));";
cmd.Parameters.AddWithValue("@journalId", accountJournalId);
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
int? defaultDebit = null;
int? defaultCredit = null;
// debit check
if (reader.IsDBNull(0) == false)
{
defaultDebit = reader.GetInt32(0);
}
// credit check
if (reader.IsDBNull(1) == false)
{
defaultCredit = reader.GetInt32(1);
}
return (defaultDebit, defaultCredit);
}
else
{
throw new Exception("AccountJournalID '" + accountJournalId + "' does not exist.");
}
}
}
}
//
// update
//
public bool UpdateJournalEntryModifiedDate(int accountJournalId)
{
if (accountJournalId <= 0)
{
throw new ArgumentException("Invalid journal ID provided.", nameof(accountJournalId));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
UPDATE
tblAccountJournal
SET
tblAccountJournal.LastModified=@utcNow
WHERE
(((tblAccountJournal.AccountJournalID)=@accountJournalId));";
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
cmd.Parameters.AddWithValue("@utcNow", DateTime.UtcNow);
if (cmd.ExecuteNonQuery() == 1)
{
return true; // update successful
}
else
{
return false; // update failed, journal ID may not exist
}
}
}
//
// delete
//
public int DeleteJournalHeader(int accountJournalId)
{
if (accountJournalId <= 0)
{
throw new ArgumentException("Invalid journal ID provided.", nameof(accountJournalId));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
DELETE FROM
tblAccountJournal
WHERE
AccountJournalID=@accountJournalId;";
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
return cmd.ExecuteNonQuery();
}
}
public int DeleteJournalPostAll(int accountJournalId)
{
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
DELETE FROM
tblAccountJournalPost
WHERE
tblAccountJournalPost.AccountJournalID = @accountJournalId;";
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
return cmd.ExecuteNonQuery();
}
}
}
}
@@ -1,372 +0,0 @@
using bnhtrade.Core.Data.Database._BoilerPlate;
using bnhtrade.Core.Data.Database.Repository.Interface;
using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtrade.Core.Data.Database.Repository.Implementation
{
internal class JournalRepository : _Base, IJournalRepository
{
public JournalRepository(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction)
{
}
public Dictionary<int, Core.Model.Account.Journal> ReadJournal(List<int> journalIdList)
{
var sqlBuilder = new SqlWhereBuilder();
//build sql query
string sql = @"
SELECT tblAccountJournal.AccountJournalID
,tblAccountJournal.AccountJournalTypeID
,tblAccountJournal.EntryDate
,tblAccountJournal.PostDate
,tblAccountJournal.LastModified
,tblAccountJournal.IsLocked
,tblAccountJournalPost.AccountJournalPostID
,tblAccountJournalPost.AccountChartOfID
,tblAccountJournalPost.AmountGbp
FROM tblAccountJournal
INNER JOIN tblAccountJournalPost ON tblAccountJournal.AccountJournalID = tblAccountJournalPost.AccountJournalID
WHERE 1 = 1 ";
// build the where statments
if (journalIdList.Any())
{
sqlBuilder.In("tblAccountJournal.AccountJournalID", journalIdList, "AND");
}
// append where string to the sql
if (sqlBuilder.IsSetSqlWhereString)
{
sql = sql + sqlBuilder.SqlWhereString;
}
// build tuple list
var dbJournalList = new List<(
int AccountJournalId
, int AccountJournalTypeId
, DateTime EntryDate
, DateTime PostDate
, DateTime LastModified
, bool IsLocked
)>();
var dbJournalPostList = new List<(
int AccountJournalId
, int AccountJournalPostId
, int AccountChartOfId
, decimal AmountGbp
)>();
bool hasRows = false;
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction;
if (sqlBuilder.ParameterListIsSet)
{
sqlBuilder.AddParametersToSqlCommand(cmd);
}
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.HasRows)
{
hasRows = true;
int lastJournalId = 0;
while (reader.Read())
{
// read journal header
int journalId = reader.GetInt32(0);
if (journalId != lastJournalId)
{
lastJournalId = journalId;
(int AccountJournalId
, int AccountJournalTypeId
, DateTime EntryDate
, DateTime PostDate
, DateTime LastModified
, bool IsLocked
)
journal =
(journalId
, reader.GetInt32(1)
, DateTime.SpecifyKind(reader.GetDateTime(2), DateTimeKind.Utc)
, DateTime.SpecifyKind(reader.GetDateTime(3), DateTimeKind.Utc)
, DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc)
, reader.GetBoolean(5)
);
dbJournalList.Add(journal);
}
// read journal posts
(int AccountJournalId
, int AccountJournalPostId
, int AccountChartOfId
, decimal AmountGbp
)
journalPost =
(journalId
, reader.GetInt32(6)
, reader.GetInt32(7)
, reader.GetDecimal(8)
);
dbJournalPostList.Add(journalPost);
}
}
}
}
var returnList = new Dictionary<int, Core.Model.Account.Journal>();
if (hasRows)
{
// build lists to filter db results by
var journalTypeIdList = new List<int>();
var accountIdList = new List<int>();
foreach (var item in dbJournalList)
{
journalTypeIdList.Add(item.AccountJournalTypeId);
}
foreach (var item in dbJournalPostList)
{
accountIdList.Add(item.AccountChartOfId);
}
// get journalTypes from db
var journalTypeDict = new JournalRepository(_connection, _transaction).ReadJournalType(journalTypeIdList);
// get accounts from db
var accountDict = new AccountCodeRepository(_connection, _transaction).ReadAccountCode(accountIdList);
// build final return dictionary
foreach (var dbJournal in dbJournalList)
{
// build posts
var newPosts = new List<Core.Model.Account.Journal.Post>();
foreach (var dbJournalPost in dbJournalPostList)
{
if (dbJournalPost.AccountJournalId == dbJournal.AccountJournalId)
{
var newPost = new Core.Model.Account.Journal.Post(
dbJournalPost.AccountJournalPostId
, accountDict[dbJournalPost.AccountChartOfId]
, dbJournalPost.AmountGbp);
newPosts.Add(newPost);
}
}
// create the journal
var newJournal = new Core.Model.Account.Journal(
dbJournal.AccountJournalId
, journalTypeDict[dbJournal.AccountJournalTypeId]
, newPosts
, dbJournal.EntryDate
, dbJournal.PostDate
, dbJournal.LastModified
, dbJournal.IsLocked);
returnList.Add(dbJournal.AccountJournalId, newJournal);
}
}
// all done, return the list herevar
return returnList;
}
/// <summary>
/// Test for locked journal entry
/// </summary>
/// <returns>False on locked journal entry</returns>
public bool ReadJournalIsLocked(int journalId)
{
string sql = @"
SELECT
tblAccountJournal.IsLocked
FROM
tblAccountJournal
WHERE
tblAccountJournal.AccountJournalID=@accountJournalId;";
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction;
cmd.Parameters.AddWithValue("@accountJournalId", journalId);
object obj = cmd.ExecuteScalar();
if (obj == null)
{
throw new Exception("Journal entry not found for AccountJournalID=" + journalId);
}
else
{
return (bool)obj;
}
}
}
public Dictionary<int, Model.Account.JournalType> ReadJournalType(List<int> journalTypeIds = null, List<string> typeTitles = null)
{
var sqlBuilder = new SqlWhereBuilder();
// create the return (emptyP list) here
var returnList = new Dictionary<int, Model.Account.JournalType>();
sqlBuilder.Init();
//build sql query
string sql = @"
SELECT [AccountJournalTypeID]
,[TypeTitle]
,[ChartOfAccountID_Debit]
,[ChartOfAccountID_Credit]
FROM [e2A].[dbo].[tblAccountJournalType]
WHERE 1 = 1 ";
// build the where statments
if (journalTypeIds.Any())
{
sqlBuilder.In("AccountJournalTypeID", journalTypeIds, "AND");
}
if (typeTitles.Any())
{
sqlBuilder.In("TypeTitle", typeTitles, "AND");
}
// append where string to the sql
if (sqlBuilder.IsSetSqlWhereString)
{
sql = sql + sqlBuilder.SqlWhereString;
}
// create dictionary to add credit/debit accounts on after db read
var creditDict = new Dictionary<int, int>();
var debitDict = new Dictionary<int, int>();
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction;
if (sqlBuilder.ParameterListIsSet)
{
sqlBuilder.AddParametersToSqlCommand(cmd);
}
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
// read from db and create object
int journalTypeId = reader.GetInt32(0);
string title = reader.GetString(1);
int? debitAccountId = null;
if (!reader.IsDBNull(2)) { debitAccountId = reader.GetInt32(2); }
int? creditAccountId = null;
if (!reader.IsDBNull(3)) { creditAccountId = reader.GetInt32(3); }
// build return list
var item = new Model.Account.JournalType(journalTypeId, title);
returnList.Add(journalTypeId, item);
// build dictionaries
if (debitAccountId != null)
{
debitDict.Add(journalTypeId, debitAccountId.Value);
}
if (creditAccountId != null)
{
creditDict.Add(journalTypeId, creditAccountId.Value);
}
}
}
}
}
// get account objects from db
var accountIdList = debitDict.Values.ToList();
accountIdList.AddRange(creditDict.Values.ToList());
var dbDict = new AccountCodeRepository(_connection, _transaction).ReadAccountCode(accountIdList);
// add to the returnlist
foreach (var account in returnList.Values)
{
Model.Account.Account debitAccount = null;
if (debitDict.ContainsKey(account.JournalTypeId))
{
debitAccount = dbDict[debitDict[account.JournalTypeId]];
}
Model.Account.Account creditAccount = null;
if (creditDict.ContainsKey(account.JournalTypeId))
{
creditAccount = dbDict[creditDict[account.JournalTypeId]]; // key of 59 needed
}
account.AddDefaultAccounts(creditAccount, debitAccount);
}
// all done, return the list here
return returnList;
}
/// <summary>
/// Old code needs sorting
/// </summary>
public bool DeleteJournal(int accountJournalId)
{
bool IsLocked = ReadJournalIsLocked(accountJournalId);
if (IsLocked == true)
{
return false;
}
// make the delete
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = @"
DELETE FROM tblAccountJournalPost
WHERE AccountJournalID=@accountJournalId;";
cmd.Transaction = _transaction as SqlTransaction;
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
int rows = cmd.ExecuteNonQuery();
if (rows == 0)
{
throw new Exception("Journal entry and/or entry posts do not exist for AccountJournalId=" + accountJournalId);
}
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = @"
DELETE FROM tblAccountJournal
WHERE AccountJournalID=@accountJournalId;";
cmd.Transaction = _transaction as SqlTransaction;
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
cmd.ExecuteNonQuery();
}
return true;
}
}
}
@@ -0,0 +1,140 @@
using bnhtrade.Core.Data.Database.Repository.Interface;
using bnhtrade.Core.Model.Account;
using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtrade.Core.Data.Database.Repository.Implementation
{
internal class PurchaseRepository : _Base, IPurchaseRepository
{
private static int accountJournalTypeIdNet = 1;
private static int accountJournalTypeIdTax = 2;
private static int stockJournalTypeId = 1;
private static int creditAccountId = 59;
private static int creditStatusId = 13;
private static int defaultAccountId = 138;
public PurchaseRepository(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction)
{
}
public int DeletePurchaseLineTransaction(int accountJournalId)
{
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
DELETE FROM tblPurchaseLineTransaction
WHERE AccountJournalID=@accountJournalId;";
cmd.Parameters.AddWithValue("@accountJournalID", accountJournalId);
return cmd.ExecuteNonQuery();
}
}
public void InsertPurchaseLineTransaction(int accountJournalId, int purchaseLineId)
{
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
INSERT INTO
tblPurchaseLineTransaction
( PurchaseLineID, AccountJournalID )
VALUES
( @purchaseLineID, @accountJournalID );";
cmd.Parameters.AddWithValue("@purchaseLineID", purchaseLineId);
cmd.Parameters.AddWithValue("@accountJournalID", accountJournalId);
int count = cmd.ExecuteNonQuery();
if (count != 1)
{
throw new Exception("Failed to insert record to tblPurchaseLineTransaction table");
}
}
}
public void WIP_PurchaseLineTransactionNetUpdate(int accountJouranlId, string currencyCode, decimal amountNet, int debitAccountId)
{
// stock accountId check
if (debitAccountId == 86)
{
int count = 0;
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT Count(tblStock.StockID) AS CountOfStockID
FROM tblStock
WHERE (((tblStock.AccountJournalID)=@accountJournalId));
";
cmd.Parameters.AddWithValue("@accountJournalId", accountJouranlId);
count = (int)cmd.ExecuteScalar();
}
if (count == 0)
{
throw new Exception("Add account journal entry to stock before attempting this operation.");
}
else if (count > 1)
{
throw new Exception("Houston we have a problem! An account journal entry is assigned to " + count + " stock lines.");
}
}
}
// delete after testing....
//public void WIP_PurchaseLineTransactionStockDelete(string sqlConnectionString, int accountJournalId, int stockId)
//{
// using (TransactionScope scope = new TransactionScope())
// using (SqlConnection conn = new SqlConnection(sqlConnectionString))
// {
// conn.Open();
// // get stock cost
// (int quantity, decimal totalCost) result = Stock.StockJournal.StockGetTotalQuantityAndCost(sqlConnectionString, stockId);
// decimal amount = result.totalCost;
// // delete accountJournalId from stock table
// using (SqlCommand cmd = new SqlCommand(@"
// UPDATE tblStock
// SET AccountJournalID=Null
// WHERE StockID=@stockId AND AccountJournalID=@accountJournalId;
// ", conn))
// {
// cmd.Parameters.AddWithValue("@stockId", stockId);
// cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
// int count = cmd.ExecuteNonQuery();
// if (count != 1)
// {
// throw new Exception("Integrity check failure! StockID=" + stockId + ", AccountJournalID=" + accountJournalId +
// " combination not found on stock table.");
// }
// }
// // reset the account journal to default
// Account.AccountQuery.AccountJournalPostUpdate(sqlConnectionString, accountJournalId, "GBP", amount, defaultAccountId, creditAccountId);
// // delete the stock entry
// Stock.StockCreate.WIP_StockDeleteSub(sqlConnectionString, stockId);
// scope.Complete();
// }
//}
}
}
@@ -0,0 +1,552 @@
using bnhtrade.Core.Data.Database._BoilerPlate;
using bnhtrade.Core.Data.Database.Repository.Interface;
using bnhtrade.Core.Model.Account;
using bnhtrade.Core.Model.Stock;
using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
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
{
internal class StockJournalRepository : _Base, IStockJournalRepository
{
public StockJournalRepository(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction)
{
}
private List<string> ConvertStockNumbers(List<int> stockNumbers)
{
if (stockNumbers == null || stockNumbers.Count == 0)
{
return new List<string>();
}
return stockNumbers.Select(sn => "STK#" + sn.ToString("D6")).ToList();
}
private List<int> ConvertStockNumbers(List<string> stockNumbers)
{
if (stockNumbers == null || stockNumbers.Count == 0)
{
return new List<int>();
}
return stockNumbers.Select(sn => int.Parse(sn.Substring(4))).ToList();
}
//
// Create
//
public int InsertStockJournalHeader(int stockId, int journalTypeId, DateTime entryDate, bool isLocked)
{
if (entryDate.Kind != DateTimeKind.Utc)
{
throw new ArgumentException("Entry date must be in UTC format.", nameof(entryDate));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
INSERT INTO tblStockJournal ( EntryDate, StockJournalTypeID, StockID, IsLocked )
OUTPUT INSERTED.StockJournalID
VALUES ( @EntryDate, @journalTypeId, @stockID, @isLocked );";
cmd.Parameters.AddWithValue("@stockID", stockId);
cmd.Parameters.AddWithValue("@journalTypeId", journalTypeId);
cmd.Parameters.AddWithValue("@EntryDate", entryDate.ToUniversalTime());
cmd.Parameters.AddWithValue("@isLocked", isLocked);
//execute
return (int)cmd.ExecuteScalar();
}
}
public int InsertStockJournalPost(int stockJournalId, int stockStatusId, int quantity)
{
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 );";
cmd.Parameters.AddWithValue("@StockJournalId", stockJournalId);
cmd.Parameters.AddWithValue("@stockStatudId", stockStatusId);
cmd.Parameters.AddWithValue("@quantity", quantity);
// execute
return cmd.ExecuteNonQuery();
}
}
//
// Read
//
public Dictionary<int, Model.Stock.StockJournalBuilder> ReadStockJournal(bool lockRecords = false,
List<int> stockJournalIds = null, List<int> stockIds = null, List<string> stockNumbers = null
, DateTime? minEntryDate = null, DateTime? maxEntryDate = null, List<int> stockStatusIds = null)
{
var returnDict = new Dictionary<int, Model.Stock.StockJournalBuilder>();
var sqlWhere = new SqlWhereBuilder();
string lockClause = "";
if (lockRecords)
{
lockClause = "WITH (UPDLOCK, HOLDLOCK)";
}
string sql = $@"
SELECT tblStockJournal.StockJournalID,
tblStockJournal.StockJournalTypeID,
tblStockJournal.StockID,
tblStock.StockNumber
tblStockJournal.EntryDate,
tblStockJournal.PostDate,
tblStockJournal.LastModified,
tblStockJournal.IsLocked,
tblStockJournal.AccountIsProcessed,
tblStockJournalPost.StockJournalPostID,
tblStockJournalPost.StockStatusID,
tblStockJournalPost.Quantity,
FROM tblStockJournal {lockClause}
LEFT OUTER JOIN tblStock {lockClause} ON tblStockJournal.StockID = tblStock.StockID
LEFT OUTER JOIN tblStockJournalPost {lockClause} ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID
WHERE 1=1 ";
// build where clause based on provided filters
bool noFilter = true;
if (stockJournalIds != null && stockJournalIds.Any())
{
sql += sqlWhere.InClause("tblStockJournal.StockJournalID", stockJournalIds, "AND");
noFilter = false;
}
if (stockIds != null && stockIds.Any())
{
sql += sqlWhere.InClause("tblStockJournal.StockID", stockIds, "AND");
noFilter = false;
}
if (stockNumbers != null && stockNumbers.Any())
{
List<int> stockNumbersInt = ConvertStockNumbers(stockNumbers);
sql += sqlWhere.InClause("tblStock.StockNumber", stockNumbersInt, "AND");
noFilter = false;
}
if (minEntryDate.HasValue)
{
sql += sqlWhere.IsEqualToOrGreaterThanClause("tblStockJournal.EntryDate", minEntryDate.Value.ToUniversalTime(), "AND");
noFilter = false;
}
if (maxEntryDate.HasValue)
{
sql += sqlWhere.IsEqualToOrLessThanClause("tblStockJournal.EntryDate", maxEntryDate.Value.ToUniversalTime(), "AND");
noFilter = false;
}
if (stockStatusIds != null && stockStatusIds.Any())
{
sql += sqlWhere.InClause(
"StockStatusID"
, stockStatusIds
, " AND tblStockJournal.StockJournalID IN (SELECT StockJournalID FROM tblStockJournalPost WHERE "
, " ) "
);
noFilter = false;
}
if (noFilter)
{
throw new ArgumentException("At least one filter must be provided for stock journal retrieval.");
}
sql += " ORDER BY tblStockJournal.EntryDate, tblStockJournal.StockJournalID, tblStockJournalPost.StockJournalPostID;";
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = sql;
if (sqlWhere.ParameterListIsSet)
{
sqlWhere.AddParametersToSqlCommand(cmd);
}
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
int stockJournalId = reader.GetInt32(0);
// add header information, if not already in dictionary
if (returnDict.ContainsKey(stockJournalId) == false)
{
// create new StockJournalBuilder
var stockJournalBuilder = new Model.Stock.StockJournalBuilder
{
StockJournalId = stockJournalId,
StockJournalTypeId = reader.GetInt32(1),
StockId = reader.GetInt32(2),
StockNumber = "STK#" + reader.GetInt32(3).ToString("D6"),
EntryDate = DateTime.SpecifyKind(reader.GetDateTime(4), DateTimeKind.Utc),
PostDate = DateTime.SpecifyKind(reader.GetDateTime(5), DateTimeKind.Utc),
LastModified = DateTime.SpecifyKind(reader.GetDateTime(6), DateTimeKind.Utc),
IsLocked = reader.GetBoolean(7),
AccountIsProcessed = reader.GetBoolean(8),
StockJournalBuilderPosts = new List<Model.Stock.StockJournalBuilder.StockJournalBuilderPost>()
};
returnDict.Add(stockJournalId, stockJournalBuilder);
}
var stockJournalBuilderPost = new Model.Stock.StockJournalBuilder.StockJournalBuilderPost
{
StockJournalPostId = reader.GetInt32(9),
StatusId = reader.GetInt32(10),
Quantity = reader.GetInt32(11)
};
returnDict[stockJournalId].StockJournalBuilderPosts.Add(stockJournalBuilderPost);
}
return returnDict;
}
}
}
public Dictionary<int, (int id, string title, int? stockStatusIdDebit, int? stockStatusIdCredit)> ReadStockJournalType(List<int> stockJournalTypeIdList)
{
var returnDict = new Dictionary<int, (int id, string title, int? stockStatusIdDebit, int? stockStatusIdCredit)>();
var sqlWhere = new SqlWhereBuilder();
string sql = @"
SELECT StockJournalTypeID, TypeTitle, StockStatusID_Debit, StockStatusID_Credit
FROM tblStockJournalType
WHERE 1=1 ";
if (stockJournalTypeIdList != null && stockJournalTypeIdList.Any())
{
sql += sqlWhere.InClause("StockJournalTypeID", stockJournalTypeIdList, "AND");
}
sql += ";";
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = sql;
if (sqlWhere.ParameterListIsSet)
{
sqlWhere.AddParametersToSqlCommand(cmd);
}
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
int stockJournalTypeId = reader.GetInt32(0);
returnDict.Add(stockJournalTypeId, (
id: stockJournalTypeId,
title: reader.GetString(1),
stockStatusIdDebit: reader.IsDBNull(2) ? (int?)null : reader.GetInt32(2),
stockStatusIdCredit: reader.IsDBNull(3) ? (int?)null : reader.GetInt32(3)
));
}
}
}
return returnDict;
}
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)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT tblStockJournal.StockJournalTypeID
FROM tblStock INNER JOIN
tblStockJournal ON tblStock.StockJournalID = tblStockJournal.StockJournalID
WHERE (tblStock.StockID = @stockId);";
cmd.Parameters.AddWithValue("@stockId", stockId);
object obj = cmd.ExecuteScalar();
if (obj == null || obj == DBNull.Value)
{
throw new Exception("No stock journal type found for StockID=" + stockId);
}
return (int)obj;
}
}
public int ReadStatusBalanceBySku(string sku, int statusId)
{
int statusBalance = new int();
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT SUM(tblStockJournalPost.Quantity) AS Balance
FROM tblStockJournal INNER JOIN
tblStockJournalPost ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID INNER JOIN
tblStock ON tblStockJournal.StockID = tblStock.StockID INNER JOIN
tblSku ON tblStock.SkuID = tblSku.skuSkuID
WHERE (tblStockJournalPost.StockStatusID = @statusId) AND (tblSku.skuSkuNumber = @sku);";
cmd.Parameters.AddWithValue("@sku", sku);
cmd.Parameters.AddWithValue("@statusId", statusId);
// execute
object obj = cmd.ExecuteScalar();
if (obj == null || obj == DBNull.Value)
{
statusBalance = 0;
}
else
{
statusBalance = (int)obj;
}
}
return statusBalance;
}
public int ReadStatusBalanceByStockNumber(int stockNumber, int statusId)
{
int statusBalance = new int();
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT SUM(tblStockJournalPost.Quantity) AS Balance
FROM tblStockJournal INNER JOIN
tblStockJournalPost ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID INNER JOIN
tblStock ON tblStockJournal.StockID = tblStock.StockID
WHERE (tblStockJournalPost.StockStatusID = @statusId) AND (tblStock.StockNumber = @stockNumber);";
cmd.Parameters.AddWithValue("@stockNumber", stockNumber);
cmd.Parameters.AddWithValue("@statusId", statusId);
// execute
object obj = cmd.ExecuteScalar();
if (obj == null || obj == DBNull.Value)
{
statusBalance = 0;
}
else
{
statusBalance = (int)obj;
}
}
return statusBalance;
}
public int ReadStatusBalanceByStockId(int stockId, int statusId)
{
int statusBalance = new int();
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT
SUM(tblStockJournalPost.Quantity) AS Balance
FROM
tblStockJournal
INNER JOIN tblStockJournalPost
ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID
WHERE
(tblStockJournal.StockID = @stockId )
AND (tblStockJournalPost.StockStatusID = @statusId );";
cmd.Parameters.AddWithValue("@stockId", stockId);
cmd.Parameters.AddWithValue("@statusId", statusId);
// execute
object obj = cmd.ExecuteScalar();
if (obj == null || obj == DBNull.Value)
{
statusBalance = 0;
}
else
{
statusBalance = (int)obj;
}
}
return statusBalance;
}
public int ReadJournalEntryCountByStockId(int stockId)
{
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT Count(tblStockJournal.StockJournalID) AS CountOfStockJournalID
FROM tblStockJournal
WHERE (((tblStockJournal.StockID)=@stockId));";
cmd.Parameters.AddWithValue("@stockId", stockId);
return (int)cmd.ExecuteScalar();
}
}
public int? ReadTypeIdStatusCreditId(int stockJournalTypeId)
{
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT
tblStockJournalType.StockStatusID_Credit
FROM
tblStockJournalType
WHERE
tblStockJournalType.StockJournalTypeID=@stockJournalTypeId
AND tblStockJournalType.StockStatusID_Credit Is Not Null;";
cmd.Parameters.AddWithValue("@stockJournalTypeId", stockJournalTypeId);
object obj = cmd.ExecuteScalar();
if (obj == null || obj == DBNull.Value)
{
return null;
}
else
{
return (int)obj;
}
}
}
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 ";
stringSql += sqlWhere.InClause("tblStockJournalPost.StockStatusID", stockStatusIdList, "AND") + ";";
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
//
//
// Delete
//
public void StockJournalDelete(int stockJournalId)
{
// delete posts
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
DELETE FROM tblStockJournalPost
WHERE StockJournalID=@stockJournalId;";
cmd.Parameters.AddWithValue("@StockJournalId", stockJournalId);
// execute
cmd.ExecuteNonQuery();
}
// delete journal entry
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
DELETE FROM tblStockJournal
WHERE StockJournalID=@stockJournalId;";
// add parameters
cmd.Parameters.AddWithValue("@stockJournalId", stockJournalId);
int count = cmd.ExecuteNonQuery();
if (count != 1)
{
throw new Exception("Failed to delete stock journal header.");
}
}
}
}
}
@@ -1,5 +1,5 @@
using bnhtrade.Core.Data.Database._BoilerPlate; using bnhtrade.Core.Data.Database.Repository.Interface;
using bnhtrade.Core.Data.Database.Repository.Interface; using bnhtrade.Core.Data.Database.UnitOfWork;
using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -7,6 +7,8 @@ using System.Data;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Transactions;
using static System.Formats.Asn1.AsnWriter;
namespace bnhtrade.Core.Data.Database.Repository.Implementation namespace bnhtrade.Core.Data.Database.Repository.Implementation
{ {
@@ -16,126 +18,254 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
{ {
} }
public List<Model.Stock.Status> ReadStatus(List<int> statusIds = null, List<int> statusTypeIds = null) //
{ // create
var sqlBuilder = new SqlWhereBuilder(); //
var returnList = new List<Model.Stock.Status>();
public int InsertNewStock(int skuId, int accountJournalId)
{
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
INSERT INTO tblStock
(SkuID, AccountJournalID)
OUTPUT INSERTED.StockID
VALUES
(@skuId, @accountJournalId);";
cmd.Parameters.AddWithValue("@skuId", skuId);
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
return (int)cmd.ExecuteScalar();
}
}
//
// read
//
public int CountStockTableRecords(List<int> accountJournalId = null)
{
throw new NotImplementedException("This needs testing before use");
var sqlWhere = new SqlWhereBuilder();
//build sql query
string sql = @" string sql = @"
SELECT [StockStatusID] SELECT Count(tblStock.StockID) AS CountOfID
,[StatusCode] FROM tblStock
,[StockStatus]
,[StockStatusTypeID]
,[Reference]
,[ForeignKeyID]
,[IsCreditOnly]
,[IsClosed]
,[RecordCreated]
FROM [e2A].[dbo].[tblStockStatus]
WHERE 1=1 "; WHERE 1=1 ";
// build the where statments if (accountJournalId != null)
if (statusIds.Any())
{ {
sqlBuilder.In("StockStatusID", statusIds, "AND"); sqlWhere.In("AccountJournalID", accountJournalId, "AND");
}
if (statusTypeIds.Any())
{
sqlBuilder.In("StockStatusTypeID", statusTypeIds, "AND");
} }
// append where string to the sql if (sqlWhere.IsSetSqlWhereString)
if (sqlBuilder.IsSetSqlWhereString)
{ {
sql = sql + sqlBuilder.SqlWhereString; sql += sqlWhere.SqlWhereString;
} }
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = sql;
if (sqlWhere.ParameterListIsSet)
{
sqlWhere.AddParametersToSqlCommand(cmd);
}
return (int)cmd.ExecuteScalar();
}
}
public int? ReadStockIdByAccountJournalId(int accountJournalId)
{
if (accountJournalId <= 0)
{
throw new ArgumentException("Account Journal ID must be greater than zero", nameof(accountJournalId));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT StockID
FROM tblStock
WHERE AccountJournalID = @accountJournalId;
";
cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId);
object obj = cmd.ExecuteScalar();
// unique index on tblStock.AccountJournalID ensures that this will return at most one row
if (obj == null)
{
throw new Exception("AccountJournalID=" + accountJournalId + " does not exist.");
}
else if (obj == DBNull.Value)
{
return null; // no stock entry found
}
else
{
return (int)obj;
}
}
}
public int? ReadStockJournalId(int stockId)
{
if (stockId <= 0)
{
throw new ArgumentException("Stock ID must be greater than zero", nameof(stockId));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT StockJournalID
FROM tblStock
WHERE StockID=@stockId;;";
cmd.Parameters.AddWithValue("@stockId", stockId);
object obj = cmd.ExecuteScalar();
if (obj == null )
{
throw new Exception("StockID=" + stockId + " does not exist.");
}
else if (obj == DBNull.Value)
{
return null; // no stock journal entry
}
else
{
return (int)obj;
}
}
}
public int? ReadAccountJournalId(int stockId)
{
if (stockId <= 0)
{
throw new ArgumentException("Stock ID must be greater than zero", nameof(stockId));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
SELECT AccountJournalID
FROM tblStock
WHERE StockID=@stockId;";
cmd.Parameters.AddWithValue("@stockId", stockId);
object obj = cmd.ExecuteScalar();
if (obj == null)
{
throw new Exception("StockID=" + stockId + " does not exist.");
}
else if (obj == DBNull.Value)
{
return null; // no account journal entry
}
else
{
return (int)obj;
}
}
}
//
// update
//
public int UpdateAccountJournalId(int stockId, int? accountJournalID)
{
if (stockId <= 0)
{
throw new ArgumentException("Stock ID must be greater than zero", nameof(stockId));
}
string sql = @"
UPDATE
tblStock
SET
AccountJournalID = @accountJournalID
WHERE
StockID = @stockId;";
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{ {
cmd.CommandText = sql; cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction; cmd.Transaction = _transaction as SqlTransaction;
cmd.Parameters.AddWithValue("@stockId", stockId);
if (sqlBuilder.ParameterListIsSet) if (accountJournalID == null)
{ {
sqlBuilder.AddParametersToSqlCommand(cmd); cmd.Parameters.AddWithValue("@accountJournalID", DBNull.Value);
}
else
{
cmd.Parameters.AddWithValue("@accountJournalID", accountJournalID);
} }
using (SqlDataReader reader = cmd.ExecuteReader()) return cmd.ExecuteNonQuery();
{
var typeDict = new StockRepository(_connection, _transaction).ReadStatusType();
while (reader.Read())
{
int statusId = reader.GetInt32(0);
int? statusCode = null;
if (!reader.IsDBNull(1)) { statusCode = reader.GetInt32(1); }
string stockStatus = reader.GetString(2);
int typeId = reader.GetInt32(3);
string reference = null;
if (!reader.IsDBNull(4)) { reference = reader.GetString(4); }
int? foreignKeyId = null;
if (!reader.IsDBNull(5)) { foreignKeyId = reader.GetInt32(5); }
bool isCreditOnly = reader.GetBoolean(6);
bool isClosed = reader.GetBoolean(7);
DateTime recordCreated = DateTime.SpecifyKind(reader.GetDateTime(8), DateTimeKind.Utc);
var newItem = new Model.Stock.Status(statusId
, statusCode
, stockStatus
, typeDict[typeId]
, reference
, foreignKeyId
, isCreditOnly
, isClosed
, recordCreated
);
returnList.Add(newItem);
}
}
} }
return returnList;
} }
public Dictionary<int, Model.Stock.StatusType> ReadStatusType() public int UpdateStockJournalId(int stockId, int? stockJournalId)
{ {
var returnDict = new Dictionary<int, Model.Stock.StatusType>(); if (stockId <= 0)
{
throw new ArgumentException("Stock ID must be greater than zero", nameof(stockId));
}
// get all account info before we start string sql = @"
var accountDict = new AccountCodeRepository(_connection, _transaction).ReadAccountCode(); UPDATE
tblStock
SET
StockJournalID = @stockJournalId
WHERE
StockID = @stockId;";
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{ {
cmd.CommandText = @" cmd.CommandText = sql;
SELECT [StockStatusTypeID]
,[StatusTypeName]
,[ForeignKeyType]
,[ReferenceType]
,[AccountChartOfID]
FROM [e2A].[dbo].[tblStockStatusType]";
cmd.Transaction = _transaction as SqlTransaction; cmd.Transaction = _transaction as SqlTransaction;
cmd.Parameters.AddWithValue("@stockId", stockId);
using (SqlDataReader reader = cmd.ExecuteReader()) if (stockJournalId == null)
{ {
var accountIdDict = new Dictionary<int, int>(); cmd.Parameters.AddWithValue("@stockJournalId", DBNull.Value);
while (reader.Read())
{
int statusTypeId = reader.GetInt32(0);
string name = reader.GetString(1);
string foreignKey = null;
if (!reader.IsDBNull(2)) { foreignKey = reader.GetString(2); }
string reference = null;
if (!reader.IsDBNull(3)) { reference = reader.GetString(3); }
uint accountId = (uint)reader.GetInt32(4);
var statusType = new Model.Stock.StatusType(statusTypeId, name, foreignKey, reference, accountDict[(int)accountId]);
returnDict.Add(statusTypeId, statusType);
}
} }
else
{
cmd.Parameters.AddWithValue("@stockJournalId", stockJournalId);
}
return cmd.ExecuteNonQuery();
}
}
//
// delete
//
public int DeleteStock(int stockId)
{
if (stockId <= 0)
{
throw new ArgumentException("Stock ID must be greater than zero", nameof(stockId));
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = @"
DELETE FROM tblStock
WHERE StockID=@stockId;";
cmd.Parameters.AddWithValue("@stockId", stockId);
return cmd.ExecuteNonQuery();
} }
return returnDict;
} }
} }
} }
@@ -0,0 +1,131 @@
using Amazon.Runtime.Internal.Transform;
using bnhtrade.Core.Data.Database._BoilerPlate;
using bnhtrade.Core.Data.Database.Repository.Interface;
using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Data.Database.Repository.Implementation
{
internal class StockStatusRepository : _Base, IStockStatusRepository
{
public StockStatusRepository(IDbConnection connection, IDbTransaction transaction) : base(connection, transaction)
{
}
public Dictionary<int, Model.Stock.StatusBuilder> ReadStatus(List<int> statusIds = null, List<int> statusTypeIds = null)
{
var sqlBuilder = new SqlWhereBuilder();
var returnList = new Dictionary<int, Model.Stock.StatusBuilder>();
//build sql query
string sql = @"
SELECT [StockStatusID]
,[StatusCode]
,[StockStatus]
,[StockStatusTypeID]
,[Reference]
,[ForeignKeyID]
,[IsCreditOnly]
,[IsClosed]
,[RecordCreated]
FROM [e2A].[dbo].[tblStockStatus]
WHERE 1=1 ";
// build the where statments
if (statusIds.Any())
{
sqlBuilder.In("StockStatusID", statusIds, "AND");
}
if (statusTypeIds.Any())
{
sqlBuilder.In("StockStatusTypeID", statusTypeIds, "AND");
}
// append where string to the sql
if (sqlBuilder.IsSetSqlWhereString)
{
sql = sql + sqlBuilder.SqlWhereString;
}
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction;
if (sqlBuilder.ParameterListIsSet)
{
sqlBuilder.AddParametersToSqlCommand(cmd);
}
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var statusBuilder = new Model.Stock.StatusBuilder
{
StatusId = reader.GetInt32(0),
StatusCode = reader.IsDBNull(1) ? (int?)null : reader.GetInt32(1),
StatusTitle = reader.GetString(2),
StatusTypeId = reader.GetInt32(3),
Reference = reader.IsDBNull(4) ? null : reader.GetString(4),
ForeignKeyID = reader.IsDBNull(5) ? (int?)null : reader.GetInt32(5),
IsCreditOnly = reader.GetBoolean(6),
IsClosed = reader.GetBoolean(7),
RecordCreated = DateTime.SpecifyKind(reader.GetDateTime(8), DateTimeKind.Utc)
};
returnList.Add(statusBuilder.StatusId, statusBuilder);
}
}
}
return returnList;
}
public Dictionary<int, Model.Stock.StatusTypeBuilder> ReadStatusType(List<int> stockStatusTypeIds = null)
{
var returnDict = new Dictionary<int, Model.Stock.StatusTypeBuilder>();
var sqlWhere = new SqlWhereBuilder();
string sql = @"
SELECT [StockStatusTypeID]
,[StatusTypeName]
,[ForeignKeyType]
,[ReferenceType]
,[AccountChartOfID]
FROM [e2A].[dbo].[tblStockStatusType]
WHERE 1=1 ";
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction;
if (sqlWhere.ParameterListIsSet)
{
sqlWhere.AddParametersToSqlCommand(cmd);
}
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var statusTypeBuilder = new Model.Stock.StatusTypeBuilder
{
StockStatusTypeID = reader.GetInt32(0),
StatusTypeName = reader.GetString(1),
ForeignKeyType = reader.IsDBNull(2) ? null : reader.GetString(2),
ReferenceType = reader.IsDBNull(3) ? null : reader.GetString(3),
AccountId = reader.GetInt32(4)
};
returnDict.Add(statusTypeBuilder.StockStatusTypeID, statusTypeBuilder);
}
}
}
return returnDict;
}
}
}
@@ -44,8 +44,8 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation
using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand)
{ {
cmd.CommandText = sql;
cmd.Transaction = _transaction as SqlTransaction; cmd.Transaction = _transaction as SqlTransaction;
cmd.CommandText = sql;
sqlwhere.AddParametersToSqlCommand(cmd); sqlwhere.AddParametersToSqlCommand(cmd);
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Data.Database.Repository.Interface
{
internal interface IAccountJournalRepository
{
int InsertJournalHeader(int journalTypeId, DateTime entryDate, bool lockEntry = false);
int InsertJournalPost(int accountJournalId, int accountChartOfId, decimal amountGbp);
Dictionary<int, Model.Account.JournalBuilder> ReadJournalBuilder(List<int> journalIdList);
DateTime ReadJournalEntryDate(int journalId);
int CountJournalPosts(int accountJournalId);
bool IsJournalDebitAssetType(int accountJournalId);
bool? IsJournalLocked(int journalId);
List<(int, string, int?, int?)> ReadJournalType(List<int> accountJournalTypeIds = null, List<string> typeTitles = null);
(int?, int?) ReadJournalTypeDefaultDebitCredit(int accountJournalId);
bool UpdateJournalEntryModifiedDate(int accountJournalId);
int DeleteJournalHeader(int accountJournalId);
int DeleteJournalPostAll(int accountJournalId);
}
}
@@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Data.Database.Repository.Interface
{
internal interface IJournalRepository
{
Dictionary<int, Core.Model.Account.Journal> ReadJournal(List<int> journalIdList);
bool ReadJournalIsLocked(int journalId);
Dictionary<int, Model.Account.JournalType> ReadJournalType(List<int> journalTypeIds = null, List<string> typeTitles = null);
bool DeleteJournal(int accountJournalId);
}
}
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Data.Database.Repository.Interface
{
internal interface IPurchaseRepository
{
int DeletePurchaseLineTransaction(int accountJournalId);
void InsertPurchaseLineTransaction(int accountJournalId, int purchaseLineId);
void WIP_PurchaseLineTransactionNetUpdate(int accountJouranlId, string currencyCode, decimal amountNet, int debitAccountId);
}
}
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Data.Database.Repository.Interface
{
internal interface IStockJournalRepository
{
int InsertStockJournalHeader(int stockId, int journalTypeId, DateTime entryDate, bool isLocked);
int InsertStockJournalPost(int stockJournalId, int stockStatusId, int quantity);
Dictionary<int, Model.Stock.StockJournalBuilder> ReadStockJournal(bool lockRecords = false,
List<int> stockJournalIds = null, List<int> stockIds = null, List<string> stockNumbers = null
, DateTime? minEntryDate = null, DateTime? maxEntryDate = null, List<int> stockStatusIds = null);
Dictionary<int, (int id, string title, int? stockStatusIdDebit, int? stockStatusIdCredit)> ReadStockJournalType(List<int> stockJournalTypeIdList);
(int, DateTime) ReadJournalStockIdAndEntryDate(int stockJournalId);
int ReadJournalTypeIdByStockId(int stockId);
int ReadStatusBalanceBySku(string sku, int statusId);
int ReadStatusBalanceByStockNumber(int stockNumber, int statusId);
int ReadStatusBalanceByStockId(int stockId, int statusId);
int ReadJournalEntryCountByStockId(int stockId);
int? ReadTypeIdStatusCreditId(int stockJournalTypeId);
DateTime? ReadMostRecentEntryDateForStatusDebit(int stockId, List<int> stockStatusIdList);
void StockJournalDelete(int stockJournalId);
}
}
@@ -8,7 +8,13 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface
{ {
internal interface IStockRepository internal interface IStockRepository
{ {
List<Model.Stock.Status> ReadStatus(List<int> statusIds = null, List<int> statusTypeIds = null); int InsertNewStock(int skuId, int accountJournalId);
Dictionary<int, Model.Stock.StatusType> ReadStatusType(); int CountStockTableRecords(List<int> accountJournalId = null);
int? ReadStockIdByAccountJournalId(int accountJournalId);
int? ReadAccountJournalId(int stockId);
int? ReadStockJournalId(int stockId);
int UpdateAccountJournalId(int stockId, int? accountJournalID);
int UpdateStockJournalId(int stockId, int? stockJournalID);
int DeleteStock(int stockId);
} }
} }
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Data.Database.Repository.Interface
{
internal interface IStockStatusRepository
{
Dictionary<int, Model.Stock.StatusBuilder> ReadStatus(List<int> statusIds = null, List<int> statusTypeIds = null);
Dictionary<int, Model.Stock.StatusTypeBuilder> ReadStatusType(List<int> stockStatusTypeIds = null);
}
}
@@ -8,17 +8,15 @@ using Microsoft.IdentityModel.Tokens;
namespace bnhtrade.Core.Data.Database namespace bnhtrade.Core.Data.Database
{ {
/// <summary> /// <summary>
/// Step 1: Call the methods for each where clause you want to create. This can be done multiple times to create an sql where string. Pay attention /// Sep 1: Call each Where_____ method in this class as needed, the method returns a string that can be appended to your SQL statement and builds
/// to the prefixes that you'll require between each where clause, as each time a method is called the sql statement will be appended to the previous sql /// a parameter list.
/// string.
/// ///
/// Step 2: Appened the created sql string to your sql statement, NB the WHERE statemet is not included by default. /// Step 2: Once you've created your sql command object, add the parameters to it using the method contained within this class.
///
/// Step 3: Once you've created your sql command object, add the parameters to it using the method contained within this class.
/// ///
/// Step 4: exceute your sql commend. /// Step 4: exceute your sql commend.
/// </summary> /// </summary>
public class SqlWhereBuilder
internal class SqlWhereBuilder
{ {
private int parameterCount = 0; private int parameterCount = 0;
@@ -27,8 +25,10 @@ namespace bnhtrade.Core.Data.Database
Init(); Init();
} }
[Obsolete("Deprecated. Use the new methods.")]
public string SqlWhereString { get; private set; } public string SqlWhereString { get; private set; }
[Obsolete("Deprecated. Use the new methods.")]
public bool IsSetSqlWhereString public bool IsSetSqlWhereString
{ {
get get
@@ -115,6 +115,7 @@ namespace bnhtrade.Core.Data.Database
/// <param name="columnReference">Name of the column to used to for the condition statement</param> /// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="phraseList">List of phrases to test in condition statement</param> /// <param name="phraseList">List of phrases to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param> /// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
[Obsolete("Deprecated. Use the new methods.")]
public void LikeAnd(string columnReference, IEnumerable<string> phraseList, string wherePrefix = null) public void LikeAnd(string columnReference, IEnumerable<string> phraseList, string wherePrefix = null)
{ {
Like(columnReference, phraseList, true, wherePrefix); Like(columnReference, phraseList, true, wherePrefix);
@@ -126,11 +127,13 @@ namespace bnhtrade.Core.Data.Database
/// <param name="columnReference">Name of the column to used to for the condition statement</param> /// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="phraseList">List of phrases to test in condition statement</param> /// <param name="phraseList">List of phrases to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param> /// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
[Obsolete("Deprecated. Use the new methods.")]
public void LikeOr(string columnReference, IEnumerable<string> phraseList, string wherePrefix = null) public void LikeOr(string columnReference, IEnumerable<string> phraseList, string wherePrefix = null)
{ {
Like(columnReference, phraseList, false, wherePrefix); Like(columnReference, phraseList, false, wherePrefix);
} }
[Obsolete("Deprecated. Use the new methods.")]
private void Like(string columnReference, IEnumerable<string> phraseList, bool isAnd, string wherePrefix = null) private void Like(string columnReference, IEnumerable<string> phraseList, bool isAnd, string wherePrefix = null)
{ {
if (phraseList == null || !phraseList.Any()) if (phraseList == null || !phraseList.Any())
@@ -190,35 +193,62 @@ namespace bnhtrade.Core.Data.Database
} }
public void IsEqualTo(string columnReference, bool booleanValue, string wherePrefix = null) //public void IsEqualTo(string columnReference, bool booleanValue, string wherePrefix = null)
//{
// string sqlWhereString = @"
// ";
// if (wherePrefix != null)
// {
// sqlWhereString += wherePrefix;
// }
// sqlWhereString += " ( " + columnReference + " = ";
// if (booleanValue)
// {
// sqlWhereString += "1 ) ";
// }
// else
// {
// sqlWhereString += "0 ) ";
// }
// SqlWhereString = SqlWhereString + sqlWhereString;
//}
[Obsolete("Deprecated. Use the new methods.")]
public void IsEqualTo(string columnReference, object whereArgument, string wherePrefix = null)
{ {
string sqlWhereString = @" IsEqualSub(columnReference, whereArgument, "=", wherePrefix);
";
if (wherePrefix != null)
{
sqlWhereString += wherePrefix;
}
sqlWhereString += " ( " + columnReference + " = ";
if (booleanValue)
{
sqlWhereString += "1 ) ";
}
else
{
sqlWhereString += "0 ) ";
}
SqlWhereString = SqlWhereString + sqlWhereString;
} }
public void IsEqualTo(string columnReference, string stringValue, string wherePrefix = null) [Obsolete("Deprecated. Use the new methods.")]
public void IsEqualToOrLessThan(string columnReference, object whereArgument, string wherePrefix = null)
{ {
if (string.IsNullOrEmpty(stringValue) || string.IsNullOrEmpty(columnReference)) IsEqualSub(columnReference, whereArgument, "<=", wherePrefix);
}
[Obsolete("Deprecated. Use the new methods.")]
public void IsEqualToOrGreaterThan(string columnReference, object whereArgument, string wherePrefix = null)
{
IsEqualSub(columnReference, whereArgument, ">=", wherePrefix);
}
[Obsolete("Deprecated. Use the new methods.")]
private void IsEqualSub(string columnReference, object whereArgument, string operatorString, string wherePrefix = null)
{
if (string.IsNullOrEmpty(columnReference))
{ {
throw new Exception(wherePrefix + " IsEqualTo method requires a valid column reference and string value."); throw new Exception(wherePrefix + " IsEqual method requires a valid column reference.");
}
if (whereArgument == null)
{
throw new Exception(wherePrefix + " IsEqual method requires a valid where argument.");
}
if (whereArgument is string && string.IsNullOrEmpty(whereArgument.ToString()))
{
throw new Exception(wherePrefix + " IsEqual method requires a valid where argument.");
} }
string sqlWhereString = @" string sqlWhereString = @"
@@ -228,27 +258,26 @@ namespace bnhtrade.Core.Data.Database
{ {
sqlWhereString += wherePrefix; sqlWhereString += wherePrefix;
} }
sqlWhereString += " ( " + columnReference + " " + operatorString + " " + GetSetParameter(whereArgument) + " ) ";
sqlWhereString += " ( " + columnReference + " = " + GetSetParameter(stringValue) + " ) ";
SqlWhereString = SqlWhereString + sqlWhereString; SqlWhereString = SqlWhereString + sqlWhereString;
} }
/// <summary> /// <summary>
/// Append an 'In' statement and parameter list to the class properties /// Append an 'In' statement and parameter list to the class properties
/// </summary> /// </summary>
/// <param name="columnReference">Name of the column to used to for the condition statement</param> /// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="orValueList">List of values to test in condition statement</param> /// <param name="orArgumentList">List of values to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param> /// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void In(string columnReference, IEnumerable<object> orValueList, string wherePrefix = null) [Obsolete("Deprecated. Use the new methods.")]
public void In(string columnReference, IEnumerable<object> orArgumentList, string wherePrefix = null)
{ {
if (orValueList == null || !orValueList.Any()) if (orArgumentList == null || !orArgumentList.Any())
{ {
return; return;
} }
var distinctList = orValueList.Distinct().ToList(); var distinctList = orArgumentList.Distinct().ToList();
string sqlWhere = @" string sqlWhere = @"
"; ";
@@ -279,15 +308,16 @@ namespace bnhtrade.Core.Data.Database
/// Append an 'In' statement and parameter list to the class properties /// Append an 'In' statement and parameter list to the class properties
/// </summary> /// </summary>
/// <param name="columnReference">Name of the column to used to for the condition statement</param> /// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="orValueList">List of values to test in condition statement</param> /// <param name="orArgumentList">List of values to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param> /// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void In(string columnReference, IEnumerable<string> orValueList, string wherePrefix = null) [Obsolete("Deprecated. Use the new methods.")]
public void In(string columnReference, IEnumerable<string> orArgumentList, string wherePrefix = null)
{ {
var objectList = new List<object>(); var objectList = new List<object>();
if (orValueList != null && orValueList.Any()) if (orArgumentList != null && orArgumentList.Any())
{ {
foreach (string value in orValueList) foreach (string value in orArgumentList)
{ {
objectList.Add(value.ToString()); objectList.Add(value.ToString());
} }
@@ -300,15 +330,16 @@ namespace bnhtrade.Core.Data.Database
/// Append an 'In' statement and parameter list to the class properties /// Append an 'In' statement and parameter list to the class properties
/// </summary> /// </summary>
/// <param name="columnReference">Name of the column to used to for the condition statement</param> /// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="orValueList">List of values to test in condition statement</param> /// <param name="orArgumentList">List of values to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param> /// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void In(string columnReference, IEnumerable<int> orValueList, string wherePrefix = null) [Obsolete("Deprecated. Use the new methods.")]
public void In(string columnReference, IEnumerable<int> orArgumentList, string wherePrefix = null)
{ {
var objectList = new List<object>(); var objectList = new List<object>();
if (orValueList != null && orValueList.Any()) if (orArgumentList != null && orArgumentList.Any())
{ {
foreach (var value in orValueList) foreach (var value in orArgumentList)
{ {
objectList.Add(value.ToString()); objectList.Add(value.ToString());
} }
@@ -321,15 +352,16 @@ namespace bnhtrade.Core.Data.Database
/// Append an 'In' statement and parameter list to the class properties /// Append an 'In' statement and parameter list to the class properties
/// </summary> /// </summary>
/// <param name="columnReference">Name of the column to used to for the condition statement</param> /// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="orValueList">List of values to test in condition statement</param> /// <param name="orArgumentList">List of values to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param> /// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public void In(string columnReference, IEnumerable<uint> orValueList, string wherePrefix = null) [Obsolete("Deprecated. Use the new methods.")]
public void In(string columnReference, IEnumerable<uint> orArgumentList, string wherePrefix = null)
{ {
var objectList = new List<object>(); var objectList = new List<object>();
if (orValueList != null && orValueList.Any()) if (orArgumentList != null && orArgumentList.Any())
{ {
foreach (var value in orValueList) foreach (var value in orArgumentList)
{ {
objectList.Add(value.ToString()); objectList.Add(value.ToString());
} }
@@ -338,13 +370,14 @@ namespace bnhtrade.Core.Data.Database
In(columnReference, objectList, wherePrefix); In(columnReference, objectList, wherePrefix);
} }
public void In(string columnReference, IEnumerable<DateTime> orValueList, string wherePrefix = null) [Obsolete("Deprecated. Use the new methods.")]
public void In(string columnReference, IEnumerable<DateTime> orArgumentList, string wherePrefix = null)
{ {
var objectList = new List<object>(); var objectList = new List<object>();
if (orValueList != null && orValueList.Any()) if (orArgumentList != null && orArgumentList.Any())
{ {
foreach(var value in orValueList) foreach(var value in orArgumentList)
{ {
objectList.Add(value.ToString()); objectList.Add(value.ToString());
} }
@@ -353,17 +386,188 @@ namespace bnhtrade.Core.Data.Database
In(columnReference, objectList, wherePrefix); In(columnReference, objectList, wherePrefix);
} }
//
// new code below, going to change everything over to these methods, it returns the sql string instead of appending it to the class property
// which is more versatile and more intuitive to use
//
/// <summary>
/// Append an 'Like' statement (with AND between each like string) and parameter list to the class properties
/// </summary>
/// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="arguments">List of phrases to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public string LikeAndClause(string columnReference, IEnumerable<string> arguments, string clausePrefix = null, string clausePostfix = null)
{
return LikeClause(columnReference, arguments, true, clausePrefix, clausePostfix);
}
/// <summary>
/// Append an 'Like' statement (with OR between each like string) and parameter list to the class properties
/// </summary>
/// <param name="columnReference">Name of the column to used to for the condition statement</param>
/// <param name="phraseList">List of phrases to test in condition statement</param>
/// <param name="wherePrefix">Optional prefix that gets added to the sql string result</param>
public string LikeOrClause(string columnReference, IEnumerable<string> arguments, string clausePrefix = null, string clausePostfix = null)
{
return LikeClause(columnReference, arguments, false, clausePrefix, clausePostfix);
}
private string LikeClause(string columnReference, IEnumerable<string> arguments, bool isAnd, string clausePrefix = null, string clausePostfix = null)
{
if (arguments == null || !arguments.Any())
{
throw new ArgumentException("The arguments cannot be null or empty.", nameof(arguments));
}
// ensure no values are repeated
var distinctList = arguments.Distinct().ToList();
// clean the list
for (int i = 0; i < distinctList.Count; i++)
{
if (string.IsNullOrEmpty(distinctList[i]))
{
distinctList.RemoveAt(i);
i--;
}
}
// check again
if (distinctList == null || !distinctList.Any())
{
throw new ArgumentException("The arguments must contain at least one valid value.", nameof(arguments));
}
string sqlWhere = Environment.NewLine + clausePrefix + " ";
int listCount = distinctList.Count();
for (int i = 0; i < listCount; i++)
{
if (i > 0)
{
if (isAnd)
{
sqlWhere += " AND ";
}
else
{
sqlWhere += " OR ";
}
}
sqlWhere += " ( " + columnReference + " LIKE '%' + ";
sqlWhere = sqlWhere + GetSetParameter(distinctList[i]) + " + '%' ) ";
}
return sqlWhere + clausePostfix;
}
public string IsEqualToClause(string columnReference, object whereArgument, string clausePrefix = null, string clausePostfix = null)
{
return IsEqualSubClause(columnReference, whereArgument, "=", clausePrefix, clausePostfix);
}
public string IsEqualToOrLessThanClause(string columnReference, object whereArgument, string clausePrefix = null, string clausePostfix = null)
{
return IsEqualSubClause(columnReference, whereArgument, "<=", clausePrefix, clausePostfix);
}
public string IsEqualToOrGreaterThanClause(string columnReference, object whereArgument, string clausePrefix = null, string clausePostfix = null)
{
return IsEqualSubClause(columnReference, whereArgument, ">=", clausePrefix, clausePostfix);
}
private string IsEqualSubClause(string columnReference, object whereArgument, string operatorString, string clausePrefix = null, string clausePostfix = null)
{
if (string.IsNullOrEmpty(columnReference))
{
throw new Exception(" WhereIsEqual* method requires a valid column reference.");
}
if (whereArgument == null)
{
throw new Exception(" WhereIsEqual* method requires a valid where argument.");
}
if (whereArgument is string && string.IsNullOrEmpty(whereArgument.ToString()))
{
throw new Exception(" WhereIsEqual* method requires a valid where argument.");
}
// build the SQL WHERE clause and parameter list
string sqlWhere = Environment.NewLine + clausePrefix + " ( " + columnReference + " " + operatorString + " " + GetSetParameter(whereArgument) + " ) ";
return sqlWhere + clausePostfix;
}
/// <summary>
/// Constructs a SQL WHERE clause with an IN condition for the specified column and a list of values and adds to the parameter list.
/// </summary>
/// <param name="columnReference">The name of the column to be used in the IN condition</param>
/// <param name="orArgumentList">A collection of values to include in the IN condition.</param>
/// <param name="lineReturn">A boolean value indicating whether to prefix the returned SQL clause with a line return</param>
/// <returns>A string representing the SQL WHERE clause with the specified column and values in the IN condition.</returns>
public string InClause<T>(string columnReference, IEnumerable<T> orArgumentList, string clausePrefix = null, string clausePostfix = null)
{
// new code, going to change everything over to this method, it returns the sql string instead of appending it to the class property
// which is more versatile and more intuitive to use
if (string.IsNullOrEmpty(columnReference))
{
throw new ArgumentException("Column reference cannot be null or empty.", nameof(columnReference));
}
if (orArgumentList == null || !orArgumentList.Any())
{
throw new ArgumentException("The orArgumentList cannot be null or empty.", nameof(orArgumentList));
}
// ensure no values are repeated, null, or empty
var distinctList = orArgumentList
.Where(x =>
x is not null && // not null
(!(x.GetType().IsGenericType && x.GetType().GetGenericTypeDefinition() == typeof(Nullable<>)) // not a nullable type
|| x.GetType().GetProperty("HasValue")?.GetValue(x) as bool? == true) // or, if nullable, HasValue is true
&& (!(x is string s) || !string.IsNullOrEmpty(s)) // not an empty string
)
.ToList();
if (!distinctList.Any())
{
throw new ArgumentException("The orArgumentList must contain at least one valid value.", nameof(orArgumentList));
}
// build the SQL WHERE clause and parameter list
string sqlWhere = Environment.NewLine + clausePrefix + " " + columnReference + " IN ( ";
int listCount = distinctList.Count();
for (int i = 0; i < listCount; i++)
{
if (i > 0)
{
sqlWhere += ", ";
}
sqlWhere = sqlWhere + GetSetParameter(distinctList[i]);
}
sqlWhere += " ) ";
return sqlWhere + clausePostfix;
}
/// <summary> /// <summary>
/// Adds a string value to the ParameterList and returns '@parameterN' that is then appended to the SQL statement /// Adds a string value to the ParameterList and returns '@parameterN' that is then appended to the SQL statement
/// i.e. @parameter1, @parameter2, etc. /// i.e. @parameter1, @parameter2, etc.
/// </summary> /// </summary>
/// <param name="value">parameter string that is then appended to the SQL statement</param> /// <param name="argument">parameter string that is then appended to the SQL statement</param>
/// <returns></returns> /// <returns></returns>
private string GetSetParameter(string value) private string GetSetParameter(object argument)
{ {
parameterCount++; parameterCount++;
string parameterString = "@parameter" + parameterCount; string parameterString = "@parameter" + parameterCount;
ParameterList.Add(parameterString, value); ParameterList.Add(parameterString, argument);
return parameterString; return parameterString;
} }
} }
@@ -373,7 +373,7 @@ namespace bnhtrade.Core.Data.Database.Stock
{ {
if (item.Value < 0 && dicStatusIsCreditOnly[item.Key] == false) if (item.Value < 0 && dicStatusIsCreditOnly[item.Key] == false)
{ {
int quantity = new Data.Database.Stock.ReadStatusBalance().ByStockId(stockId, item.Key); int quantity = new Data.Database.Stock.ReadStatusBalance().ReadStatusBalanceByStockId(stockId, item.Key);
if (quantity + item.Value < 0) if (quantity + item.Value < 0)
{ {
@@ -13,76 +13,7 @@ namespace bnhtrade.Core.Data.Database.Stock
{ {
} }
public int BySku(string sku, int statusId) public int ReadStatusBalanceByStockId(int stockId, int statusId)
{
int statusBalance = new int();
using (SqlConnection conn = new SqlConnection(SqlConnectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(@"
SELECT SUM(tblStockJournalPost.Quantity) AS Balance
FROM tblStockJournal INNER JOIN
tblStockJournalPost ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID INNER JOIN
tblStock ON tblStockJournal.StockID = tblStock.StockID INNER JOIN
tblSku ON tblStock.SkuID = tblSku.skuSkuID
WHERE (tblStockJournalPost.StockStatusID = @statusId) AND (tblSku.skuSkuNumber = @sku)
", conn))
{
// add parameters
cmd.Parameters.AddWithValue("@sku", sku);
cmd.Parameters.AddWithValue("@statusId", statusId);
// execute
object obj = cmd.ExecuteScalar();
if (obj == null || obj == DBNull.Value)
{
statusBalance = 0;
}
else
{
statusBalance = (int)obj;
}
}
}
return statusBalance;
}
public int ByStockNumber(int stockNumber, int statusId)
{
int statusBalance = new int();
using (SqlConnection conn = new SqlConnection(SqlConnectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(@"
SELECT SUM(tblStockJournalPost.Quantity) AS Balance
FROM tblStockJournal INNER JOIN
tblStockJournalPost ON tblStockJournal.StockJournalID = tblStockJournalPost.StockJournalID INNER JOIN
tblStock ON tblStockJournal.StockID = tblStock.StockID
WHERE (tblStockJournalPost.StockStatusID = @statusId) AND (tblStock.StockNumber = @stockNumber)
", conn))
{
// add parameters
cmd.Parameters.AddWithValue("@stockNumber", stockNumber);
cmd.Parameters.AddWithValue("@statusId", statusId);
// execute
object obj = cmd.ExecuteScalar();
if (obj == null || obj == DBNull.Value)
{
statusBalance = 0;
}
else
{
statusBalance = (int)obj;
}
}
}
return statusBalance;
}
public int ByStockId(int stockId, int statusId)
{ {
int statusBalance = new int(); int statusBalance = new int();
using (SqlConnection conn = new SqlConnection(SqlConnectionString)) using (SqlConnection conn = new SqlConnection(SqlConnectionString))
@@ -125,7 +56,7 @@ namespace bnhtrade.Core.Data.Database.Stock
/// </summary> /// </summary>
/// <param name="statusId">The stock status id</param> /// <param name="statusId">The stock status id</param>
/// <returns>Dictionary of SKUs and the balance of each</returns> /// <returns>Dictionary of SKUs and the balance of each</returns>
public Dictionary<string, int> ByStatusId(int statusId) public Dictionary<string, int> ReadStatusBalanceByStatusId(int statusId)
{ {
var returnList = new Dictionary<string, int>(); var returnList = new Dictionary<string, int>();
@@ -16,10 +16,13 @@ namespace bnhtrade.Core.Data.Database.UnitOfWork
IExportInvoiceRepository ExportInvoiceRepository { get; } IExportInvoiceRepository ExportInvoiceRepository { get; }
IAmazonSettlementRepository AmazonSettlementRepository { get; } IAmazonSettlementRepository AmazonSettlementRepository { get; }
IInvoiceRepository InvoiceRepository { get; } IInvoiceRepository InvoiceRepository { get; }
IJournalRepository JournalRepository { get; } IPurchaseRepository PurchaseRepository { get; }
IAccountJournalRepository AccountJournalRepository { get; }
ISequenceGenerator SequenceGenerator { get; } ISequenceGenerator SequenceGenerator { get; }
ISkuRepository SkuRepository { get; } ISkuRepository SkuRepository { get; }
IStockRepository StockRepository { get; } IStockRepository StockRepository { get; }
IStockJournalRepository StockJournalRepository { get; }
IStockStatusRepository StockStatusRepository { get; }
// Methods to manage the transaction // Methods to manage the transaction
void Commit(); void Commit();
@@ -19,15 +19,18 @@ namespace bnhtrade.Core.Data.Database.UnitOfWork
// Private field for lazy loading, add here for each repository // Private field for lazy loading, add here for each repository
private IAccountCodeRepository _accountCodeRepository; private IAccountCodeRepository _accountCodeRepository;
private IAccountJournalRepository _accountJournalRepository;
private IAccountTaxRepository _accountTaxRepository; private IAccountTaxRepository _accountTaxRepository;
private IAmazonSettlementRepository _amazonSettlementRepository; private IAmazonSettlementRepository _amazonSettlementRepository;
private ICurrencyRepository _currencyRepository; private ICurrencyRepository _currencyRepository;
private IExportInvoiceRepository _exportInvoiceRepository; private IExportInvoiceRepository _exportInvoiceRepository;
private IInvoiceRepository _invoiceRepository; private IInvoiceRepository _invoiceRepository;
private IJournalRepository _journalRepository; private IPurchaseRepository _purchaseRepository;
private ISequenceGenerator _sequenceGenerator; private ISequenceGenerator _sequenceGenerator;
private ISkuRepository _skuRepository; private ISkuRepository _skuRepository;
private IStockRepository _stockRepository; private IStockRepository _stockRepository;
private IStockJournalRepository _stockJournalRespository;
private IStockStatusRepository _stockStatusRepository;
internal UnitOfWork() internal UnitOfWork()
{ {
@@ -48,6 +51,18 @@ namespace bnhtrade.Core.Data.Database.UnitOfWork
} }
} }
public IAccountJournalRepository AccountJournalRepository
{
get
{
if (_accountJournalRepository == null)
{
_accountJournalRepository = new AccountJournalRepository(_connection, _transaction);
}
return _accountJournalRepository;
}
}
public IAccountTaxRepository AccountTaxRepository public IAccountTaxRepository AccountTaxRepository
{ {
get get
@@ -108,15 +123,15 @@ namespace bnhtrade.Core.Data.Database.UnitOfWork
} }
} }
public IJournalRepository JournalRepository public IPurchaseRepository PurchaseRepository
{ {
get get
{ {
if (_journalRepository == null) if (_purchaseRepository == null)
{ {
_journalRepository = new JournalRepository(_connection, _transaction); _purchaseRepository = new PurchaseRepository(_connection, _transaction);
} }
return _journalRepository; return _purchaseRepository;
} }
} }
@@ -156,6 +171,30 @@ namespace bnhtrade.Core.Data.Database.UnitOfWork
} }
} }
public IStockJournalRepository StockJournalRepository
{
get
{
if (_stockJournalRespository == null)
{
_stockJournalRespository = new StockJournalRepository(_connection, _transaction);
}
return _stockJournalRespository;
}
}
public IStockStatusRepository StockStatusRepository
{
get
{
if (_stockStatusRepository == null)
{
_stockStatusRepository = new StockStatusRepository(_connection, _transaction);
}
return _stockStatusRepository;
}
}
public void Commit() public void Commit()
{ {
try try
@@ -1,4 +1,5 @@
using System; using bnhtrade.Core.Data.Database.UnitOfWork;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -12,52 +13,16 @@ namespace bnhtrade.Core.Logic.Account
{ {
} }
public Dictionary<int, Model.Account.Account> GetAll() internal AccountCodeService(IUnitOfWork unitOfWork) : base(unitOfWork)
{
}
public Dictionary<int, Model.Account.Account> GetAccountCode(List<int> accountIdList = null, List<int> accountCodeList = null)
{ {
return WithUnitOfWork(uow => return WithUnitOfWork(uow =>
{ {
return uow.AccountCodeRepository.ReadAccountCode(); return uow.AccountCodeRepository.ReadAccountCode(accountIdList, accountCodeList);
}); });
} }
public Model.Account.Account ByAccountCode(int accountCode)
{
var list = ByAccountCode(new List<int> { accountCode });
if (list.Any())
{
return list[0];
}
else
{
return null;
}
}
public List<Model.Account.Account> ByAccountCode(List<int> accountCodeList)
{
return WithUnitOfWork(uow =>
{
return uow.AccountCodeRepository.ReadAccountCode(null, accountCodeList).Values.ToList();
});
}
public Dictionary<int, Model.Account.Account> ConvertToDictionary(List<Model.Account.Account> accountCodeList)
{
var returnDict = new Dictionary<int, Model.Account.Account>();
if (accountCodeList == null)
{
return returnDict;
}
foreach (var accountCode in accountCodeList)
{
if (!returnDict.ContainsKey((int)accountCode.AccountCode))
{
returnDict.Add((int)accountCode.AccountCode, accountCode);
}
}
return returnDict;
}
} }
} }
@@ -0,0 +1,300 @@
using bnhtrade.Core.Data.Database;
using bnhtrade.Core.Data.Database.Repository.Implementation;
using bnhtrade.Core.Data.Database.UnitOfWork;
using FikaAmazonAPI.AmazonSpApiSDK.Models.FulfillmentInbound;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtrade.Core.Logic.Account
{
public class AccountJournalService : UnitOfWorkBase
{
public AccountJournalService()
{
}
internal AccountJournalService(IUnitOfWork unitOfWork) : base(unitOfWork)
{
}
public int JournalInsert(int journalTypeId, DateTime entryDate, string currencyCode,
decimal amount, int? debitAccountId = null, int? creditAccountId = null, bool lockEntry = false)
{
return WithUnitOfWork(uow =>
{
// insert header record
int journalId = uow.AccountJournalRepository.InsertJournalHeader(journalTypeId, entryDate, lockEntry);
// ensure their are no other entries (not sure why this is needed, but it was in the old code)
int count = uow.AccountJournalRepository.CountJournalPosts(journalId);
if (count > 0)
{
throw new Exception("Unable the insert journal posts, post already present AccountJournalID=" + journalId);
}
// check defaults for debit and credit accounts
var result = uow.AccountJournalRepository.ReadJournalTypeDefaultDebitCredit(journalId);
int? defaultDebit = result.Item1;
int? defaultCredit = result.Item2;
if (defaultDebit == null)
{
if (debitAccountId == null)
{
throw new Exception("Debit Account ID required, default not set for journal type");
}
}
else
{
if (debitAccountId == null)
{
debitAccountId = defaultDebit.Value;
}
else if (debitAccountId.Value != defaultDebit)
{
throw new Exception("Debit Account ID supplied does not match default set for journal type");
}
}
if (defaultCredit == null)
{
if (creditAccountId == null)
{
throw new Exception("Credit Account ID required, default not set for journal type");
}
}
else
{
if (creditAccountId == null)
{
creditAccountId = defaultCredit.Value;
}
else if (creditAccountId.Value != defaultCredit)
{
throw new Exception("Credit Account ID supplied does not match default set for journal type");
}
}
// currency conversion
if (currencyCode != "GBP")
{
amount = new Logic.Account.CurrencyService(uow).CurrencyConvertToGbp(currencyCode, amount, entryDate);
}
// ensure decimal is rounded
amount = Math.Round(amount, 2);
// insert posts
int debitPostId = uow.AccountJournalRepository.InsertJournalPost(journalId, debitAccountId.Value, amount);
int creditPostId = uow.AccountJournalRepository.InsertJournalPost(journalId, creditAccountId.Value, amount * -1);
// need to add verification here to ensure the entry is correct
// finished
CommitIfOwned(uow);
return journalId;
});
}
public Dictionary<int, Core.Model.Account.Journal> ReadJournal(List<int> journalIdList)
{
throw new NotImplementedException("done, but needs testing");
var returnDict = new Dictionary<int, Core.Model.Account.Journal>();
WithUnitOfWork(uow =>
{
var builderDict = uow.AccountJournalRepository.ReadJournalBuilder(journalIdList);
if (builderDict.Any())
{
// build list of journal types and accounts from the builderDict
var journalTypeIdList = new List<int>();
var accountIdList = new List<int>();
foreach (var journal in builderDict.Values)
{
journalTypeIdList.Add(journal.JournalTypeId);
foreach (var post in journal.JournalBuilderPosts)
{
accountIdList.Add(post.AccountId);
}
}
journalTypeIdList = journalTypeIdList.Distinct().ToList();
accountIdList = accountIdList.Distinct().ToList();
// get object dictionaries
var journalTypeDict = ReadJournalType(journalTypeIdList);
var accountDict = uow.AccountCodeRepository.ReadAccountCode(accountIdList);
// build final return dictionary
foreach (var item in builderDict)
{
var journal = item.Value.Build(journalTypeDict, accountDict);
returnDict.Add(journal.JournalId, journal);
}
}
});
return returnDict;
}
public DateTime ReadJournalEntryDate(int journalId)
{
return WithUnitOfWork(uow =>
{
return uow.AccountJournalRepository.ReadJournalEntryDate(journalId);
});
}
public Dictionary<int, Model.Account.JournalType> ReadJournalType(List<int> journalTypeIdList)
{
var returnDict = new Dictionary<int, Model.Account.JournalType>();
WithUnitOfWork(uow =>
{
// get base info from db
var dbJournalTypeList = uow.AccountJournalRepository.ReadJournalType(journalTypeIdList);
// build list of account object to retrieve
var accountIdList = new List<int>();
foreach (var dbJournalType in dbJournalTypeList)
{
if (dbJournalType.Item3.HasValue)
{
accountIdList.Add(dbJournalType.Item3.Value);
}
if (dbJournalType.Item4.HasValue)
{
accountIdList.Add(dbJournalType.Item4.Value);
}
}
accountIdList = accountIdList.Distinct().ToList();
// retieve account objects from db
var accountDict = uow.AccountCodeRepository.ReadAccountCode(accountIdList);
// build the return dictionary
foreach (var dbJournalType in dbJournalTypeList)
{
var journalType = new Model.Account.JournalType(
dbJournalType.Item1, // JournalTypeId
dbJournalType.Item2, // Name
dbJournalType.Item3.HasValue ? accountDict[dbJournalType.Item3.Value] : null, // DebitAccount
dbJournalType.Item4.HasValue ? accountDict[dbJournalType.Item4.Value] : null // CreditAccount
);
returnDict.Add(journalType.JournalTypeId, journalType);
}
});
return returnDict;
}
/// <summary>
/// Deletes all posts associated with an entry and replaces the posts for a journal entry with two new posts
/// </summary>
/// <param name="twoPostCheck">Raise exception if journal entry has more than two associated posts</param>
internal bool AccountJournalPostReplace(int accountJournalId, string currencyCode, decimal amountGbp, int debitAccountId = 0, int creditAccountId = 0, bool twoPostCheck = true)
{
if (amountGbp <= 0)
{
throw new ArgumentException("Amount must be greater than zero", nameof(amountGbp));
}
if ( amountGbp.Scale > 2)
{
throw new ArgumentException("Amount must have a maximum of two decimal places", nameof(amountGbp));
}
if (debitAccountId <= 0 || creditAccountId <= 0)
{
throw new ArgumentException("Debit and Credit Account IDs must be greater than zero", nameof(debitAccountId));
}
WithUnitOfWork(uow =>
{
// check if the journal entry is locked
bool? isLocked = uow.AccountJournalRepository.IsJournalLocked(accountJournalId);
if (isLocked == null)
{
throw new Exception("Journal entry does not exist for AccountJournalID=" + accountJournalId);
}
else if (isLocked.Value)
{
throw new Exception("Cannot replace posts for locked journal entry AccountJournalID=" + accountJournalId);
}
// retrive journal entry date
DateTime entryDate = uow.AccountJournalRepository.ReadJournalEntryDate(accountJournalId);
// delete the original posts
var rowsDeleted = uow.AccountJournalRepository.DeleteJournalPostAll(accountJournalId);
if (rowsDeleted == 0)
{
throw new Exception("No posts found for AccountJournalID=" + accountJournalId);
}
if (twoPostCheck && rowsDeleted > 2)
{
throw new Exception("More than two posts found for AccountJournalID=" + accountJournalId + ", cannot replace posts with two new posts.");
}
//insert new posts
var rowsInserted = uow.AccountJournalRepository.InsertJournalPost(accountJournalId, debitAccountId, amountGbp);
rowsInserted += uow.AccountJournalRepository.InsertJournalPost(accountJournalId, creditAccountId, amountGbp * -1);
if (rowsInserted != 2)
{
throw new Exception("Failed to insert two posts for AccountJournalID=" + accountJournalId);
}
// update modified date on journal
if (uow.AccountJournalRepository.UpdateJournalEntryModifiedDate(accountJournalId) == false)
{
throw new Exception("Failed to update LastModified date for AccountJournalID=" + accountJournalId);
}
CommitIfOwned(uow);
});
return true;
}
/// <summary>
/// Old code needs sorting
/// </summary>
public bool DeleteJournal(int accountJournalId)
{
WithUnitOfWork(uow =>
{
if (accountJournalId <= 0)
{
throw new ArgumentException("Account journal ID must be greater than zero", nameof(accountJournalId));
}
// check if the journal entry is locked
bool? isLocked = uow.AccountJournalRepository.IsJournalLocked(accountJournalId);
if (isLocked == null)
{
throw new Exception("Journal entry does not exist for AccountJournalID=" + accountJournalId);
}
else if (isLocked.Value)
{
throw new Exception("Cannot replace posts for locked journal entry AccountJournalID=" + accountJournalId);
}
// delete posts first
uow.AccountJournalRepository.DeleteJournalPostAll(accountJournalId);
// then delete header record
uow.AccountJournalRepository.DeleteJournalHeader(accountJournalId);
CommitIfOwned(uow);
});
return true;
}
}
}
@@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtrade.Core.Logic.Account
{
public class Journal : UnitOfWorkBase
{
public int AccountJournalInsert(int journalTypeId, DateTime entryDate, string currencyCode,
decimal amount, int debitAccountId = 0, int creditAccountId = 0, bool lockEntry = false)
{
return new Data.Database.Account.CreateJournal().AccountJournalInsert(journalTypeId, entryDate, currencyCode,
amount, debitAccountId, creditAccountId, lockEntry);
}
public bool AccountJournalDelete(int accountJournalId)
{
return WithUnitOfWork(uow =>
{
bool result = uow.JournalRepository.DeleteJournal(accountJournalId);
CommitIfOwned(uow);
return result;
});
}
}
}
@@ -1,11 +1,16 @@
using System; using bnhtrade.Core.Data.Database.UnitOfWork;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Transactions; using System.Transactions;
namespace bnhtrade.Core.Logic.Sku namespace bnhtrade.Core.Logic.Inventory
{ {
public class SkuService : UnitOfWorkBase public class SkuService : UnitOfWorkBase
{ {
public SkuService() : base() { }
internal SkuService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
/// <summary> /// <summary>
/// Used for retriving an SKU ID by parameters. If no match, can create a new SKU if required. /// Used for retriving an SKU ID by parameters. If no match, can create a new SKU if required.
/// </summary> /// </summary>
@@ -0,0 +1,570 @@
using Amazon.Runtime.Internal.Transform;
using bnhtrade.Core.Data.Database.UnitOfWork;
using bnhtrade.Core.Logic.Account;
using bnhtrade.Core.Model.Account;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Formats.Asn1.AsnWriter;
namespace bnhtrade.Core.Logic.Inventory
{
public class StockJournalService : UnitOfWorkBase
{
public StockJournalService() { }
internal StockJournalService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
public Dictionary<int, Model.Stock.StockJournal> ReadStockJournal(List<int> stockJournalIds)
{
return WithUnitOfWork(uow =>
{
return ReadStockJournal(uow, false, stockJournalIds);
});
}
internal Dictionary<int, Model.Stock.StockJournal> ReadStockJournal(IUnitOfWork uow, bool lockJournalEntries, List<int> stockJournalIds)
{
if (stockJournalIds == null || stockJournalIds.Count == 0)
{
throw new ArgumentException("Stock journal IDs cannot be null or empty.", nameof(stockJournalIds));
}
// get the stock journal entries from the repository
var builderList = uow.StockJournalRepository.ReadStockJournal(lockJournalEntries, stockJournalIds);
// get the stock status for the journal posts
var statusIds = new List<int>();
foreach (var journal in builderList.Values)
{
foreach (var post in journal.StockJournalBuilderPosts)
{
if (!statusIds.Contains(post.StatusId))
{
statusIds.Add(post.StatusId);
}
}
}
var statusDict = new StockStatusService(uow).GetStatus(statusIds);
// get the stock journal types for the journal entries
var journalTypeIds = new List<int>();
foreach (var journal in builderList.Values)
{
if (!journalTypeIds.Contains(journal.StockJournalTypeId))
{
journalTypeIds.Add(journal.StockJournalTypeId);
}
}
var journalTypeDict = ReadStockJournalType(uow, journalTypeIds);
// build the stock journal objects and return them in a dictionary
var returnDict = new Dictionary<int, Model.Stock.StockJournal>();
foreach (var journal in builderList.Values)
{
returnDict.Add(journal.StockJournalId, journal.Build(journalTypeDict, statusDict));
}
return returnDict;
}
public Dictionary<int, Model.Stock.StockJournalType> ReadStockJournalType(List<int> stockJournalTypeIds = null)
{
return WithUnitOfWork(uow =>
{
return ReadStockJournalType(uow, stockJournalTypeIds);
});
}
private Dictionary<int, Model.Stock.StockJournalType> ReadStockJournalType(IUnitOfWork uow, List<int> stockJournalTypeIds = null)
{
return WithUnitOfWork(uow =>
{
// get the stock journal types from the repository
var tupleList = uow.StockJournalRepository.ReadStockJournalType(stockJournalTypeIds);
// get the accounts for the stock journal types
var statusIds = new List<int>();
foreach (var type in tupleList.Values)
{
if (type.stockStatusIdDebit.HasValue)
{
statusIds.Add(type.stockStatusIdDebit.Value);
}
if (type.stockStatusIdCredit.HasValue)
{
statusIds.Add(type.stockStatusIdCredit.Value);
}
}
statusIds = statusIds.Distinct().ToList();
var statusDict = new StockStatusService(uow).GetStatus(statusIds);
// build the stock journal type objects and return them in a dictionary
var returnDict = new Dictionary<int, Model.Stock.StockJournalType>();
foreach (var type in tupleList.Values)
{
var debitStatus = type.stockStatusIdDebit.HasValue ? statusDict[type.stockStatusIdDebit.Value] : null;
var creditStatus = type.stockStatusIdCredit.HasValue ? statusDict[type.stockStatusIdCredit.Value] : null;
var stockJournalType = new Model.Stock.StockJournalType(
type.id,
type.title,
debitStatus,
creditStatus
);
returnDict.Add(stockJournalType.StockJournalTypeID, stockJournalType);
}
return returnDict;
});
}
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(uow, 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 statusDict = new StockStatusService(uow).GetStatus(dicStatusQty.Keys.ToList());
var dicStatusIsCreditOnly = new Dictionary<int, bool>();
foreach (var status in statusDict.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 bool StockJournalDelete(int stockJournalId)
{
return WithUnitOfWork(uow =>
{
var result = StockJournalDelete(uow, stockJournalId);
if (result)
{
CommitIfOwned(uow);
return true;
}
else
{
return false;
}
});
}
/// <summary>
/// To be used internally (private). Deletes a stock journal entry and performs a consistency check, does not roll back transaction on fail.
/// </summary>
/// <returns>consistantcy check result, true=good, false=bad</returns>
private bool StockJournalDelete(IUnitOfWork uow, int stockJournalId)
{
// get journal entry
var stockJournalDict = uow.StockJournalRepository.ReadStockJournal(true, new List<int> { stockJournalId });
if (stockJournalDict.Count == 0)
{
throw new Exception("StockJournalID=" + stockJournalId + " does not exist!");
}
var stockJournal = stockJournalDict[stockJournalId];
// build list of debits to check
var debitStatusIds = new List<int>();
foreach (var post in stockJournal.StockJournalBuilderPosts)
{
if (post.IsDebit)
{
debitStatusIds.Add(post.StatusId);
}
}
// get all journal entries for stockId and debit status combination where entryDate >= stockJournal.EntryDate
var journalList = uow.StockJournalRepository.ReadStockJournal(
true, null, new List<int> { stockJournal.StockId }, null, stockJournal.EntryDate, null, debitStatusIds
);
// check no credits for stockId & debit status combination have been made since delete entry
bool consistancyCheckRequired = false;
foreach (var item in journalList.Values)
{
foreach (var post in item.StockJournalBuilderPosts)
{
if (post.Quantity < 0 && debitStatusIds.Contains(post.StatusId))
{
consistancyCheckRequired = true;
}
}
}
// make the delete
uow.StockJournalRepository.StockJournalDelete(stockJournalId);
// consistanct check
bool consistencyResult = true;
if (consistancyCheckRequired)
{
// run check
consistencyResult = WIP_StockJournalConsistencyCheck(uow, stockJournal.StockId, debitStatusIds);
}
if (consistencyResult)
{
return true;
}
else
{
return false;
}
}
// can be used before commiting an sql insert, update or delete to the stock journal to ensure a status does not fall below 0
// (unless the status is enabled to do so)
// set empty list or statusIdEffected to null to check entier stock entries for consistency
internal bool WIP_StockJournalConsistencyCheck(int stockId, List<int> statusIdsEffected = null)
{
if (statusIdsEffected == null)
{
statusIdsEffected = new List<int>();
}
// if no list supplied, build list of all used status' for stockId
if (statusIdsEffected.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())
{
statusIdsEffected.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 < statusIdsEffected.Count; i++)
{
if (i == 0)
{
sqlString = sqlString + " WHERE tblStockStatus.StockStatusID=" + statusIdsEffected[i];
}
else
{
sqlString = sqlString + " OR tblStockStatus.StockStatusID=" + statusIdsEffected[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 statusIdsEffected)
{
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 statusIdsEffected)
{
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;
}
}
}
@@ -0,0 +1,222 @@
using bnhtrade.Core.Data.Database.UnitOfWork;
using bnhtrade.Core.Logic.Account;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
using static System.Formats.Asn1.AsnWriter;
namespace bnhtrade.Core.Logic.Inventory
{
public class StockService : UnitOfWorkBase
{
public StockService() : base() { }
internal StockService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
public int WIP_StockInsertPurchase(int productId, int conditionId, int accountTaxCodeId, int accountJournalId, int quantity, int statusDebitId)
{
DateTime stockJournalEntryDate;
int stockJournalTypeId = 1;
return WithUnitOfWork(uow =>
{
stockJournalEntryDate = new AccountJournalService(uow).ReadJournalEntryDate(accountJournalId);
int result = WIP_StockInsertSub(
uow, productId, conditionId, accountTaxCodeId, accountJournalId, stockJournalTypeId, stockJournalEntryDate, quantity, statusDebitId);
CommitIfOwned(uow);
return result;
});
}
public int WIP_StockInsertOwnerIntroduced(decimal amount, int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId)
{
int accountJournalType = 3;
int stockJournalType = 2;
string currencyCode = "GBP";
return WithUnitOfWork(uow =>
{
int stockId = WIP_StockInsert(uow, accountJournalType, stockJournalType, currencyCode, amount, quantity, productId, conditionId, accountTaxCodeId, entryDate, debitStatusId);
CommitIfOwned(uow);
return stockId;
});
}
private int WIP_StockInsert(IUnitOfWork uow, int accountJournalType, int stockJournalType, string currencyCode, decimal amount,
int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId)
{
// add account journal entry
int accountJournalId = new Logic.Account.AccountJournalService(uow).JournalInsert(accountJournalType, entryDate, currencyCode, amount);
// make the stock insert
int stockId = WIP_StockInsertSub(uow, productId, conditionId, accountTaxCodeId,
accountJournalId, stockJournalType, entryDate, quantity, debitStatusId);
return stockId;
}
private int WIP_StockInsertSub(IUnitOfWork uow, int productId, int conditionId, int accountTaxCodeId,
int accountJournalId, int stockJournalTypeId, DateTime stockJournalEntryDate, int quantity, int statusDebitId)
{
stockJournalEntryDate = DateTime.SpecifyKind(stockJournalEntryDate, DateTimeKind.Utc);
// ensure account journal id hasn't already been added to stock table
int count = uow.StockRepository.CountStockTableRecords(new List<int> { accountJournalId });
if (count == 1)
{
throw new Exception("Add account journal entry already assigned to stock line.");
}
else if (count > 1)
{
throw new Exception("Houston we have a problem! An account journal entry is assigned to " + count + " stock lines.");
}
// ensure the debit for the account journal transaction is to an 'Asset' account type
bool isIt = uow.AccountJournalRepository.IsJournalDebitAssetType(accountJournalId);
if (!isIt)
{
throw new Exception("Supplied AccountJournal entry must debit an 'Asset' account type.");
}
// get statusCreditId for stock journal type
int? statusCreditId = uow.StockJournalRepository.ReadTypeIdStatusCreditId(stockJournalTypeId);
if (statusCreditId == null)
{
throw new Exception("Default credit status not set for StockJournalTypeID=" + stockJournalTypeId);
}
// get/set an skuId
int skuId = new Logic.Inventory.SkuService(uow).GetSkuId(productId, conditionId, accountTaxCodeId, true);
// add the entry to the stock table (minus stockJournalId)
int stockId = uow.StockRepository.InsertNewStock(skuId, accountJournalId);
// insert stock journal entry
var journalPosts = new List<(int statusId, int quantity)>();
journalPosts.Add((statusDebitId, quantity));
journalPosts.Add((statusCreditId.Value, (quantity * -1)));
int stockJournalId = new StockJournalService(uow).StockJournalInsert(stockJournalTypeId, stockId, journalPosts, stockJournalEntryDate, true);
// update the stock table
count = uow.StockRepository.UpdateStockJournalId(stockId, stockJournalId);
if (count < 1)
{
throw new Exception("New stock insert cancelled, failed to update StockJournalID");
}
return stockId;
}
public void WIP_StockDeletePurchase(int stockId)
{
WithUnitOfWork(uow =>
{
if (stockId <= 0)
{
throw new ArgumentException("Stock ID must be greater than zero", nameof(stockId));
}
WIP_StockDelete(uow, stockId);
CommitIfOwned(uow);
});
}
public void WIP_StockDeleteOwnerIntroduced(int stockId)
{
WithUnitOfWork(uow =>
{
if (stockId <= 0)
{
throw new ArgumentException("Stock ID must be greater than zero", nameof(stockId));
}
WIP_StockDelete(uow, stockId);
CommitIfOwned(uow);
});
}
private void WIP_StockDelete(IUnitOfWork uow, int stockId)
{
int stockJournalType = 0;
// ensure stockId is owner-introduced
stockJournalType = uow.StockJournalRepository.ReadJournalTypeIdByStockId(stockId);
// check stock journal type is not restricted
// owner inventory introduced
if (stockJournalType == 2)
{
// no dramas
}
else
{
throw new Exception("Manual delete of this stock type is not supported, use the method that created it!");
}
// check there is only one stock journal entry for stock item (i.e. the source entry)
int count = uow.StockJournalRepository.ReadJournalEntryCountByStockId(stockId);
if (count > 1)
{
throw new Exception("Delete " + count + " stock journal entries (other than source entry), before peforming this operation.");
}
// remove account journal entry
WIP_StockDeleteSubAccountJournalEntry(uow, stockId);
// remove stock
WIP_StockDeleteSub(uow, stockId);
}
private void WIP_StockDeleteSub(IUnitOfWork uow, int stockId)
{
// check for accountJournalId on stock table
int? accountJournalId = uow.StockRepository.ReadAccountJournalId(stockId);
if (accountJournalId.HasValue)
{
throw new Exception("StockID=" + stockId + " remove account journal entry using method that created it first.");
}
// get stockJournalId
int stockJournalId = uow.StockRepository.ReadStockJournalId(stockId) ?? throw new Exception("StockJournalID not found for StockID=" + stockId);
// remove stockJournalId from stock table
int count = uow.StockRepository.UpdateStockJournalId(stockId, null);
if (count != 2) // we need to count the LastUpdated trigger!
{
throw new Exception("Failed to remove StockJournalID from stock table StockID=" + stockId);
}
// delete stock journal entry
new StockJournalService(uow).StockJournalDelete(stockJournalId);
// delete stock table entry
count = uow.StockRepository.DeleteStock(stockId);
if (count != 1)
{
throw new Exception("StockID = " + stockId + " delete failed");
}
}
// to be used by other methods within a transaction scope
private void WIP_StockDeleteSubAccountJournalEntry(IUnitOfWork uow, int stockId)
{
// get the account journal id
int accountJournalId = uow.StockRepository.ReadAccountJournalId(stockId) ?? throw new Exception("AccountJournalID not found for StockID=" + stockId);
// remove entry from stock table
int count = uow.StockRepository.UpdateAccountJournalId(stockId, null);
if (count != 2) // must include last modified trigger
{
throw new Exception("Failed to set AccountJournalID to NULL on stock table for StockID=" + stockId);
}
// delete account journal entry
new AccountJournalService(uow).DeleteJournal(accountJournalId);
}
}
}
@@ -0,0 +1,91 @@
using bnhtrade.Core.Data.Database.UnitOfWork;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Logic.Inventory
{
public class StockStatusService : UnitOfWorkBase
{
public StockStatusService() : base() { }
internal StockStatusService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
public Dictionary<int, Model.Stock.Status> GetStatus(List<int> statusIds = null, List<int> statusTypeIds = null)
{
return WithUnitOfWork(uow =>
{
var builderList = uow.StockStatusRepository.ReadStatus(statusIds, statusTypeIds).Values.ToList();
var satusTypeIdList = new List<int>();
foreach (var status in builderList)
{
satusTypeIdList.Add(status.StatusTypeId);
}
satusTypeIdList = satusTypeIdList.Distinct().ToList();
var statusTypeDict = GetStatusType(uow, satusTypeIdList);
var retrunDict = new Dictionary<int, Model.Stock.Status>();
foreach (var status in builderList)
{
retrunDict.Add(status.StatusId, status.Build(statusTypeDict));
}
return retrunDict;
});
}
private Dictionary<int, Model.Stock.StatusType> GetStatusType(IUnitOfWork uow, List<int> statusTypeIds = null)
{
// get the status types from the repository
var typeBuilderList = uow.StockStatusRepository.ReadStatusType(statusTypeIds);
// get the accounts for the status types
var accountIds = new List<int>();
foreach (var type in typeBuilderList.Values)
{
accountIds.Add(type.AccountId);
}
accountIds = accountIds.Distinct().ToList();
var accountDict = uow.AccountCodeRepository.ReadAccountCode(accountIds);
// build the status type objects and return them in a dictionary
var returnDict = new Dictionary<int, Model.Stock.StatusType>();
foreach (var type in typeBuilderList.Values)
{
returnDict.Add(type.StockStatusTypeID, type.Build(accountDict));
}
return returnDict;
}
public Dictionary<int, Model.Stock.StatusType> GetStatusType(List<int> statusTypeIds = null)
{
return WithUnitOfWork(uow =>
{
return GetStatusType(uow, 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
/// this method may give an available quantity, but transfers before that date wouod not be possible).
/// </summary>
/// <param name="sku">SKU number</param>
/// <param name="statusId">Status ID</param>
/// <returns>Balance as quantity</returns>
private int GetAvailableBalanceBySku(string sku, int statusId)
{
return WithUnitOfWork(uow =>
{
if (string.IsNullOrWhiteSpace(sku))
{
throw new ArgumentException("SKU number is null, empty, or whitespace", nameof(sku));
}
return uow.StockJournalRepository.ReadStatusBalanceBySku(sku, statusId);
});
}
}
}
@@ -0,0 +1,89 @@
using bnhtrade.Core.Data.Database.Repository.Implementation;
using bnhtrade.Core.Data.Database.UnitOfWork;
using bnhtrade.Core.Logic.Account;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Transactions;
namespace bnhtrade.Core.Logic.Purchase
{
public class PurchaseService : UnitOfWorkBase
{
private static int accountJournalTypeIdNet = 1;
private static int accountJournalTypeIdTax = 2;
private static int stockJournalTypeId = 1;
private static int creditAccountId = 59;
private static int creditStatusId = 13;
private static int defaultAccountId = 138;
public PurchaseService() : base() { }
internal PurchaseService(IUnitOfWork unitOfWork) : base(unitOfWork) { }
public void WIP_PurchaseLineTransactionNetInsert(int purchaseLineId, string currencyCode, decimal amountNet, DateTime entryDate, int? debitAccountId = null)
{
// default to 'Inventory, Receivable/Processing'
if (debitAccountId == null)
{
debitAccountId = defaultAccountId;
}
WithUnitOfWork(uow =>
{
int journalId = new AccountJournalService(uow).JournalInsert(accountJournalTypeIdNet, entryDate, currencyCode, amountNet, debitAccountId.Value);
uow.PurchaseRepository.InsertPurchaseLineTransaction(journalId, purchaseLineId);
CommitIfOwned(uow);
});
}
public void WIP_PurchaseLineTransactionNetUpdate(int accountJouranlId, string currencyCode, decimal amountNet, int debitAccountId)
{
WithUnitOfWork(uow =>
{
// stock accountId check
if (debitAccountId == 86)
{
int? stockId = uow.StockRepository.ReadStockIdByAccountJournalId(accountJouranlId);
if (stockId.HasValue == false)
{
throw new Exception("Add account journal entry to stock before attempting this operation.");
}
}
// make the update
new AccountJournalService(uow).AccountJournalPostReplace(accountJouranlId, currencyCode, amountNet, debitAccountId, creditAccountId);
CommitIfOwned(uow);
});
}
public void WIP_PurchaseLineTransactionDelete(int purchaseLineId, int accountJournalId)
{
WithUnitOfWork(uow =>
{
// check accountJournalId does not exist in stock table
int? stockId = uow.StockRepository.ReadStockIdByAccountJournalId(accountJournalId);
if (stockId.HasValue)
{
throw new Exception("Delete stock first before proceeding, AccountJournalID=" + accountJournalId);
}
// delete line in purchase line transaction table
int deleted = uow.PurchaseRepository.DeletePurchaseLineTransaction(accountJournalId);
if (deleted != 1)
{
throw new Exception("Operation cancelled, failed to delete entry in tblPurchaseLineTransaction WHERE AccountJournalID=" + accountJournalId);
}
// delete account journal entry
new AccountJournalService(uow).DeleteJournal(accountJournalId);
CommitIfOwned(uow);
});
}
}
}
+3 -16
View File
@@ -12,22 +12,9 @@ namespace bnhtrade.Core.Logic.Stock
{ {
} }
/// <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
/// this method may give an available quantity, but transfers before that date wouod not be possible).
/// </summary>
/// <param name="sku">SKU number</param>
/// <param name="statusId">Status ID</param>
/// <returns>Balance as quantity</returns>
private int GetAvailableBalanceBySku(string sku, int statusId)
{
return new Data.Database.Stock.ReadStatusBalance().BySku(sku, statusId);
}
/// <summary> /// <summary>
/// Return the avaliable balance of a status, includes datetime that each stockNumber became avaiable. /// Return the avaliable balance of a status, includes datetime that each stockNumber became avaiable.
/// Useful for checking availability before moving stock/sku retrospectivly /// Useful for checking availability before moving stock/sku retrospectivly
/// </summary> /// </summary>
/// <param name="skuNumber">SKU number</param> /// <param name="skuNumber">SKU number</param>
/// <param name="statusId">Status ID</param> /// <param name="statusId">Status ID</param>
@@ -113,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.StockRepository.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);
} }
@@ -131,7 +118,7 @@ namespace bnhtrade.Core.Logic.Stock
/// <returns>Dictionary of SKUs and the repective balance of each</returns> /// <returns>Dictionary of SKUs and the repective balance of each</returns>
public Dictionary<string, int> GetSkuQuantity(int statusId) public Dictionary<string, int> GetSkuQuantity(int statusId)
{ {
return new Data.Database.Stock.ReadStatusBalance().ByStatusId(statusId); return new Data.Database.Stock.ReadStatusBalance().ReadStatusBalanceByStatusId(statusId);
} }
} }
} }
@@ -45,6 +45,20 @@ namespace bnhtrade.Core.Logic.Validate
return true; return true;
} }
/// <summary>
/// Validates that a stock number string is in the format 'STK#000000' (six digits).
/// </summary>
/// <param name="stockNumber"></param>
/// <returns></returns>
public static bool StockNumber(string stockNumber)
{
if (string.IsNullOrEmpty(stockNumber))
return false;
// Check for exact match: STK# followed by 6 digits
return System.Text.RegularExpressions.Regex.IsMatch(stockNumber, @"^STK#\d{6}$");
}
/// <summary> /// <summary>
/// Checks the datetime is not default and is utc /// Checks the datetime is not default and is utc
/// </summary> /// </summary>
+8 -8
View File
@@ -11,7 +11,7 @@ namespace bnhtrade.Core.Model.Account
{ {
public Account(int id, int accountCode, string accountName, string description, string type, string basicType, int multiplier) public Account(int id, int accountCode, string accountName, string description, string type, string basicType, int multiplier)
{ {
Id = id; AccountId = id;
AccountCode = accountCode; AccountCode = accountCode;
AccountName = accountName; AccountName = accountName;
Description = description; Description = description;
@@ -23,18 +23,18 @@ namespace bnhtrade.Core.Model.Account
/// <summary> /// <summary>
/// Database record id /// Database record id
/// </summary> /// </summary>
public int Id { get; private set; } public int AccountId { get; }
public int AccountCode { get; private set; } public int AccountCode { get; }
public string AccountName { get; private set; } public string AccountName { get; }
public string Description { get; private set; } public string Description { get; }
public string Type { get; private set; } public string Type { get; }
public string BasicType { get; private set; } public string BasicType { get; }
public int Multiplier { get; private set; } public int Multiplier { get; }
} }
} }
@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Model.Account
{
public class JournalBuilder
{
// Setters for each property
public int JournalId { get; set; }
public int JournalTypeId { get; set; }
public List<JournalPostBuilder> JournalBuilderPosts { get; set; }
public DateTime EntryDate { get; set; }
public DateTime PostDate { get; set; }
public DateTime LastModified { get; set; }
public bool IsLocked { get; set; }
public class JournalPostBuilder
{
public int AccountId { get; set; }
public int PostId { get; set; }
public decimal AmountGbp { get; set; }
}
public Journal Build(Dictionary<int, Model.Account.JournalType> journalTypeDict, Dictionary<int, Model.Account.Account> accountDict)
{
if (journalTypeDict == null || !journalTypeDict.ContainsKey(JournalTypeId))
{
throw new ArgumentException($"Journal type with ID {JournalTypeId} does not exist.");
}
if (JournalBuilderPosts == null || JournalBuilderPosts.Count == 0)
{
throw new ArgumentException("Journal posts cannot be null or empty.");
}
if (accountDict == null || accountDict.Any() == false)
{
throw new ArgumentException("Account dictionary cannot be null or empty");
}
// build lines
var journalPosts = new List<Journal.Post>();
foreach (var post in JournalBuilderPosts)
{
if (!accountDict.ContainsKey(post.AccountId))
{
throw new ArgumentException($"Account with ID {post.AccountId} does not exist.");
}
journalPosts.Add(new Journal.Post(post.PostId, accountDict[post.AccountId], post.AmountGbp));
}
return new Journal(
JournalId
, journalTypeDict[JournalTypeId]
, journalPosts
, EntryDate
, PostDate
, LastModified
, IsLocked);
}
}
}
+4 -10
View File
@@ -16,24 +16,18 @@ namespace bnhtrade.Core.Model.Account
DefaultDebitAccount = defaultDebitAccount; DefaultDebitAccount = defaultDebitAccount;
} }
internal void AddDefaultAccounts(Model.Account.Account defaultCreditAccount = null, Model.Account.Account defaultDebitAccount = null) public int JournalTypeId { get; }
{
DefaultCreditAccount = defaultCreditAccount;
DefaultDebitAccount = defaultDebitAccount;
}
public int JournalTypeId { get ; private set; } public string Title { get; }
public string Title { get; private set; } public Model.Account.Account DefaultDebitAccount { get; }
public Model.Account.Account DefaultDebitAccount { get; private set; }
public bool IsSetDefaultDebitAccount public bool IsSetDefaultDebitAccount
{ {
get { return DefaultDebitAccount != null; } get { return DefaultDebitAccount != null; }
} }
public Model.Account.Account DefaultCreditAccount { get; private set; } public Model.Account.Account DefaultCreditAccount { get; }
public bool IsSetDefaultCreditAccount public bool IsSetDefaultCreditAccount
{ {
@@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Model.Stock
{
public class JournalEntry : IValidatableObject
{
public string TypeTitle { get; set; }
public int StockNumber { get; set; }
public DateTime EntryDate { get; set; }
public DateTime PostDate { get; set; }
public DateTime LastModified { get; set; }
public bool IsLocked { get; set; }
public List<JournalEntryPost> JournalPosts { get; set; } = new List<JournalEntryPost>();
public class JournalEntryPost
{
public int JournalPostId { get; set; }
public int StockStatusId { get; set; }
[Required()]
public string StockStatus { get; set; }
[Required()]
public int Quantity { get; set; }
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
throw new NotImplementedException();
}
}
}
+19 -9
View File
@@ -21,22 +21,32 @@ namespace bnhtrade.Core.Model.Stock
RecordCreated = recordCreated; RecordCreated = recordCreated;
} }
public int StatusId { get; private set; } public int StatusId { get; }
public int? StatusCode { get; private set; } public int? StatusCode { get; }
public string StatusTitle { get; private set; } public bool StatusCodeIsSet
{
get { return StatusCode.HasValue; }
}
public Model.Stock.StatusType StatusType { get; private set; } public string StatusTitle { get; }
public string Reference { get; private set; } public Model.Stock.StatusType StatusType { get; }
public int? ForeignKeyID { get; private set; } public string Reference { get; }
public bool IsCreditOnly { get; private set; } public int? ForeignKeyID { get; }
public bool IsClosed { get; private set; } public bool ForeignKeyIDIsSet
{
get { return ForeignKeyID.HasValue; }
}
public DateTime RecordCreated { get; private set; } public bool IsCreditOnly { get; }
public bool IsClosed { get; }
public DateTime RecordCreated { get; }
} }
} }
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Model.Stock
{
internal class StatusBuilder
{
public int StatusId { get; set; }
public int? StatusCode { get; set; }
public string StatusTitle { get; set; }
public int StatusTypeId { get; set; }
public string Reference { get; set; }
public int? ForeignKeyID { get; set; }
public bool IsCreditOnly { get; set; }
public bool IsClosed { get; set; }
public DateTime RecordCreated { get; set; }
public Status Build(Dictionary<int, StatusType> statusTypeDict)
{
if (!statusTypeDict.ContainsKey(StatusTypeId))
{
throw new InvalidOperationException($"Status type with ID {StatusTypeId} does not exist.");
}
return new Status(
StatusId,
StatusCode,
StatusTitle,
statusTypeDict[StatusTypeId],
Reference,
ForeignKeyID,
IsCreditOnly,
IsClosed,
RecordCreated
);
}
}
}
+5 -5
View File
@@ -17,10 +17,10 @@ namespace bnhtrade.Core.Model.Stock
ReferenceType = referenceType; ReferenceType = referenceType;
Account = account; Account = account;
} }
public int StockStatusTypeID { get; private set; } public int StockStatusTypeID { get; }
public string StatusTypeName { get; private set; } public string StatusTypeName { get; }
public string ForeignKeyType { get; private set; } public string ForeignKeyType { get; }
public string ReferenceType { get; private set; } public string ReferenceType { get; }
public Model.Account.Account Account { get; private set; } public Model.Account.Account Account { get; }
} }
} }
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Model.Stock
{
internal class StatusTypeBuilder
{
public int StockStatusTypeID { get; set; }
public string StatusTypeName { get; set; }
public string ForeignKeyType { get; set; }
public string ReferenceType { get; set; }
public int AccountId { get; set; }
public StatusType Build(Dictionary<int, Model.Account.Account> accountDict)
{
if (!accountDict.ContainsKey(AccountId))
{
throw new InvalidOperationException($"Account with ID {AccountId} does not exist.");
}
return new StatusType(
StockStatusTypeID,
StatusTypeName,
ForeignKeyType,
ReferenceType,
accountDict[AccountId]
);
}
}
}
@@ -0,0 +1,153 @@
using bnhtrade.Core.Logic.Validate;
using bnhtrade.Core.Model.Account;
using FikaAmazonAPI.AmazonSpApiSDK.Models.FulfillmentInboundv20240320;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Model.Stock
{
public class StockJournal : IValidatableObject
{
public StockJournal(int stockJournalId, StockJournalType stockJournalType, int stockId, string stockNumber, DateTime entryDate, DateTime postDate, DateTime lastModified, bool isLocked, List<StockJournalPost> journalPosts)
{
StockJournalId = stockJournalId;
StockJournalType = stockJournalType;
StockId = stockId;
StockNumber = stockNumber;
EntryDate = entryDate;
PostDate = postDate;
LastModified = lastModified;
IsLocked = isLocked;
StockJournalPosts = journalPosts ?? throw new ArgumentNullException(nameof(journalPosts), "Journal posts cannot be null.");
}
[Required()]
[Range(1, int.MaxValue, ErrorMessage = "StockJournalId must be greater than 0.")]
public int StockJournalId { get; }
public StockJournalType StockJournalType { get; }
[Required()]
[Range(1, int.MaxValue, ErrorMessage = "StockId must be greater than 0.")]
public int StockId { get; }
[Required(ErrorMessage = "Stock number is required.")]
public string StockNumber { get; }
public DateTime EntryDate { get; }
public DateTime PostDate { get; }
public DateTime LastModified { get; }
public bool IsLocked { get; }
public List<StockJournalPost> StockJournalPosts { get; }
public class StockJournalPost : IValidatableObject
{
public StockJournalPost(int journalPostId, Status status, int quantity)
{
StockJournalPostId = journalPostId;
Status = status;
Quantity = quantity;
}
public int StockJournalPostId { get; }
[Required()]
public Status Status { get; }
[Required()]
public int Quantity { get; }
public bool IsCredit
{
get
{
if (Quantity < 0)
{
return true;
}
else if (Quantity > 0)
{
return false;
}
else
{
throw new InvalidOperationException("Quantity cannot be zero for a journal post.");
}
}
}
public bool IsDebit
{
get
{
return !IsCredit;
}
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var validationResults = new List<ValidationResult>();
if (Quantity == 0)
{
validationResults.Add(new ValidationResult("Quantity cannot be zero for a journal post.", new[] { nameof(Quantity) }));
}
return validationResults;
}
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var validationResults = new List<ValidationResult>();
// dates are in UTC format
if (EntryDate.Kind != DateTimeKind.Utc || EntryDate == default(DateTime))
{
validationResults.Add(new ValidationResult("Entry Date must be in UTC format and not a default value.", new[] { nameof(EntryDate) }));
}
if (PostDate.Kind != DateTimeKind.Utc || PostDate == default(DateTime))
{
validationResults.Add(new ValidationResult("PostDate must be in UTC format and not a default value.", new[] { nameof(PostDate) }));
}
if (LastModified.Kind != DateTimeKind.Utc || LastModified == default(DateTime))
{
validationResults.Add(new ValidationResult("LastModified must be in UTC format and not a default value.", new[] { nameof(LastModified) }));
}
if (Format.StockNumber(StockNumber) == false)
{
validationResults.Add(new ValidationResult("StockNumber must be in the format 'STK#000000'.", new[] { nameof(StockNumber) }));
}
// Validate each StockJournalPost using its own context and Validate method
int sum = 0;
for (int i = 0; i < StockJournalPosts.Count; i++)
{
sum += StockJournalPosts[i].Quantity;
var post = StockJournalPosts[i];
var postContext = new ValidationContext(post, validationContext, validationContext.Items);
foreach (var result in post.Validate(postContext))
{
// Prefix property name with the index for clarity
var memberNames = result.MemberNames.Select(name => $"StockJournalPosts[{i}].{name}");
validationResults.Add(new ValidationResult(result.ErrorMessage, memberNames));
}
}
if (sum != 0)
{
validationResults.Add(new ValidationResult("The sum of all journal posts must equal zero.", new[] { nameof(StockJournalPosts) }));
}
return validationResults;
}
}
}
@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static bnhtrade.Core.Data.Database.Constants;
using static bnhtrade.Core.Model.Stock.StockJournal;
namespace bnhtrade.Core.Model.Stock
{
internal class StockJournalBuilder
{
public int StockJournalId { get; set; }
public int StockJournalTypeId { get; set; }
public int StockId { get; set; }
public string StockNumber { get; set; }
public DateTime EntryDate { get; set; }
public DateTime PostDate { get; set; }
public DateTime LastModified { get; set; }
public bool IsLocked { get; set; }
public bool AccountIsProcessed { get; set; }
public List<StockJournalBuilderPost> StockJournalBuilderPosts { get; set; }
public class StockJournalBuilderPost
{
public int StockJournalPostId { get; set; }
public int StatusId { get; set; }
public int Quantity { get; set; }
public bool IsCredit
{
get
{
if (Quantity < 0)
{
return true; // Negative quantity indicates a credit post
}
else if (Quantity > 0)
{
return false; // Positive quantity indicates a debit post
}
else
{
throw new InvalidOperationException("Quantity cannot be zero for a stock journal post.");
}
}
}
public bool IsDebit
{
get
{
return !IsCredit; // Debit is the opposite of credit
}
}
}
public StockJournal Build(Dictionary<int, StockJournalType> stockJournalTypeDict, Dictionary<int, Status> stockStatusDict)
{
if (StockJournalBuilderPosts == null || !StockJournalBuilderPosts.Any())
{
throw new InvalidOperationException("At least one journal post is required.");
}
var posts = new List<StockJournalPost>();
foreach (var builderPost in StockJournalBuilderPosts)
{
var post = new StockJournalPost(
builderPost.StockJournalPostId,
stockStatusDict[builderPost.StockJournalPostId],
builderPost.Quantity
);
posts.Add(post);
}
return new StockJournal(
StockJournalId,
stockJournalTypeDict[StockJournalTypeId],
StockId,
StockNumber,
EntryDate,
PostDate,
LastModified,
IsLocked,
posts
);
}
}
}
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace bnhtrade.Core.Model.Stock
{
public class StockJournalType
{
public StockJournalType(int stockJournalTypeId, string stockJournalTypeTitle, Status defaultDebitStatus, Status defaultCreditStatus)
{
StockJournalTypeID = stockJournalTypeId;
StockJournalTypeTitle = stockJournalTypeTitle ?? throw new ArgumentNullException(nameof(stockJournalTypeTitle), "Stock journal type title cannot be null.");
DefaultDebitStatus = defaultDebitStatus;
DefaultCreditStatus = defaultCreditStatus;
}
public int StockJournalTypeID { get; }
public string StockJournalTypeTitle { get; }
public Status DefaultDebitStatus { get; }
public bool DefaultDebitStatusIsSet
{
get { return DefaultDebitStatus != null; }
}
public Status DefaultCreditStatus { get; }
public bool DefaultCreditStatusIsSet
{
get { return DefaultCreditStatus != null; }
}
}
}
File diff suppressed because it is too large Load Diff