Compare commits
2 Commits
9408b98775
...
74dc481081
Author | SHA1 | Date | |
---|---|---|---|
74dc481081 | |||
5d1a4a1e4d |
@ -25,7 +25,6 @@
|
||||
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
||||
<PackageReference Include="BCrypt.Net" Version="0.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaProxy" Version="7.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.9">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -49,6 +49,8 @@ export default function AccountDashboard() {
|
||||
const [envelopes, setEnvelopes] = useState([]);
|
||||
const [autoclassRules, setAutoclassRules] = useState([]);
|
||||
const [accountHistorical, setAccountHistorical] = useState({});
|
||||
|
||||
const [autoclassRuleTriggerMessage, setAutoclassRuleTriggerMessage] = useState("");
|
||||
|
||||
const [account, setAccount] = useState({
|
||||
name: '',
|
||||
@ -142,6 +144,17 @@ export default function AccountDashboard() {
|
||||
);
|
||||
}
|
||||
|
||||
function triggerAutoclassRule(id) {
|
||||
getData(EndPoints.AUTOCLASS_TRIGGERAUTOCLASSRULE + "/" + id).then(
|
||||
(result) => {
|
||||
if (result) {
|
||||
setAutoclassRuleTriggerMessage(result.message);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function findAccountName(id) {
|
||||
let name = accounts?.find(x => x.id === id)?.name;
|
||||
console.log("Id: " + id + ", Name: " + name);
|
||||
@ -229,6 +242,11 @@ export default function AccountDashboard() {
|
||||
className="btn btn-sm btn-outline-danger"
|
||||
text="Delete"
|
||||
onClick={() => navigate(Routes.AUTOCLASS_DELETE + "?id=" + rule.id)} />
|
||||
<RowButton
|
||||
className="btn btn-sm btn-outline-warning"
|
||||
text="Trigger"
|
||||
onClick={() => triggerAutoclassRule(rule.id)} />
|
||||
<span>{autoclassRuleTriggerMessage}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
|
@ -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) {
|
||||
|
@ -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)." });
|
||||
}
|
||||
}
|
@ -23,6 +23,39 @@ public class Transaction
|
||||
public List<string>? 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<string>() { 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,
|
||||
|
@ -19,6 +19,7 @@ public class TransactionCreate
|
||||
public string Notes { get; set; }
|
||||
public bool IsPending { get; set; }
|
||||
public List<string>? Tags { get; set; } = null;
|
||||
public bool? TriggerAutoclassRules { get; set; } = null;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -65,6 +65,11 @@ internal class Program
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Apply Database Migrations - This is NOT recommended for multi-node deployment!!!
|
||||
using var scope = app.Services.CreateScope();
|
||||
using var dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
|
||||
dbContext.Database.Migrate();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
|
@ -21,21 +21,28 @@ public interface IAutoclassService
|
||||
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)
|
||||
IMapper mapper,
|
||||
ILogger<AutoclassService> 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<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;
|
||||
}
|
||||
}
|
@ -28,15 +28,18 @@ 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)
|
||||
ILogger<TransactionService> logger,
|
||||
IAutoclassService autoclassService)
|
||||
{
|
||||
_context = context;
|
||||
_mapper = mapper;
|
||||
_logger = logger;
|
||||
_autoclassService = autoclassService;
|
||||
}
|
||||
|
||||
public IEnumerable<Transaction> 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<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);
|
||||
|
||||
_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);
|
||||
|
||||
_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<string>() { 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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user