407 lines
15 KiB
C#
407 lines
15 KiB
C#
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.Autoclass;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Internal;
|
|
|
|
public interface IAutoclassService
|
|
{
|
|
IEnumerable<AutoclassRule> GetAll(int? account = null);
|
|
AutoclassRule GetById(int id);
|
|
void Create(AutoclassRuleCreateRequest model);
|
|
void Update(int autoclassId, AutoclassRuleUpdateRequest model);
|
|
void Delete(int id);
|
|
List<AutoclassFieldMetaDTO> GetAllFieldInfo();
|
|
List<AutoclassTypeMetaDTO> GetAllTypeOperatorInfo();
|
|
Transaction ProcessRule(Transaction transaction, AutoclassRule rule, out bool ruleFired);
|
|
int ApplyRulesForTransaction(int transactionId);
|
|
int ApplyAutoclassRule(int autoclassRule);
|
|
int ApplyAutoclassRule(AutoclassRule autoclassRule);
|
|
}
|
|
|
|
public class AutoclassService : IAutoclassService
|
|
{
|
|
private DataContext _context;
|
|
private readonly IMapper _mapper;
|
|
private readonly ILogger<AutoclassService> _logger;
|
|
private List<AutoclassTypeMetaDTO> typeMetaData;
|
|
private List<AutoclassFieldMetaDTO> fieldMetaData;
|
|
|
|
public AutoclassService(
|
|
DataContext context,
|
|
IMapper mapper,
|
|
ILogger<AutoclassService> logger)
|
|
{
|
|
_context = context;
|
|
_mapper = mapper;
|
|
_logger = logger;
|
|
|
|
this.CreateFieldMetaData();
|
|
this.CreateTypeMetaData();
|
|
}
|
|
|
|
public IEnumerable<AutoclassRule> GetAll(int? account = null)
|
|
{
|
|
if (account.HasValue)
|
|
{
|
|
return _context.AutoclassRules
|
|
.Include(t => t.Account)
|
|
.Include(t => t.Expressions)
|
|
.Include(t => t.Changes)
|
|
.ToList()
|
|
.Where(a => a.Account.Id == account.Value);
|
|
}
|
|
|
|
return _context.AutoclassRules
|
|
.Include(t => t.Expressions)
|
|
.Include(t => t.Changes)
|
|
.ToList();
|
|
}
|
|
|
|
public AutoclassRule GetById(int id)
|
|
{
|
|
return getAutoclass(id);
|
|
}
|
|
|
|
public void Create(AutoclassRuleCreateRequest model)
|
|
{
|
|
AutoclassRule rule = new AutoclassRule {
|
|
Name = model.Name,
|
|
Account = _context.Accounts.Find(model.AccountId),
|
|
Expressions = _mapper.Map<IEnumerable<AutoclassExpression>>(model.Expressions),
|
|
Changes = _mapper.Map<IEnumerable<AutoclassChange>>(model.Changes),
|
|
Enabled = model.Enabled
|
|
};
|
|
|
|
if (rule.Account == null)
|
|
throw new AppException($"Could not find the account with id '{model.AccountId}'.");
|
|
|
|
if (rule.Account.Id == 0)
|
|
throw new AppException($"Can not add rules to account with id '0'.");
|
|
|
|
if (_context.AutoclassRules.Any(x =>
|
|
x.Name.ToUpper() == rule.Name.ToUpper()
|
|
&& x.Account.Id == rule.Account.Id))
|
|
{
|
|
throw new AppException("AutoclassRule with the same name already exists for this account.");
|
|
}
|
|
|
|
_context.AutoclassRules.Add(rule);
|
|
_context.SaveChanges();
|
|
|
|
if (model.TriggerOnCreate.HasValue && model.TriggerOnCreate.Value)
|
|
this.ApplyAutoclassRule(rule.Id);
|
|
}
|
|
|
|
public void Update(int autoclassId, AutoclassRuleUpdateRequest model)
|
|
{
|
|
AutoclassRule rule = getAutoclass(autoclassId);
|
|
|
|
// Account
|
|
if (model.AccountId.HasValue)
|
|
rule.Account = _context.Accounts.Find(model.AccountId.Value);
|
|
|
|
// Name
|
|
if (!string.IsNullOrWhiteSpace(model.Name))
|
|
{
|
|
if (model.Name != rule.Name && _context.AutoclassRules.Any(x =>
|
|
x.Name.ToUpper() == model.Name.ToUpper()
|
|
&& x.Account.Id == rule.Account.Id))
|
|
{
|
|
throw new AppException("AutoclassRule with the same name already exists for this account.");
|
|
}
|
|
|
|
rule.Name = model.Name;
|
|
}
|
|
|
|
// Enabled
|
|
if (model.Enabled.HasValue)
|
|
rule.Enabled = model.Enabled.Value;
|
|
|
|
// Expressions
|
|
if (model.Expressions != null)
|
|
{
|
|
// Remove expression defined on autoclass rule
|
|
foreach (AutoclassExpression expression in rule.Expressions)
|
|
_context.AutoclassExpressions.Remove(expression);
|
|
|
|
// Add the expressions from request
|
|
rule.Expressions = _mapper.Map<IEnumerable<AutoclassExpression>>(model.Expressions);
|
|
}
|
|
|
|
// Changes
|
|
if (model.Changes != null)
|
|
{
|
|
// Remove changes defined on autoclass rule
|
|
foreach (AutoclassChange changes in rule.Changes)
|
|
_context.AutoclassChanges.Remove(changes);
|
|
|
|
// Add the expressions from request
|
|
rule.Changes = _mapper.Map<IEnumerable<AutoclassChange>>(model.Changes);
|
|
}
|
|
|
|
_context.AutoclassRules.Update(rule);
|
|
_context.SaveChanges();
|
|
|
|
// TriggerOnUpdate
|
|
if (model.TriggerOnUpdate.HasValue && model.TriggerOnUpdate.Value)
|
|
this.ApplyAutoclassRule(rule.Id);
|
|
|
|
}
|
|
|
|
public void Delete(int id)
|
|
{
|
|
var autoclassRule = getAutoclass(id);
|
|
_context.AutoclassRules.Remove(autoclassRule);
|
|
_context.SaveChanges();
|
|
}
|
|
|
|
private AutoclassRule getAutoclass(int id)
|
|
{
|
|
var autoclassRule = _context.AutoclassRules
|
|
.Include(t => t.Account)
|
|
.Include(t => t.Expressions)
|
|
.Include(t => t.Changes)
|
|
.FirstOrDefault(o => o.Id == id);
|
|
|
|
//_context.Entry(autoclassRule).Reference(t => t.Expressions).Load();
|
|
//_context.Entry(autoclassRule).Reference(t => t.Changes).Load();
|
|
|
|
if (autoclassRule == null) throw new KeyNotFoundException("AutoclassRule not found");
|
|
return autoclassRule;
|
|
}
|
|
|
|
private void CreateFieldMetaData()
|
|
{
|
|
List<AutoclassFieldMetaDTO> fields = new List<AutoclassFieldMetaDTO>();
|
|
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.AMOUNT));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.CREDIT_ACCOUNT));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.CREDIT_ENVELOPE));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.CURRENCY_TYPE));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.DATE));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.DEBIT_ACCOUNT));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.DEBIT_ENVELOPE));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.DESCRIPTION));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.EXTERNAL_ID));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.IS_PENDING));
|
|
fields.Add(new AutoclassFieldMetaDTO(AutoclassTransactionField.TAGS));
|
|
|
|
this.fieldMetaData = fields;
|
|
}
|
|
|
|
public List<AutoclassFieldMetaDTO> GetAllFieldInfo()
|
|
{
|
|
return this.fieldMetaData;
|
|
}
|
|
|
|
private void CreateTypeMetaData()
|
|
{
|
|
List<AutoclassTypeMetaDTO> types = new List<AutoclassTypeMetaDTO>();
|
|
|
|
types.Add(new AutoclassTypeMetaDTO(AutoclassType.DATETIME));
|
|
types.Add(new AutoclassTypeMetaDTO(AutoclassType.STRING));
|
|
types.Add(new AutoclassTypeMetaDTO(AutoclassType.DECIMAL));
|
|
types.Add(new AutoclassTypeMetaDTO(AutoclassType.BOOLEAN));
|
|
types.Add(new AutoclassTypeMetaDTO(AutoclassType.ACCOUNT));
|
|
types.Add(new AutoclassTypeMetaDTO(AutoclassType.ENVELOPE));
|
|
types.Add(new AutoclassTypeMetaDTO(AutoclassType.CURRENCYTYPE));
|
|
types.Add(new AutoclassTypeMetaDTO(AutoclassType.STRING_ARRAY));
|
|
|
|
this.typeMetaData = types;
|
|
}
|
|
|
|
public List<AutoclassTypeMetaDTO> GetAllTypeOperatorInfo()
|
|
{
|
|
return this.typeMetaData;
|
|
}
|
|
|
|
public int ApplyRulesForTransaction(int transactionId)
|
|
{
|
|
Transaction? 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 == transactionId);
|
|
|
|
if (transaction == null)
|
|
throw new AppException($"Could not find transaction with id {transactionId}.");
|
|
|
|
int rulesFired = 0;
|
|
|
|
if (transaction.DebitAccount != null)
|
|
{
|
|
IEnumerable<AutoclassRule> debitAccountRules = _context.AutoclassRules
|
|
.Include(r => r.Account)
|
|
.Where(r => r.Account.Id == transaction.DebitAccount.Id)
|
|
.Include(r => r.Expressions)
|
|
.Include(r => r.Changes)
|
|
.ToList();
|
|
|
|
|
|
_logger.LogInformation($"Processing Autoclass Rules from DebitAccount '{transaction.DebitAccount.Id}' for new transaction '{transaction.Description}'.");
|
|
foreach (AutoclassRule rule in debitAccountRules)
|
|
{
|
|
transaction = ProcessRule(transaction, rule, out bool ruleFired);
|
|
|
|
if (ruleFired)
|
|
rulesFired++;
|
|
}
|
|
|
|
_logger.LogInformation($"...DebitAccount '{transaction.DebitAccount.Id}' rule processing complete.");
|
|
}
|
|
|
|
if (transaction.CreditAccount != null)
|
|
{
|
|
IEnumerable<AutoclassRule> creditAccountRules = _context.AutoclassRules
|
|
.Include(r => r.Account)
|
|
.Where(r => r.Account.Id == transaction.CreditAccount.Id)
|
|
.Include(r => r.Expressions)
|
|
.Include(r => r.Changes)
|
|
.ToList();
|
|
|
|
_logger.LogInformation($"Processing Autoclass Rules from CreditAccount '{transaction.CreditAccount.Id}' for new transaction '{transaction.Description}'.");
|
|
foreach (AutoclassRule rule in creditAccountRules)
|
|
{
|
|
transaction = ProcessRule(transaction, rule, out bool ruleFired);
|
|
|
|
if (ruleFired)
|
|
rulesFired++;
|
|
}
|
|
|
|
_logger.LogInformation($"...CreditAccount '{transaction.CreditAccount.Id}' rule processing complete.");
|
|
}
|
|
|
|
_context.Transactions.Update(transaction);
|
|
_context.SaveChanges();
|
|
|
|
return rulesFired;
|
|
}
|
|
|
|
public int ApplyAutoclassRule(int autoclassRuleId)
|
|
{
|
|
AutoclassRule? autoclassRule = _context.AutoclassRules
|
|
.Include(r => r.Account)
|
|
.Include(r => r.Expressions)
|
|
.Include(r => r.Changes)
|
|
.FirstOrDefault(r => r.Id == autoclassRuleId);
|
|
|
|
if (autoclassRule == null)
|
|
throw new AppException($"AutoclassRule with id {autoclassRuleId} not found.");
|
|
|
|
return this.ApplyAutoclassRule(autoclassRule);
|
|
}
|
|
|
|
public int ApplyAutoclassRule(AutoclassRule autoclassRule)
|
|
{
|
|
if (autoclassRule.Account == null)
|
|
throw new AppException($"System Error: AutoclassRule with id {autoclassRule} lacks an associated account.");
|
|
|
|
// Get Transactions
|
|
List<Transaction> transactions = _context.Transactions
|
|
.Include(t => t.DebitEnvelope)
|
|
.Include(t => t.DebitAccount)
|
|
.Include(t => t.CreditAccount)
|
|
.Include(t => t.CreditEnvelope)
|
|
.Include(t => t.CurrencyType)
|
|
.Where(t => (t.DebitAccount != null && t.DebitAccount.Id == autoclassRule.Account.Id)
|
|
|| (t.CreditAccount != null && t.CreditAccount.Id == autoclassRule.Account.Id))
|
|
.ToList();
|
|
|
|
int affectedTransactionsCount = 0;
|
|
|
|
// Process Each Transaction for this rule
|
|
for (int i = 0; i < transactions.Count; i++)
|
|
{
|
|
transactions[i] = ProcessRule(transactions[i], autoclassRule, out bool ruleFired);
|
|
_context.Transactions.Update(transactions[i]);
|
|
|
|
if (ruleFired)
|
|
affectedTransactionsCount++;
|
|
}
|
|
|
|
_context.SaveChanges();
|
|
|
|
return affectedTransactionsCount;
|
|
}
|
|
|
|
public Transaction ProcessRule(Transaction transaction, AutoclassRule rule, out bool ruleFired)
|
|
{
|
|
bool triggerRule = true;
|
|
|
|
foreach (AutoclassExpression exp in rule.Expressions)
|
|
{
|
|
triggerRule &= exp.Evaluate(transaction);
|
|
}
|
|
|
|
if (triggerRule)
|
|
{
|
|
_logger.LogInformation($"Rule '{rule.Name}' was triggered.");
|
|
foreach (AutoclassChange chg in rule.Changes)
|
|
{
|
|
_logger.LogInformation($"\tField {chg.Field} will be set to '{chg.Value}'.");
|
|
|
|
switch (chg.Field)
|
|
{
|
|
case AutoclassTransactionField.DATE:
|
|
transaction.Date = Convert.ToDateTime(chg.Value);
|
|
break;
|
|
|
|
case AutoclassTransactionField.EXTERNAL_ID:
|
|
transaction.ExternalId = chg.Value;
|
|
break;
|
|
|
|
case AutoclassTransactionField.DESCRIPTION:
|
|
transaction.Description = chg.Value;
|
|
break;
|
|
|
|
case AutoclassTransactionField.AMOUNT:
|
|
transaction.Amount = Convert.ToDecimal(chg.Value);
|
|
break;
|
|
|
|
case AutoclassTransactionField.IS_PENDING:
|
|
transaction.IsPending = Convert.ToBoolean(chg.Value);
|
|
break;
|
|
|
|
case AutoclassTransactionField.DEBIT_ACCOUNT:
|
|
transaction.DebitAccount = _context.Accounts.Find(Convert.ToInt32(chg.Value));
|
|
break;
|
|
|
|
case AutoclassTransactionField.CREDIT_ACCOUNT:
|
|
transaction.CreditAccount = _context.Accounts.Find(Convert.ToInt32(chg.Value));
|
|
break;
|
|
|
|
case AutoclassTransactionField.DEBIT_ENVELOPE:
|
|
transaction.DebitEnvelope = _context.Envelopes.Find(Convert.ToInt32(chg.Value));
|
|
break;
|
|
|
|
case AutoclassTransactionField.CREDIT_ENVELOPE:
|
|
transaction.CreditEnvelope = _context.Envelopes.Find(Convert.ToInt32(chg.Value));
|
|
break;
|
|
|
|
case AutoclassTransactionField.CURRENCY_TYPE:
|
|
transaction.CurrencyType = _context.CurrencyTypes.Find(Convert.ToInt32(chg.Value));
|
|
break;
|
|
|
|
case AutoclassTransactionField.TAGS:
|
|
transaction = transaction.AddTag(chg.Value);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ruleFired = triggerRule;
|
|
|
|
return transaction;
|
|
}
|
|
} |