using System.Collections.ObjectModel; using System.Security; namespace BankOperationsUpdate.WorkerService.OperationsUpdate; public class UpdateOperations { private IRepositoryWrapper _repositoryWrapper; private readonly ILogger _logger; private readonly IServiceProvider _provider; public UpdateOperations(ILogger logger, IServiceProvider provider) { _logger = logger; _provider = provider; } public void WhenStart() { using (var scope = _provider.CreateScope()) { _repositoryWrapper = scope.ServiceProvider.GetRequiredService(); SetTransactionsInitial(); } } public void Execute() { using (var scope = _provider.CreateScope()) { _repositoryWrapper = scope.ServiceProvider.GetRequiredService(); AddConfirmedOperations(); } using (var scope = _provider.CreateScope()) { _repositoryWrapper = scope.ServiceProvider.GetRequiredService(); UpdateConfirmedTransactions(); } } #region Different Get methods public IQueryable GetAllOperations() => _repositoryWrapper.BankOperation.GetAll(); public IQueryable GetAllConfirmedOperations() => _repositoryWrapper.BankOperation.GetAllConfirmed(Guid.Empty, Guid.Empty); public IQueryable GetAllConfirmedOperations(Guid accountId, Guid businessId) => _repositoryWrapper.BankOperation.GetAllConfirmed(accountId, businessId); public IQueryable GetAllNewOperations() => _repositoryWrapper.BankOperation.GetAllNew(Guid.Empty, Guid.Empty); public IQueryable GetAllNewOperations(Guid accountId, Guid businessId) => _repositoryWrapper.BankOperation.GetAllNew(accountId, businessId); public IQueryable GetAllBankBalances() => _repositoryWrapper.BankAccountBalance.FindAll(); public BankAccountBalance GetAccountBalanceByBusinessId(Guid accountId, Guid businessId) => _repositoryWrapper.BankAccountBalance.GetByBankAccountId(accountId, businessId).FirstOrDefault(); public IQueryable GetTransactionsByAccountId(Guid accountId, Guid businessId) => _repositoryWrapper.Transaction.GetByAccountId(accountId, businessId); public IQueryable GetAllTransactions() => _repositoryWrapper.Transaction.FindAll(); public Transaction GetTransactionByOperationId(Guid operationId) => _repositoryWrapper.Transaction.GetByOperationId(operationId); #endregion void SetTransactionsInitial() { var bankBalances = GetAllBankBalances().ToList(); for(int i = 0; i < bankBalances.Count; i++) { _logger.LogDebug("Process for account {0}", bankBalances[i].Id); SetTransactionsForAccount(bankBalances[i].BankAccountId!.Value, bankBalances[i].BusinessId!.Value); } } void AddConfirmedOperations() { //get all operations with Confirmed status var operations = GetAllConfirmedOperations().OrderBy(x => x.DocDate).ThenBy(x => x.OrderId).ToList(); //get all operations with New status var newOperations = GetAllNewOperations().OrderBy(x => x.DocDate).ThenBy(x => x.OrderId).ToList(); //get all bankOperationIds that added to transactions var transactiondHashSet = GetAllTransactions().Select(x => x.BankOperationId).Distinct().ToList(); BankAccountBalance accountBalance; Transaction transaction; foreach (var item in operations) { //if transaction with opertion id doesn't exist then add new transatcion and change balance if (!transactiondHashSet.Contains(item.Id)) { try { accountBalance = GetAccountBalanceByBusinessId(item.BankAccountId!.Value, item.BusinessId!.Value); //change balance on certain amount accountBalance.Amount = accountBalance.Amount!.Value + item.IncomeAmount - item.OutcomeAmount; //create new transaction transaction = new Transaction { Id = Guid.NewGuid(), BankOperationId = item.Id, IncomeAmount = item.IncomeAmount, OutcomeAmount = item.OutcomeAmount, BalanceAmount = 0, ModifiedDate = item.DocDate, BusinessId = item.BusinessId.Value, BankAccountId = item.BankAccountId.Value, CreateDate = DateTime.Now, OperationCreateDate = item.CreatedDate ?? item.DocDate!.Value, //IsChanged = true, Status = Status.NEW.ToString(), ChangedDifference = item.IncomeAmount - item.OutcomeAmount, OperationOrderId = item.OrderId, }; _logger.LogInformation(Environment.NewLine + "Created new Transaction {0}", transaction.Id); _repositoryWrapper.Transaction.Create(transaction); _repositoryWrapper.BankAccountBalance.Update(accountBalance); _repositoryWrapper.Save(); _repositoryWrapper.Transaction.Detach(transaction); _repositoryWrapper.BankAccountBalance.Detach(accountBalance); } catch(Exception ex) { _logger.LogError(ex.Message); } } //if transactions have already contained opearation but it was changed else if(transactiondHashSet.Contains(item.Id) && item.IsChanged == true) { UpdateChangedOperations(item, true); } } foreach (var item in newOperations) { if (transactiondHashSet.Contains(item.Id)) { UpdateChangedOperations(item, false); } } } void UpdateChangedOperations(BankOperation operation, bool IsChanged) { Transaction transaction = GetTransactionByOperationId(operation.Id); var accountBalance = GetAccountBalanceByBusinessId(operation.BankAccountId.Value, operation.BusinessId.Value); decimal incomeDifference, outcomeDifference; //if it is bankOperation with new status that already was in transatctions if (!IsChanged) { //get the income or outcome sum of rollBackcked transaction incomeDifference = -1 * transaction.IncomeAmount; outcomeDifference = -1 * transaction.OutcomeAmount; transaction.BalanceAmount = 0; transaction.Status = Status.DELETED.ToString(); } //else if it is confirmed transaction that has already in transactions else { //get the income or outcome difference that opertion was changed incomeDifference = operation.IncomeAmount - transaction.IncomeAmount; outcomeDifference = operation.OutcomeAmount - transaction.OutcomeAmount; transaction.Status = Status.CHANGED.ToString(); } transaction.IncomeAmount += incomeDifference; transaction.OutcomeAmount += outcomeDifference; transaction.ChangedDifference += incomeDifference - outcomeDifference; //transaction.IsChanged = true; operation.IsChanged = false; accountBalance.Amount = accountBalance.Amount + incomeDifference - outcomeDifference; _repositoryWrapper.Transaction.Update(transaction); _repositoryWrapper.BankOperation.Update(operation); _repositoryWrapper.BankAccountBalance.Update(accountBalance); _repositoryWrapper.Save(); _repositoryWrapper.Transaction.Detach(transaction); _repositoryWrapper.BankAccountBalance.Detach(accountBalance); // //get the transactions that come after modified transaction and change their balanceAmount on cahnged certain amount // var transactionsList = GetTransactionsByAccountId(operation.BankAccountId.Value, operation.BusinessId.Value) // .Where(x => x.BusinessId == operation.BusinessId && // x.BankAccountId == operation.BankAccountId && // DateTime.Compare(x.ModifiedDate.Value, transaction.ModifiedDate.Value) >= 0) // .OrderBy(x => x.ModifiedDate) // .ThenBy(x => x.OperationOrderId).ToList(); //foreach(var item in transactionsList) { // item.BalanceAmount = item.BalanceAmount + incomeDifference - outcomeDifference; //} // _repositoryWrapper.Transaction.UpdateBulk(transactionsList); // _repositoryWrapper.Save(); //_repositoryWrapper.Transaction.DetachBulk(transactionsList); } //update all new added transactions for updating balance amount of items that comes after updated item void UpdateConfirmedTransactions() { var bankAccounts = GetAllBankBalances().ToList(); List transactions; decimal changedAmount; foreach (var account in bankAccounts) { var allTransactions = GetTransactionsByAccountId(account.BankAccountId!.Value, account.BusinessId!.Value); List removedTransactions = new(); //check if transactions contains isChange element if (allTransactions.Any(x => !x.Status.Equals(Status.CONFIRMED.ToString()))) { //order transactions by DocDate and OperationOrderId transactions = allTransactions.OrderBy(x => x.ModifiedDate).ThenBy(x => x.OperationOrderId).ToList(); //get the index of the first occurance of element with IsChanged = true int changedIndex = transactions.FindIndex(x => !x.Status.Equals(Status.CONFIRMED.ToString())); changedAmount = 0M; for (int i = changedIndex; i < transactions.Count && changedIndex != -1; i++) { //added changed amount changedAmount += transactions[i].ChangedDifference; if (!transactions[i].Status.Equals(Status.DELETED.ToString())) { if (transactions[i].Status.Equals(Status.NEW.ToString())) { transactions[i].BalanceAmount += i > 0 ? transactions[i - 1].BalanceAmount : 0; //if record is added newly then add to balance previous balance and changed difference transactions[i].BalanceAmount += transactions[i].ChangedDifference; } else { transactions[i].BalanceAmount += changedAmount; } //reset changed difference of transaction transactions[i].ChangedDifference = 0; //set status of processed transaction to confirmed transactions[i].Status = Status.CONFIRMED.ToString(); } else { removedTransactions.Add(transactions[i]); transactions.RemoveAt(i); i--; } } _repositoryWrapper.Transaction.UpdateBulk(transactions); _repositoryWrapper.Transaction.DeleteBulk(removedTransactions); _repositoryWrapper.Save(); _repositoryWrapper.Transaction.DetachBulk(transactions); } } } /// /// Create transatction for each bank operation if they don't exist /// /// /// void SetTransactionsForAccount(Guid accountId, Guid businessId) { //get all confirmed operations by accountId and businessId and order them by DocDate var operations = GetAllConfirmedOperations(accountId, businessId).OrderBy(x => x.DocDate).ThenBy(x => x.OrderId).ToList(); // get Unitque bankOpertionsIds from transatctions var transactionsDistinct = GetTransactionsByAccountId(accountId, businessId).Select(x => x.BankOperationId).Distinct().ToList(); ICollection transactions = new Collection(); //get current balance by summarizing all Incomes and outcomes from transacitons var currentBalance = 0M; for (int i = 0; i < operations.Count && transactionsDistinct.Count == 0; i++) { //if BankOperationsIds doesn't contain confirmed operations then.. if (!transactionsDistinct.Contains(operations[i].Id)) { //change balance on certain income or outcome amount currentBalance = currentBalance + operations[i].IncomeAmount - operations[i].OutcomeAmount; transactions.Add(new Transaction{ Id = Guid.NewGuid(), IncomeAmount = operations[i].IncomeAmount, OutcomeAmount = operations[i].OutcomeAmount, BankOperationId = operations[i].Id, IsChanged = false, ModifiedDate = operations[i].DocDate, BusinessId = businessId, BankAccountId = accountId, CreateDate = DateTime.Now, OperationCreateDate = operations[i].CreatedDate ?? operations[i].DocDate!.Value, BalanceAmount = currentBalance, OperationOrderId = operations[i].OrderId, ChangedDifference = 0, Status = Status.CONFIRMED.ToString(), }); } } //create transaction _repositoryWrapper.Transaction.CreateBulk(transactions); _repositoryWrapper.Save(); _logger.LogInformation($"{operations.Count} count records was written"); } }