diff --git a/ActiveAllocator.API/ClientApp/src/components/services/AccessAPI.js b/ActiveAllocator.API/ClientApp/src/components/services/AccessAPI.js
index e22139a..ba951a4 100644
--- a/ActiveAllocator.API/ClientApp/src/components/services/AccessAPI.js
+++ b/ActiveAllocator.API/ClientApp/src/components/services/AccessAPI.js
@@ -15,6 +15,8 @@ export const EndPoints = {
AUTOCLASS: "Autoclass",
AUTOCLASS_TYPEOPERATORINFO: "Autoclass/TypeOperatorInfo",
AUTOCLASS_FIELDINFO: "Autoclass/FieldInfo",
+ AUTOCLASS_TRIGGERAUTOCLASSRULE: "Autoclass/TriggerAutoclassRule",
+ AUTOCLASS_TRIGGERTRANSACTIONRULES: "Autoclass/TriggerTransactionRules",
}
export function getData(endPoint) {
diff --git a/ActiveAllocator.API/Controllers/AutoclassController.cs b/ActiveAllocator.API/Controllers/AutoclassController.cs
index a0d99c9..0dfd2b5 100644
--- a/ActiveAllocator.API/Controllers/AutoclassController.cs
+++ b/ActiveAllocator.API/Controllers/AutoclassController.cs
@@ -86,4 +86,18 @@ public class AutoclassController : ControllerBase
{
return Ok(_autoclassService.GetAllFieldInfo());
}
+
+ [HttpGet("TriggerAutoclassRule/{id}")]
+ public IActionResult TriggerAutoclassRule(int id)
+ {
+ int affectedTransactions = _autoclassService.ApplyAutoclassRule(id);
+ return Ok(new { message = $"AutoclassRule triggered and updated {affectedTransactions} transaction(s)." });
+ }
+
+ [HttpGet("TriggerTransactionRules/{id}")]
+ public IActionResult TriggerAutoclassRulesForTransaction(int id)
+ {
+ int triggeredRules = _autoclassService.ApplyRulesForTransaction(id);
+ return Ok(new { message = $"Transaction triggered {triggeredRules} autoclass rule(s)." });
+ }
}
\ No newline at end of file
diff --git a/ActiveAllocator.API/Entities/Transaction.cs b/ActiveAllocator.API/Entities/Transaction.cs
index 46026cd..b6fd0b1 100644
--- a/ActiveAllocator.API/Entities/Transaction.cs
+++ b/ActiveAllocator.API/Entities/Transaction.cs
@@ -23,6 +23,39 @@ public class Transaction
public List
? Tags { get; set; }
}
+public static class TransactionExtensions
+{
+ public static bool HasTag(this Transaction transaction, string tag)
+ {
+ return transaction.Tags != null && transaction.Tags.Any(t => t.ToUpper() == tag.ToUpper());
+ }
+
+ public static Transaction AddTag(this Transaction transaction, string tag)
+ {
+ if (transaction.Tags == null)
+ transaction.Tags = new List() { tag };
+ else if (transaction.Tags.Any(t => t.ToUpper() == tag.ToUpper()) == false)
+ transaction.Tags.Add(tag);
+
+ return transaction;
+ }
+
+ public static Transaction RemoveTag(this Transaction transaction, string tag)
+ {
+ if (transaction.Tags != null)
+ {
+ string? actualTag = transaction.Tags
+ .Where(t => t.ToUpper() == tag.ToUpper())
+ .FirstOrDefault();
+
+ if (actualTag != null)
+ transaction.Tags.Remove(actualTag);
+ }
+
+ return transaction;
+ }
+}
+
public enum AutoclassTransactionField
{
DATE,
diff --git a/ActiveAllocator.API/Models/Transactions/TransactionCreate.cs b/ActiveAllocator.API/Models/Transactions/TransactionCreate.cs
index 10a519e..f435096 100644
--- a/ActiveAllocator.API/Models/Transactions/TransactionCreate.cs
+++ b/ActiveAllocator.API/Models/Transactions/TransactionCreate.cs
@@ -19,6 +19,7 @@ public class TransactionCreate
public string Notes { get; set; }
public bool IsPending { get; set; }
public List? Tags { get; set; } = null;
+ public bool? TriggerAutoclassRules { get; set; } = null;
}
/*
diff --git a/ActiveAllocator.API/Services/AutoclassService.cs b/ActiveAllocator.API/Services/AutoclassService.cs
index 959db1c..d392dfa 100644
--- a/ActiveAllocator.API/Services/AutoclassService.cs
+++ b/ActiveAllocator.API/Services/AutoclassService.cs
@@ -21,21 +21,28 @@ public interface IAutoclassService
void Delete(int id);
List GetAllFieldInfo();
List 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 _logger;
private List typeMetaData;
private List fieldMetaData;
public AutoclassService(
DataContext context,
- IMapper mapper)
+ IMapper mapper,
+ ILogger logger)
{
_context = context;
_mapper = mapper;
+ _logger = logger;
this.CreateFieldMetaData();
this.CreateTypeMetaData();
@@ -249,4 +256,185 @@ public class AutoclassService : IAutoclassService
{
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 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 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 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;
+ }
}
\ No newline at end of file
diff --git a/ActiveAllocator.API/Services/TransactionService.cs b/ActiveAllocator.API/Services/TransactionService.cs
index 1fde882..2a6975d 100644
--- a/ActiveAllocator.API/Services/TransactionService.cs
+++ b/ActiveAllocator.API/Services/TransactionService.cs
@@ -28,15 +28,18 @@ public class TransactionService : ITransactionService
private DataContext _context;
private readonly IMapper _mapper;
private readonly ILogger _logger;
+ private readonly IAutoclassService _autoclassService;
public TransactionService(
DataContext context,
IMapper mapper,
- ILogger logger)
+ ILogger logger,
+ IAutoclassService autoclassService)
{
_context = context;
_mapper = mapper;
_logger = logger;
+ _autoclassService = autoclassService;
}
public IEnumerable GetAll()
@@ -125,15 +128,18 @@ public class TransactionService : ITransactionService
_logger.LogInformation($"Aborted adding transaction '{transaction.Description}'.");
return null;
}
-
- // At this point transaction itself is valid
- transaction = this.ApplyAutoclassRules(transaction);
+ // 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;
@@ -198,15 +204,16 @@ public class TransactionService : ITransactionService
transaction.Tags = model.Tags;
this.ValidateTransaction(transaction);
-
- if (model.TriggerAutoclassRules.HasValue && model.TriggerAutoclassRules == true)
- transaction = this.ApplyAutoclassRules(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);
}
@@ -302,112 +309,6 @@ public class TransactionService : ITransactionService
return true;
}
- private Transaction ApplyAutoclassRules(Transaction transaction)
- {
- if (transaction.DebitAccount != null)
- {
- IEnumerable 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);
-
- _logger.LogInformation($"...DebitAccount '{transaction.DebitAccount.Id}' rule processing complete.");
- }
-
- if (transaction.CreditAccount != null)
- {
- IEnumerable 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);
-
- _logger.LogInformation($"...CreditAccount '{transaction.CreditAccount.Id}' rule processing complete.");
- }
-
- return transaction;
- }
-
- private Transaction ProcessRule(Transaction transaction, AutoclassRule rule)
- {
- 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 = AddTag(transaction, chg.Value);
- break;
- }
- }
- }
-
- return transaction;
- }
-
private void UpdateAccountsAndEnvelopes(Transaction transaction, bool invert = false)
{
_logger.LogInformation($"Updating accounts and envelopes affected by transaction '{transaction.Id}'.");
@@ -428,36 +329,6 @@ public class TransactionService : ITransactionService
_context.AddAmountToEnvelope(transaction.CreditEnvelope.Id, creditAmount);
}
- public bool HasTag(Transaction transaction, string tag)
- {
- return transaction.Tags == null ? false : transaction.Tags.Any(t => t.ToUpper() == tag.ToUpper());
- }
-
- private Transaction AddTag(Transaction transaction, string tag)
- {
- if (transaction.Tags == null)
- transaction.Tags = new List() { tag };
- else if (transaction.Tags.Any(t => t.ToUpper() == tag.ToUpper()) == false)
- transaction.Tags.Add(tag);
-
- return transaction;
- }
-
- private Transaction RemoveTag(Transaction transaction, string tag)
- {
- if (transaction.Tags != null)
- {
- string? actualTag = transaction.Tags
- .Where(t => t.ToUpper() == tag.ToUpper())
- .FirstOrDefault();
-
- if (actualTag != null)
- transaction.Tags.Remove(actualTag);
- }
-
- return transaction;
- }
-
public void Delete(int id)
{
var transaction = getTransaction(id);
diff --git a/Build.sh b/Build.sh
new file mode 100755
index 0000000..c8c6c11
--- /dev/null
+++ b/Build.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+dotnet build
\ No newline at end of file