356 lines
14 KiB
C#
Raw Normal View History

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<Transaction> GetAll();
Transaction GetById(int id);
IEnumerable<Transaction> BulkCreate(List<TransactionCreate> 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<TransactionService> _logger;
private readonly IAutoclassService _autoclassService;
public TransactionService(
DataContext context,
IMapper mapper,
ILogger<TransactionService> logger,
IAutoclassService autoclassService)
{
_context = context;
_mapper = mapper;
_logger = logger;
_autoclassService = autoclassService;
}
public IEnumerable<Transaction> GetAll()
{
return _context.Transactions
.Include(t => t.CurrencyType)
.Include(t => t.DebitAccount)
.Include(t => t.CreditAccount)
.Include(t => t.DebitEnvelope)
.Include(t => t.CreditEnvelope)
.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;
}
private Envelope prepareEnvelope(int? envelopeId)
{
if (envelopeId == null || envelopeId.Value == 0)
{
return null;
}
Envelope envelope = _context.Envelopes
.Include(e => e.Account)
.FirstOrDefault(e => e.Id == envelopeId.Value);
if (envelope == null)
throw new AppException("Could not find envelope with ID of '" + envelopeId.Value + "'.");
return envelope;
}
public IEnumerable<Transaction> BulkCreate(List<TransactionCreate> model)
{
List<Transaction> transactions = new List<Transaction>();
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),
DebitEnvelope = prepareEnvelope(model.DebitEnvelope),
CreditEnvelope = prepareEnvelope(model.CreditEnvelope),
IsEnvelopeFundingTransaction = model.IsEnvelopeFundingTransaction == null ? false : model.IsEnvelopeFundingTransaction.Value,
Amount = Convert.ToDecimal(model.Amount),
CurrencyType = _context.CurrencyTypes.Find(model.CurrencyType),
Notes = model.Notes,
IsPending = model.IsPending,
Tags = model.Tags,
};
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.");
if (!model.TriggerAutoclassRules.HasValue || model.TriggerAutoclassRules.Value)
_autoclassService.ApplyRulesForTransaction(transaction.Id);
//transaction = this.ApplyAutoclassRules(transaction);
this.UpdateAccountsAndEnvelopes(transaction);
return transaction;
}
public void Update(int id, TransactionUpdateRequest model)
{
Transaction transaction = getTransaction(id);
string temp = "";
// 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.DebitEnvelope
if (model.DebitEnvelope.HasValue)
transaction.DebitEnvelope = prepareEnvelope(model.DebitEnvelope.Value);
// Transaction.CreditEnvelope
if (model.CreditEnvelope.HasValue)
transaction.CreditEnvelope = prepareEnvelope(model.CreditEnvelope.Value);
// Transaction.IsEnvelopeFundingTransaction
if (model.IsEnvelopeFundingTransaction.HasValue)
transaction.IsEnvelopeFundingTransaction = model.IsEnvelopeFundingTransaction.Value;
// Transaction.Amount
if (model.Amount.HasValue)
transaction.Amount = model.Amount.Value;
// Transaction.CurrencyType
if (model.CurrencyType.HasValue)
transaction.CurrencyType = _context.CurrencyTypes.Find(model.CurrencyType.Value);
// Transaction.Notes
if (model.Notes != null)
transaction.Notes = model.Notes;
// Transaction.IsPending
if (model.IsPending.HasValue)
transaction.IsPending = model.IsPending.Value;
// Tags
if (model.Tags != null)
transaction.Tags = model.Tags;
this.ValidateTransaction(transaction);
_context.Transactions.Update(transaction);
_context.SaveChanges();
_logger.LogInformation($"Transaction '{id}' successfully updated.");
if (model.TriggerAutoclassRules.HasValue && model.TriggerAutoclassRules == true)
_autoclassService.ApplyRulesForTransaction(transaction.Id);
//transaction = this.ApplyAutoclassRules(transaction);
this.UpdateAccountsAndEnvelopes(transaction);
}
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;
}
// FIX VALIDATETRANSACTION REFERENCES TO USE BOOL
private bool ValidateTransaction(Transaction transaction, bool errorOnFail = true)
{
// MUST!!! Classify intra-account transactions as NULL account ids, and only envelope Ids
// MUST!!! Classify inter-account transactions as correct account ids, even with envelope ids
// There has to be at least 1 specified account or envelope
if (transaction.DebitAccount == null
&& transaction.CreditAccount == null
&& transaction.DebitEnvelope == null
&& transaction.CreditEnvelope == null)
return ErrorOrFalse(errorOnFail, "There must be an envelope or account chosen for a transaction.");
// Envelopes must have associated parent account entities
if (transaction.CreditEnvelope != null && transaction.CreditEnvelope.Account == null)
return ErrorOrFalse(errorOnFail, $"The parent account of envelope '{transaction.CreditEnvelope.Name}' with id '{transaction.CreditEnvelope.Id}' could not be found.");
if (transaction.DebitEnvelope != null && transaction.DebitEnvelope.Account == null)
return ErrorOrFalse(errorOnFail, $"The parent account of envelope '{transaction.DebitEnvelope.Name}' with id '{transaction.DebitEnvelope.Id}' could not be found.");
// 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.");
// Envelopes cannot be the same
if (transaction.DebitEnvelope != null && transaction.CreditEnvelope != null &&
transaction.DebitEnvelope.Id == transaction.CreditEnvelope.Id)
return ErrorOrFalse(errorOnFail, "The debit and credit envelopes of a transaction cannot be the same.");
// Envelopes must be child of underlying parent account
if (transaction.DebitEnvelope != null && transaction.DebitAccount != null &&
transaction.DebitEnvelope.Account.Id != transaction.DebitAccount.Id)
return ErrorOrFalse(errorOnFail, "The debit envelope of a transaction must be a child of the debit account.");
if (transaction.CreditEnvelope != null && transaction.CreditAccount != null &&
transaction.CreditEnvelope.Account.Id != transaction.CreditAccount.Id)
return ErrorOrFalse(errorOnFail, "The credit envelope of a transaction must be a child of the credit account.");
// If just envelopes are specified, they must belong to the same parent account, or add appropriate accounts
if (transaction.CreditEnvelope != null
&& transaction.DebitEnvelope != null
&& (transaction.DebitAccount != null || transaction.CreditAccount != null))
{
// There is an account specified when the envelopes are of the same account
if (transaction.CreditEnvelope.Account.Id == transaction.DebitEnvelope.Account.Id)
{
transaction.CreditAccount = null;
transaction.DebitAccount = null;
}
else
// There are envelopes from different accounts, without the underlying accounts specified
{
transaction.CreditAccount = transaction.CreditEnvelope.Account;
transaction.DebitAccount = transaction.DebitEnvelope.Account;
}
}
// 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.DebitEnvelope == transaction.DebitEnvelope
&& x.CreditEnvelope == transaction.CreditEnvelope
&& x.Amount == transaction.Amount))
{
return ErrorOrFalse(errorOnFail, "Transaction with the same fields already exists");
}
return true;
}
private void UpdateAccountsAndEnvelopes(Transaction transaction, bool invert = false)
{
_logger.LogInformation($"Updating accounts and envelopes affected by transaction '{transaction.Id}'.");
decimal debitAmount = invert ? transaction.Amount : -transaction.Amount;
decimal creditAmount = invert ? -transaction.Amount : transaction.Amount;
if (transaction.DebitAccount != null)
_context.AddAmountToAccount(transaction.DebitAccount.Id, debitAmount);
if (transaction.CreditAccount != null)
_context.AddAmountToAccount(transaction.CreditAccount.Id, creditAmount);
if (transaction.DebitEnvelope != null)
_context.AddAmountToEnvelope(transaction.DebitEnvelope.Id, debitAmount);
if (transaction.CreditEnvelope != null)
_context.AddAmountToEnvelope(transaction.CreditEnvelope.Id, creditAmount);
}
public void Delete(int id)
{
var transaction = getTransaction(id);
_context.Transactions.Remove(transaction);
_context.SaveChanges();
UpdateAccountsAndEnvelopes(transaction, true);
}
private Transaction getTransaction(int id)
{
var transaction = _context.Transactions
.Include(t => t.DebitEnvelope)
.Include(t => t.DebitAccount)
.Include(t => t.CreditAccount)
.Include(t => t.CreditEnvelope)
.Include(t => t.CurrencyType)
.FirstOrDefault(t => t.Id == id);
if (transaction == null)
throw new KeyNotFoundException("Transaction not found");
return transaction;
}
}