namespace AAIntegration.SimmonsBank.API.Services; using AutoMapper; using BCrypt.Net; using AAIntegration.SimmonsBank.API.Entities; using AAIntegration.SimmonsBank.API.Config; using AAIntegration.SimmonsBank.API.Models.Transactions; using System.Collections; using System.Collections.Generic; using System.Linq; using System; using Microsoft.EntityFrameworkCore; using Internal; using System.Collections.Immutable; public interface ITransactionService { IEnumerable GetAll(); Transaction GetById(int id); IEnumerable BulkCreate(List model); Transaction Create(TransactionCreate model, bool errorOnFail = true); void Update(int id, TransactionUpdateRequest model); void Delete(int id); } public class TransactionService : ITransactionService { private DataContext _context; private readonly IMapper _mapper; private readonly ILogger _logger; public TransactionService( DataContext context, IMapper mapper, ILogger logger) { _context = context; _mapper = mapper; _logger = logger; } public IEnumerable GetAll() { return _context.Transactions .Include(t => t.DebitAccount) .Include(t => t.CreditAccount) .ToList(); } public Transaction GetById(int id) { return getTransaction(id); } private Account prepareAccount(int? accountId) { if (accountId == null || accountId.Value == 0) { return null; } Account account = _context.Accounts.Find(accountId.Value); if (account == null) throw new AppException("Could not find account with ID of '" + accountId.Value + "'."); return account; } public IEnumerable BulkCreate(List model) { List transactions = new List(); foreach (TransactionCreate tr in model) { var tran = this.Create(tr, false); if (tran != null) transactions.Add(tran); } return transactions; } public Transaction Create(TransactionCreate model, bool errorOnFail = true) { Transaction transaction = new Transaction { Description = model.Description, Date = model.Date.Date.ToUniversalTime(), CreatedOn = DateTime.UtcNow, UpdatedOn = DateTime.UtcNow, ExternalId = string.IsNullOrWhiteSpace(model.ExternalId) ? "" : model.ExternalId, DebitAccount = prepareAccount(model.DebitAccount), CreditAccount = prepareAccount(model.CreditAccount), Amount = Convert.ToDecimal(model.Amount), IsPending = model.IsPending }; if (this.ValidateTransaction(transaction, errorOnFail) == false) { _logger.LogInformation($"Aborted adding transaction '{transaction.Description}'."); return null; } // At this point transaction itself is valid _context.Transactions.Add(transaction); _context.SaveChanges(); _logger.LogInformation("New transaction successfully created."); return transaction; } public void Update(int id, TransactionUpdateRequest model) { Transaction transaction = getTransaction(id); // Transaction.Date if (model.Date.HasValue) transaction.Date = model.Date.Value; // Transaction.ExternalId if (model.ExternalId != null) transaction.ExternalId = model.ExternalId; // Transaction.Description if (model.Description != null) transaction.Description = model.Description; // Transaction.DebitAccount if (model.DebitAccount.HasValue) transaction.DebitAccount = prepareAccount(model.DebitAccount); // Transaction.CreditAccount if (model.CreditAccount.HasValue) transaction.CreditAccount = prepareAccount(model.CreditAccount.Value); // Transaction.Amount if (model.Amount.HasValue) transaction.Amount = model.Amount.Value; // Transaction.IsPending if (model.IsPending.HasValue) transaction.IsPending = model.IsPending.Value; this.ValidateTransaction(transaction); transaction.UpdatedOn = DateTime.UtcNow; _context.Transactions.Update(transaction); _context.SaveChanges(); _logger.LogInformation($"Transaction '{id}' successfully updated."); } private bool ValidateTransaction(int transactionId) { return this.ValidateTransaction(getTransaction(transactionId)); } private bool ErrorOrFalse(bool error, string errorMessage) { if (error) throw new AppException(errorMessage); _logger.LogWarning(errorMessage); return false; } private bool ValidateTransaction(Transaction transaction, bool errorOnFail = true) { // There has to be at least 1 specified account if (transaction.DebitAccount == null && transaction.CreditAccount == null) return ErrorOrFalse(errorOnFail, "There must be an envelope or account chosen for a transaction."); // Accounts cannot be the same if (transaction.DebitAccount != null && transaction.CreditAccount != null && transaction.DebitAccount.Id == transaction.CreditAccount.Id) return ErrorOrFalse(errorOnFail, "The debit and credit accounts of a transaction cannot be the same."); // Transaction Duplication Check - External ID if (!string.IsNullOrWhiteSpace(transaction.ExternalId) && _context.Transactions.Any(x => x.ExternalId == transaction.ExternalId)) return ErrorOrFalse(errorOnFail, "Transaction with the external ID '" + transaction.ExternalId + "' already exists"); // Transaction Duplication Check - All other fields /*if (_context.Transactions.Any(x => x.Description == transaction.Description && x.Date == transaction.Date && x.DebitAccount == transaction.DebitAccount && x.CreditAccount == transaction.CreditAccount && x.Amount == transaction.Amount)) { return ErrorOrFalse(errorOnFail, "Transaction with the same fields already exists"); }*/ return true; } public void Delete(int id) { var transaction = getTransaction(id); _context.Transactions.Remove(transaction); _context.SaveChanges(); } private Transaction getTransaction(int id) { var transaction = _context.Transactions .Include(t => t.DebitAccount) .Include(t => t.CreditAccount) .FirstOrDefault(t => t.Id == id); if (transaction == null) throw new KeyNotFoundException("Transaction not found"); return transaction; } }