From d4170d2b8051c0ca198439923aee175d535eeb46 Mon Sep 17 00:00:00 2001 From: Bob Hodgetts Date: Wed, 9 Jul 2025 20:19:09 +0100 Subject: [PATCH] wip --- src/bnhtrade.ComTypeLib/Account/Account.cs | 4 +- .../AccountJournalRepository.cs | 506 ++------------ .../Implementation/PurchaseRepository.cs | 88 --- .../Implementation/StockJournalRepository.cs | 30 + .../Implementation/StockRepository.cs | 434 ++---------- .../Interface/IAccountJournalRepository.cs | 7 +- .../Interface/IPurchaseRepository.cs | 3 - .../Interface/IStockJournalRepository.cs | 3 + .../Repository/Interface/IStockRepository.cs | 8 +- .../Logic/Account/AccountJournalService.cs | 633 ++++++++++++++++++ .../Logic/Account/JournalService.cs | 44 -- .../Logic/Inventory/StockService.cs | 67 +- .../Logic/Purchase/PurchaseService.cs | 96 ++- 13 files changed, 904 insertions(+), 1019 deletions(-) create mode 100644 src/bnhtrade.Core/Logic/Account/AccountJournalService.cs delete mode 100644 src/bnhtrade.Core/Logic/Account/JournalService.cs diff --git a/src/bnhtrade.ComTypeLib/Account/Account.cs b/src/bnhtrade.ComTypeLib/Account/Account.cs index bba9393..f04a09e 100644 --- a/src/bnhtrade.ComTypeLib/Account/Account.cs +++ b/src/bnhtrade.ComTypeLib/Account/Account.cs @@ -35,13 +35,13 @@ namespace bnhtrade.ComTypeLib 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) { - return new Core.Logic.Account.JournalService().JournalInsert(journalTypeId, entryDate, + return new Core.Logic.Account.AccountJournalService().JournalInsert(journalTypeId, entryDate, currencyCode, amount, debitAccountId, creditAccountId, lockEntry); } public bool AccountJournalDelete(ConnectionCredential sqlConnCred, int accountJournalId) { - return new Core.Logic.Account.JournalService().JournalDelete(accountJournalId); + return new Core.Logic.Account.AccountJournalService().DeleteJournal(accountJournalId); } [return: MarshalAs(UnmanagedType.Currency)] diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/AccountJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/AccountJournalRepository.cs index 167d264..79eede7 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Implementation/AccountJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/AccountJournalRepository.cs @@ -20,396 +20,78 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation // // create // - 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; + public int AccountJournalInsert(int journalTypeId, DateTime entryDate, bool lockEntry = false) + { // ensure date is UTC - entryDate = DateTime.SpecifyKind(entryDate, DateTimeKind.Utc); + if (entryDate.Kind != DateTimeKind.Utc) + { + throw new Exception("DateTime kind must be utc"); + } // debit and credit locks are checked in journal post method - using (TransactionScope scope = new TransactionScope()) - using (SqlConnection conn = new SqlConnection(SqlConnectionString)) + // insert the journal entry + int journalId; + + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) { - conn.Open(); - - // insert the journal entry - int journalId; - - using (SqlCommand cmd = new SqlCommand(@" + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" 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); + (@journalTypeId, @entryDate, @lockEntry);"; - //execute - journalId = (int)cmd.ExecuteScalar(); - } + // add parameters + cmd.Parameters.AddWithValue("@journalTypeId", journalTypeId); + cmd.Parameters.AddWithValue("@entryDate", entryDate.ToUniversalTime()); + cmd.Parameters.AddWithValue("@lockEntry", lockEntry); - // 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; + //execute + journalId = (int)cmd.ExecuteScalar(); } - } - /// - /// Old code needs sorting - /// - 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; - } + return journalId; } // // read // - public Dictionary ReadJournal(List journalIdList) + + + public bool IsJournalDebitAssetType(int accountJournalId) { - 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()) + if (accountJournalId <= 0) { - sqlBuilder.In("tblAccountJournal.AccountJournalID", journalIdList, "AND"); + throw new ArgumentException("Invalid journal ID provided.", nameof(accountJournalId)); } - // 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; + 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;"; - if (sqlBuilder.ParameterListIsSet) + cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); + + if ((int)cmd.ExecuteScalar() < 1) { - sqlBuilder.AddParametersToSqlCommand(cmd); + return false; // no debit posts of type Asset found } - - 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(); - if (hasRows) - { - // build lists to filter db results by - var journalTypeIdList = new List(); - var accountIdList = new List(); - 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 AccountJournalRepository(_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(); - 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; - } - - 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); + return true; } } @@ -561,112 +243,10 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation // update // - public bool AccountJournalPostUpdate(int journalId, string currencyCode, decimal amount, int debitAccountId = 0, int creditAccountId = 0) - { - // retrive journal entry date - DateTime entryDate; - using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) - { - cmd.Transaction = _transaction as SqlTransaction; - cmd.CommandText = @" - SELECT - tblAccountJournal.EntryDate - FROM - tblAccountJournal - WHERE - (((tblAccountJournal.AccountJournalID)=@accountJournalId));"; - - cmd.Parameters.AddWithValue("@accountJournalId", journalId); - - entryDate = DateTime.SpecifyKind((DateTime)cmd.ExecuteScalar(), DateTimeKind.Utc); - } - - // delete the original posts - 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", journalId); - - cmd.ExecuteNonQuery(); - } - - //insert new posts - bool postResult = AccountJournalPostInsert(journalId, entryDate, currencyCode, amount, debitAccountId, creditAccountId); - - // update modified date on journal - 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", journalId); - cmd.Parameters.AddWithValue("@utcNow", DateTime.UtcNow); - - cmd.ExecuteNonQuery(); - } - - return true; - } - // // delete // - /// - /// Old code needs sorting - /// - 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; - } } } diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/PurchaseRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/PurchaseRepository.cs index 60c370e..6024857 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Implementation/PurchaseRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/PurchaseRepository.cs @@ -23,40 +23,6 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation { } - public void WIP_PurchaseLineTransactionNetInsert(int purchaseLineId, string currencyCode, decimal amountNet, DateTime entryDate, int debitAccountId = 0) - { - // default to 'Inventory, Receivable/Processing' - if (debitAccountId < 1) - { - debitAccountId = defaultAccountId; - } - - // create account journal entry - int journalId = new Data.Database.Repository.Implementation.AccountJournalRepository(_connection, _transaction). - AccountJournalInsert(accountJournalTypeIdNet, entryDate, currencyCode, amountNet, debitAccountId); - - // add transaction to purchase line transaction table - 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", journalId); - - 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) { @@ -87,10 +53,6 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation throw new Exception("Houston we have a problem! An account journal entry is assigned to " + count + " stock lines."); } } - - // make the update - bool result = new Data.Database.Repository.Implementation.AccountJournalRepository(_connection, _transaction). - AccountJournalPostUpdate(accountJouranlId, currencyCode, amountNet, debitAccountId, creditAccountId); } // delete after testing.... @@ -135,55 +97,5 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation // } //} - public void WIP_PurchaseLineTransactionDelete(int purchaseLineId, int accountJournalId) - { - // check accountJournalId does not exist in stock table - 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); - - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - { - if (reader.Read()) - { - throw new Exception("Integrity check failure! AccountJournalID=" + accountJournalId + " exists multiple time in stock table!"); - } - else - { - throw new Exception("Delete stock first before proceeding, AccountJournalID=" + accountJournalId); - } - } - } - } - - // delete line in purchase line transaction table - 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); - - int count = cmd.ExecuteNonQuery(); - - if (count != 1) - { - throw new Exception("Operation cancelled, failed to delete entry in tblPurchaseLineTransaction WHERE AccountJournalID=" + accountJournalId); - } - } - - // delete account journal entry - new AccountJournalRepository(_connection, _transaction).DeleteJournal(accountJournalId); - } } } diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs index 15d7c18..1d1d3a9 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockJournalRepository.cs @@ -296,6 +296,36 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation } } + 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; + } + } + + } + // // update // diff --git a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockRepository.cs index 7743bca..1198212 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Implementation/StockRepository.cs @@ -22,168 +22,64 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation // create // - public int WIP_StockInsertSub(int productId, int conditionId, int accountTaxCodeId, - int accountJournalId, int stockJournalTypeId, DateTime stockJournalEntryDate, int quantity, int statusDebitId) + public int InsertNewStock(int skuId, int accountJournalId) { - stockJournalEntryDate = DateTime.SpecifyKind(stockJournalEntryDate, DateTimeKind.Utc); - - // ensure account journal id hasn't already been added to stock table - int count = 0; - - using (SqlCommand cmd = new SqlCommand(@" - SELECT Count(tblStock.StockID) AS CountOfID - FROM tblStock - WHERE (((tblStock.AccountJournalID)=@accountJouranlId)); - ", conn)) + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) { - cmd.Parameters.AddWithValue("@accountJouranlId", accountJournalId); - - count = (int)cmd.ExecuteScalar(); - } - - 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 - using (SqlCommand cmd = new SqlCommand(@" - 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; - ", conn)) - { - cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); - - if ((int)cmd.ExecuteScalar() < 1) - { - throw new Exception("Supplied AccountJournal entry must debit an 'Asset' account type."); - } - } - - // get statusCreditId for stock journal type - int statusCreditId; - using (SqlCommand cmd = new SqlCommand(@" - SELECT - tblStockJournalType.StockStatusID_Credit - FROM - tblStockJournalType - WHERE - tblStockJournalType.StockJournalTypeID=@stockJournalTypeId - AND tblStockJournalType.StockStatusID_Credit Is Not Null; - ", conn)) - { - cmd.Parameters.AddWithValue("@stockJournalTypeId", stockJournalTypeId); - - object obj = cmd.ExecuteScalar(); - - if (obj == null) - { - throw new Exception("Default credit status not set for StockJournalTypeID=" + stockJournalTypeId); - } - else - { - statusCreditId = (int)obj; - } - } - - // get/set an skuId - int skuId = new Logic.Inventory.SkuService().GetSkuId(productId, conditionId, accountTaxCodeId, true); - - // add the entry to the stock table (minus stockJournalId) - int stockId = 0; - using (SqlCommand cmd = new SqlCommand(@" + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" INSERT INTO tblStock (SkuID, AccountJournalID) OUTPUT INSERTED.StockID VALUES - (@skuId, @accountJournalId); - ", conn)) - { + (@skuId, @accountJournalId);"; + cmd.Parameters.AddWithValue("@skuId", skuId); cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); - stockId = (int)cmd.ExecuteScalar(); + return (int)cmd.ExecuteScalar(); } - - // insert stock journal entry - var journalPosts = new List<(int statusId, int quantity)>(); - journalPosts.Add((statusDebitId, quantity)); - journalPosts.Add((statusCreditId, (quantity * -1))); - int stockJournalId = Stock.StockJournal.StockJournalInsert(sqlConnectionString, stockJournalTypeId, stockId, journalPosts, stockJournalEntryDate, true); - - // update the stock table - using (SqlCommand cmd = new SqlCommand(@" - UPDATE tblStock - SET StockJournalID=@stockJournalId - WHERE StockID=@stockId; - ", conn)) - { - cmd.Parameters.AddWithValue("@stockJournalId", stockJournalId); - cmd.Parameters.AddWithValue("@stockId", stockId); - - count = cmd.ExecuteNonQuery(); - - if (count < 1) - { - throw new Exception("New stock insert cancelled, failed to update StockJournalID"); - } - } - - scope.Complete(); - return stockId; - } - - public int WIP_StockInsertPurchase(int productId, int conditionId, int accountTaxCodeId, int accountJournalId, int quantity, int statusDebitId) - { - DateTime stockJournalEntryDate; - int stockJournalTypeId = 1; - - using (SqlConnection conn = new SqlConnection(sqlConnectionString)) - { - conn.Open(); - - // retrive info from purchase invoice line/transaction - using (SqlCommand cmd = new SqlCommand(@" - SELECT tblAccountJournal.EntryDate - FROM tblAccountJournal - WHERE (((tblAccountJournal.AccountJournalID)=@accountJournalId)); - ", conn)) - { - cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); - - stockJournalEntryDate = DateTime.SpecifyKind((DateTime)cmd.ExecuteScalar(), DateTimeKind.Utc); - } - } - return WIP_StockInsertSub(productId, conditionId, accountTaxCodeId, accountJournalId, stockJournalTypeId, stockJournalEntryDate, quantity, statusDebitId); - } - - 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 WIP_StockInsert(accountJournalType, stockJournalType, currencyCode, amount, quantity, productId, conditionId, accountTaxCodeId, entryDate, debitStatusId); } // // read // + public int CountStockTableRecords(List accountJournalId = null) + { + throw new NotImplementedException("This needs testing before use"); + + var sqlWhere = new SqlWhereBuilder(); + + string sql = @" + SELECT Count(tblStock.StockID) AS CountOfID + FROM tblStock + WHERE 1=1 "; + + if (accountJournalId != null) + { + sqlWhere.In("AccountJournalID", accountJournalId, "AND"); + } + + if (sqlWhere.IsSetSqlWhereString) + { + 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? ReadStockJournalId(int stockId) { if (stockId <= 0) @@ -337,251 +233,5 @@ namespace bnhtrade.Core.Data.Database.Repository.Implementation return cmd.ExecuteNonQuery(); } } - - private void WIP_StockDelete(int stockId) - { - int accountJournalType = 0; - int stockJournalType = 0; - - // get stock and account types - using (TransactionScope scope = new TransactionScope()) - using (SqlConnection conn = new SqlConnection(sqlConnectionString)) - { - conn.Open(); - - // ensure stockId is owner-introduced - using (SqlCommand cmd = new SqlCommand(@" - SELECT - tblStockJournal.StockJournalTypeID, tblAccountJournal.AccountJournalTypeID - FROM - (tblStock INNER JOIN tblAccountJournal - ON tblStock.AccountJournalID = tblAccountJournal.AccountJournalID) - INNER JOIN tblStockJournal - ON tblStock.StockJournalID = tblStockJournal.StockJournalID - WHERE - (((tblStock.StockID)=@stockId)); - ", conn)) - { - cmd.Parameters.AddWithValue("@stockId", stockId); - - using (var reader = cmd.ExecuteReader()) - { - if (reader.Read()) - { - accountJournalType = reader.GetInt32(1); - stockJournalType = reader.GetInt32(0); - } - else - { - throw new Exception("Integrity check failed, cancelling StockDeleteOwnerIntroduced"); - } - } - } - - // 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 - using (SqlCommand cmd = new SqlCommand(@" - SELECT Count(tblStockJournal.StockJournalID) AS CountOfStockJournalID - FROM tblStockJournal - WHERE (((tblStockJournal.StockID)=@stockId)); - ", conn)) - { - cmd.Parameters.AddWithValue("@stockId", stockId); - - int count = (int)cmd.ExecuteScalar(); - - 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(sqlConnectionString, stockId); - - // remove stock - WIP_StockDeleteSub(sqlConnectionString, stockId); - - scope.Complete(); - } - } - - private void WIP_StockDeleteSub(int stockId) - { - using (TransactionScope scope = new TransactionScope()) - using (SqlConnection conn = new SqlConnection(sqlConnectionString)) - { - conn.Open(); - - // check for accountJournalId on stock table - using (SqlCommand cmd = new SqlCommand(@" - SELECT AccountJournalID - FROM tblStock - WHERE StockID=@stockId; - ", conn)) - { - 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) - { - // nothing to do all is good - } - else - { - int id = (int)obj; - if (id > 0) - { - throw new Exception("StockID=" + stockId + " remove account journal entry using method that created it first."); - } - } - } - - // get stockJournalId - int stockJournalId; - using (SqlCommand cmd = new SqlCommand(@" - SELECT StockJournalID - FROM tblStock - WHERE StockID=@stockId; - ", conn)) - { - cmd.Parameters.AddWithValue("@stockId", stockId); - - try - { - stockJournalId = (int)cmd.ExecuteScalar(); - } - catch - { - throw new Exception("Could not retrive StockJournalID for StockID=" + stockId); - } - } - - // remove stockJournalId from stock table - using (SqlCommand cmd = new SqlCommand(@" - UPDATE tblStock - SET StockJournalID=NULL - WHERE StockID=@stockId; - ", conn)) - { - cmd.Parameters.AddWithValue("@stockId", stockId); - - int count = cmd.ExecuteNonQuery(); - - 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 Core.Logic.Inventory.StockJournalService().StockJournalDelete(stockJournalId); - - // delete stock table entry - using (SqlCommand cmd = new SqlCommand(@" - DELETE FROM tblStock - WHERE StockID=@stockId; - ", conn)) - { - cmd.Parameters.AddWithValue("@stockId", stockId); - - int count = cmd.ExecuteNonQuery(); - - if (count != 1) - { - throw new Exception("StockID = " + stockId + " delete failed"); - } - else - { - scope.Complete(); - } - } - } - } - - // to be used by other methods within a transaction scope - private void WIP_StockDeleteSubAccountJournalEntry(int stockId) - { - using (SqlConnection conn = new SqlConnection(sqlConnectionString)) - { - conn.Open(); - var trans = conn.BeginTransaction(); - - // get the account journal id - int accountJournalId = 0; - using (SqlCommand cmd = new SqlCommand(@" - SELECT AccountJournalID - FROM tblStock - WHERE StockID=@stockId; - ", conn)) - { - cmd.Transaction = trans; - 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) - { - throw new Exception("AccountJournalID not found for StockID=" + stockId); - } - else - { - accountJournalId = (int)obj; - } - } - - // remove entry from stock table - using (SqlCommand cmd = new SqlCommand(@" - UPDATE tblStock - SET AccountJournalID=NULL - WHERE StockID=@stockId; - ", conn)) - { - cmd.Transaction = trans; - cmd.Parameters.AddWithValue("@stockId", stockId); - - int count = cmd.ExecuteNonQuery(); - - 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 Data.Database.Repository.Implementation.AccountJournalRepository(conn, trans).DeleteJournal(accountJournalId); - - trans.Commit(); - } - } - - public void WIP_StockDeletePurchase(int stockId) - { - WIP_StockDeleteSub(sqlConnectionString, stockId); - } - - public void WIP_StockDeleteOwnerIntroduced(int stockId) - { - WIP_StockDelete(sqlConnectionString, stockId); - } } } diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IAccountJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IAccountJournalRepository.cs index 4d74003..35f2e8c 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Interface/IAccountJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IAccountJournalRepository.cs @@ -8,12 +8,9 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface { internal interface IAccountJournalRepository { - int AccountJournalInsert(int journalTypeId, DateTime entryDate, string currencyCode, decimal amount, int debitAccountId = 0, int creditAccountId = 0, bool lockEntry = false); - bool AccountJournalPostInsert(int journalId, DateTime entryDate, string currencyCode, decimal amount, int debitAccountId = 0, int creditAccountId = 0); - Dictionary ReadJournal(List journalIdList); - DateTime ReadJournalEntryDate(int journalId); + int AccountJournalInsert(int journalTypeId, DateTime entryDate, bool lockEntry = false); + bool IsJournalDebitAssetType(int accountJournalId); bool ReadJournalIsLocked(int journalId); Dictionary ReadJournalType(List journalTypeIds = null, List typeTitles = null); - bool DeleteJournal(int accountJournalId); } } diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IPurchaseRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IPurchaseRepository.cs index 4e6bbbb..e0a0fed 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Interface/IPurchaseRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IPurchaseRepository.cs @@ -8,9 +8,6 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface { internal interface IPurchaseRepository { - void WIP_PurchaseLineTransactionNetInsert(int purchaseLineId, string currencyCode, decimal amountNet, DateTime entryDate, int debitAccountId = 0); void WIP_PurchaseLineTransactionNetUpdate(int accountJouranlId, string currencyCode, decimal amountNet, int debitAccountId); - void WIP_PurchaseLineTransactionDelete(int purchaseLineId, int accountJournalId); - } } diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs index b629dbe..3d19b63 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockJournalRepository.cs @@ -8,11 +8,14 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface { internal interface IStockJournalRepository { + public int StockJournalInsert(int journalTypeId, int stockId, List<(int statusId, int quantity)> journalPosts, + DateTime entryDate, bool isNewStock = false); int 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); Dictionary ReadStatusBalanceByStatusId(int statusId); void StockJournalDelete(int stockJournalId); void StockJournalPostInsert(int stockId, int stockJournalId, List<(int statusId, int quantity)> journalPosts, bool isNewStock = false); diff --git a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockRepository.cs b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockRepository.cs index 98188a3..8300651 100644 --- a/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockRepository.cs +++ b/src/bnhtrade.Core/Data/Database/Repository/Interface/IStockRepository.cs @@ -8,10 +8,8 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface { internal interface IStockRepository { - int WIP_StockInsertSub(int productId, int conditionId, int accountTaxCodeId, - int accountJournalId, int stockJournalTypeId, DateTime stockJournalEntryDate, int quantity, int statusDebitId); - int WIP_StockInsertPurchase(int productId, int conditionId, int accountTaxCodeId, int accountJournalId, int quantity, int statusDebitId); - int WIP_StockInsertOwnerIntroduced(decimal amount, int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId); + int InsertNewStock(int skuId, int accountJournalId); + int CountStockTableRecords(List accountJournalId = null); int? ReadAccountJournalId(int stockId); int? ReadStockJournalId(int stockId); int UpdateAccountJournalId(int stockId, int? accountJournalID); @@ -20,7 +18,5 @@ namespace bnhtrade.Core.Data.Database.Repository.Interface void WIP_StockDelete(int stockId); void WIP_StockDeleteSub(int stockId); void WIP_StockDeleteSubAccountJournalEntry(int stockId); - void WIP_StockDeletePurchase(int stockId); - void WIP_StockDeleteOwnerIntroduced(int stockId); } } diff --git a/src/bnhtrade.Core/Logic/Account/AccountJournalService.cs b/src/bnhtrade.Core/Logic/Account/AccountJournalService.cs new file mode 100644 index 0000000..eafd8cf --- /dev/null +++ b/src/bnhtrade.Core/Logic/Account/AccountJournalService.cs @@ -0,0 +1,633 @@ +using bnhtrade.Core.Data.Database; +using bnhtrade.Core.Data.Database.Repository.Implementation; +using bnhtrade.Core.Data.Database.UnitOfWork; +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 = 0, int creditAccountId = 0, bool lockEntry = false) + { + return WithUnitOfWork(uow => + { + // insert header record + int journalId = uow.AccountJournalRepository.AccountJournalInsert(journalTypeId, entryDate, lockEntry); + + // insert post record + int defaultDebit; + int defaultCredit; + + // 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(); + } + + return true; + + + + + + + + + + + + + CommitIfOwned(uow); + return journalId; + }); + } + + public Dictionary ReadJournal(List 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(); + if (hasRows) + { + // build lists to filter db results by + var journalTypeIdList = new List(); + var accountIdList = new List(); + 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 AccountJournalRepository(_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(); + 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; + } + + 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); + } + } + + /// + /// Old code needs sorting + /// + public bool AccountJournalPostInsert(IUnitOfWork uow, 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; + } + + } + + public bool AccountJournalPostUpdate(int journalId, string currencyCode, decimal amount, int debitAccountId = 0, int creditAccountId = 0) + { + // retrive journal entry date + DateTime entryDate; + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) + { + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" + SELECT + tblAccountJournal.EntryDate + FROM + tblAccountJournal + WHERE + (((tblAccountJournal.AccountJournalID)=@accountJournalId));"; + + cmd.Parameters.AddWithValue("@accountJournalId", journalId); + + entryDate = DateTime.SpecifyKind((DateTime)cmd.ExecuteScalar(), DateTimeKind.Utc); + } + + // delete the original posts + 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", journalId); + + cmd.ExecuteNonQuery(); + } + + //insert new posts + bool postResult = AccountJournalPostInsert(journalId, entryDate, currencyCode, amount, debitAccountId, creditAccountId); + + // update modified date on journal + 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", journalId); + cmd.Parameters.AddWithValue("@utcNow", DateTime.UtcNow); + + cmd.ExecuteNonQuery(); + } + + return true; + } + + /// + /// Old code needs sorting + /// + 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; + } + } +} diff --git a/src/bnhtrade.Core/Logic/Account/JournalService.cs b/src/bnhtrade.Core/Logic/Account/JournalService.cs deleted file mode 100644 index 4c6227e..0000000 --- a/src/bnhtrade.Core/Logic/Account/JournalService.cs +++ /dev/null @@ -1,44 +0,0 @@ -using bnhtrade.Core.Data.Database.UnitOfWork; -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Transactions; - -namespace bnhtrade.Core.Logic.Account -{ - public class JournalService : UnitOfWorkBase - { - public JournalService() - { - } - - internal JournalService(IUnitOfWork unitOfWork) : base(unitOfWork) - { - } - - public int JournalInsert(int journalTypeId, DateTime entryDate, string currencyCode, - decimal amount, int debitAccountId = 0, int creditAccountId = 0, bool lockEntry = false) - { - return WithUnitOfWork(uow => - { - int journalId = uow.AccountJournalRepository.AccountJournalInsert(journalTypeId, entryDate, currencyCode, - amount, debitAccountId, creditAccountId, lockEntry); - CommitIfOwned(uow); - return journalId; - }); - } - - public bool JournalDelete(int accountJournalId) - { - return WithUnitOfWork(uow => - { - bool result = uow.AccountJournalRepository.DeleteJournal(accountJournalId); - CommitIfOwned(uow); - return result; - }); - } - } -} diff --git a/src/bnhtrade.Core/Logic/Inventory/StockService.cs b/src/bnhtrade.Core/Logic/Inventory/StockService.cs index 32677e4..3ebc3b6 100644 --- a/src/bnhtrade.Core/Logic/Inventory/StockService.cs +++ b/src/bnhtrade.Core/Logic/Inventory/StockService.cs @@ -1,4 +1,5 @@ using bnhtrade.Core.Data.Database.UnitOfWork; +using bnhtrade.Core.Logic.Account; using System; using System.Collections.Generic; using System.Linq; @@ -22,9 +23,9 @@ namespace bnhtrade.Core.Logic.Inventory return WithUnitOfWork(uow => { - stockJournalEntryDate = uow.AccountJournalRepository.ReadJournalEntryDate(accountJournalId); - int result = uow.StockRepository.WIP_StockInsertSub( - productId, conditionId, accountTaxCodeId, accountJournalId, stockJournalTypeId, stockJournalEntryDate, quantity, statusDebitId); + stockJournalEntryDate = new AccountJournalService(uow).ReadJournalEntryDate(accountJournalId); + int result = WIP_StockInsertSub( + uow, productId, conditionId, accountTaxCodeId, accountJournalId, stockJournalTypeId, stockJournalEntryDate, quantity, statusDebitId); CommitIfOwned(uow); return result; }); @@ -48,15 +49,68 @@ namespace bnhtrade.Core.Logic.Inventory int quantity, int productId, int conditionId, int accountTaxCodeId, DateTime entryDate, int debitStatusId) { // add account journal entry - int accountJournalId = new Logic.Account.JournalService(uow).JournalInsert(accountJournalType, entryDate, currencyCode, amount); + int accountJournalId = new Logic.Account.AccountJournalService(uow).JournalInsert(accountJournalType, entryDate, currencyCode, amount); // make the stock insert - int stockId = uow.StockRepository.WIP_StockInsertSub(productId, conditionId, accountTaxCodeId, + 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 { 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 = uow.StockJournalRepository.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) { @@ -162,8 +216,7 @@ namespace bnhtrade.Core.Logic.Inventory } // delete account journal entry - uow.AccountJournalRepository.DeleteJournal(accountJournalId); + new AccountJournalService(uow).DeleteJournal(accountJournalId); } - } } diff --git a/src/bnhtrade.Core/Logic/Purchase/PurchaseService.cs b/src/bnhtrade.Core/Logic/Purchase/PurchaseService.cs index ea1cc8e..46c83e2 100644 --- a/src/bnhtrade.Core/Logic/Purchase/PurchaseService.cs +++ b/src/bnhtrade.Core/Logic/Purchase/PurchaseService.cs @@ -1,4 +1,6 @@ -using bnhtrade.Core.Data.Database.UnitOfWork; +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; @@ -10,17 +12,50 @@ 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 = 0) { - WithUnitOfWork(uow => + // default to 'Inventory, Receivable/Processing' + if (debitAccountId < 1) { - uow.PurchaseRepository.WIP_PurchaseLineTransactionNetInsert(purchaseLineId, currencyCode, amountNet, entryDate, debitAccountId); - CommitIfOwned(uow); - }); + debitAccountId = defaultAccountId; + } + + // create account journal entry + int journalId = new Data.Database.Repository.Implementation.AccountJournalRepository(_connection, _transaction). + AccountJournalInsert(accountJournalTypeIdNet, entryDate, currencyCode, amountNet, debitAccountId); + + // add transaction to purchase line transaction table + 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", journalId); + + 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) @@ -28,17 +63,60 @@ namespace bnhtrade.Core.Logic.Purchase WithUnitOfWork(uow => { uow.PurchaseRepository.WIP_PurchaseLineTransactionNetUpdate(accountJouranlId, currencyCode, amountNet, debitAccountId); + new AccountJournalService(uow).AccountJournalPostUpdate(accountJouranlId, currencyCode, amountNet, creditAccountId); CommitIfOwned(uow); }); } public void WIP_PurchaseLineTransactionDelete(int purchaseLineId, int accountJournalId) { - WithUnitOfWork(uow => + // check accountJournalId does not exist in stock table + using (SqlCommand cmd = _connection.CreateCommand() as SqlCommand) { - uow.PurchaseRepository.WIP_PurchaseLineTransactionDelete(purchaseLineId, accountJournalId); - CommitIfOwned(uow); - }); + cmd.Transaction = _transaction as SqlTransaction; + cmd.CommandText = @" + SELECT StockID + FROM tblStock + WHERE AccountJournalID=@accountJournalId;"; + + cmd.Parameters.AddWithValue("@accountJournalId", accountJournalId); + + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + if (reader.Read()) + { + throw new Exception("Integrity check failure! AccountJournalID=" + accountJournalId + " exists multiple time in stock table!"); + } + else + { + throw new Exception("Delete stock first before proceeding, AccountJournalID=" + accountJournalId); + } + } + } + } + + // delete line in purchase line transaction table + 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); + + int count = cmd.ExecuteNonQuery(); + + if (count != 1) + { + throw new Exception("Operation cancelled, failed to delete entry in tblPurchaseLineTransaction WHERE AccountJournalID=" + accountJournalId); + } + } + + // delete account journal entry + new AccountJournalService(uow).DeleteJournal(accountJournalId); } } }