457 lines
16 KiB
C#
457 lines
16 KiB
C#
namespace AAIntegration.SimmonsBank.API.Config;
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
using AAIntegration.SimmonsBank.API.Entities;
|
|
using System.Diagnostics;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using Internal;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
public class DataContext : DbContext
|
|
{
|
|
//protected readonly IConfiguration Configuration;
|
|
readonly ILogger<DataContext> _logger;
|
|
|
|
public DataContext(
|
|
DbContextOptions<DataContext> options,
|
|
ILogger<DataContext> logger)
|
|
: base(options)
|
|
{
|
|
_logger = logger;
|
|
//Configuration = configuration;
|
|
}
|
|
|
|
/*protected override void OnConfiguring(DbContextOptionsBuilder options)
|
|
{
|
|
// in memory database used for simplicity, change to a real db for production applications
|
|
options.UseInMemoryDatabase("TestDb");
|
|
}*/
|
|
|
|
public void AddAmountToAccount(int accountId, decimal amount)
|
|
{
|
|
Account account = this.Accounts.Find(accountId);
|
|
if (account != null)
|
|
{
|
|
_logger.LogInformation($"\t Adding {amount} to account '{account.Name}' (id={accountId}).");
|
|
account.Balance += amount;
|
|
this.Accounts.Update(account);
|
|
this.SaveChanges();
|
|
}
|
|
}
|
|
|
|
public void AddAmountToEnvelope(int envelopeId, decimal amount)
|
|
{
|
|
Envelope envelope = this.Envelopes
|
|
.Include(e => e.Account)
|
|
.FirstOrDefault(e => e.Id == envelopeId);
|
|
|
|
if (envelope != null)
|
|
{
|
|
_logger.LogInformation($"\t Adding {amount} to envelope '{envelope.Name}' (id={envelopeId}).");
|
|
envelope.Balance += amount;
|
|
this.Envelopes.Update(envelope);
|
|
this.SaveChanges();
|
|
|
|
if (envelope.Account != null)
|
|
this.RecalculateAccountVirtualBalance(envelope.Account.Id);
|
|
}
|
|
}
|
|
|
|
public void RecalculateAccountBalance(int id)
|
|
{
|
|
Account account = this.Accounts
|
|
.FirstOrDefault(a => a.Id == id);
|
|
|
|
decimal amount = account.InitialBalance;
|
|
|
|
// Grab relevant transactions
|
|
List<Transaction> transactions = this.Transactions
|
|
.Include(t => t.DebitAccount)
|
|
.Include(t => t.CreditAccount)
|
|
.Where(t =>
|
|
(t.DebitAccount != null && t.DebitAccount.Id == id)
|
|
|| (t.CreditAccount != null && t.CreditAccount.Id == id))
|
|
.ToList();
|
|
|
|
// Iterate and sum values
|
|
foreach (Transaction t in transactions)
|
|
{
|
|
if (t.DebitAccount?.Id == id) {
|
|
amount -= t.Amount;
|
|
} else if (t.CreditAccount?.Id == id) {
|
|
amount += t.Amount;
|
|
}
|
|
}
|
|
|
|
account.Balance = amount;
|
|
_logger.LogInformation($"Recalculating Account Balance for '{account.Name}' as '{amount}'.");
|
|
|
|
this.Accounts.Update(account);
|
|
this.SaveChanges();
|
|
}
|
|
|
|
public void RecalculateAccountVirtualBalance(Envelope env, int lookBack = 12)
|
|
{
|
|
Envelope envelope = this.Envelopes
|
|
.Include(e => e.Account)
|
|
.FirstOrDefault(e => e.Id == env.Id);
|
|
|
|
RecalculateAccountVirtualBalance(envelope.Account.Id, lookBack);
|
|
}
|
|
|
|
public void RecalculateAccountVirtualBalance(int accountId, int lookBack = 12)
|
|
{
|
|
AccountHistorical hist = this.GetAccountHistorical(accountId, lookBack);
|
|
|
|
Account account = this.Accounts.Find(accountId);
|
|
|
|
_logger.LogInformation($"Recalculating virtual balance for account '{account.Name}' (id={accountId}).");
|
|
decimal virtualBalance = account.Balance;
|
|
|
|
// Make this configurable?
|
|
// // 2nd half of CHECK BELOW IS TEMPORARY
|
|
if (hist.Income != null && hist.Income.Median.HasValue)
|
|
{
|
|
/*if (hist.Income.Median.Value == 0 && hist.Income.Mean.HasValue)
|
|
hist.Income.Median = hist.Income.Mean.Value;*/
|
|
|
|
_logger.LogInformation($"Using Median Income Value of {hist.Income.Median.Value}...");
|
|
virtualBalance = hist.Income.Median.Value;
|
|
}
|
|
|
|
// Determine Virtual Balance
|
|
List<Envelope> envelopes = this.Envelopes
|
|
.Include(e => e.Account)
|
|
.Where(e => e.Account.Id == accountId)
|
|
.ToList();
|
|
|
|
foreach (Envelope e in envelopes)
|
|
{
|
|
decimal? envelopeFunding = GetEnvelopeFunding(e.Id);
|
|
if (envelopeFunding.HasValue)
|
|
{
|
|
_logger.LogInformation($"Envelope {e.Name} had funding of {envelopeFunding.Value}.");
|
|
virtualBalance -= envelopeFunding.Value;
|
|
}
|
|
}
|
|
|
|
_logger.LogInformation($"VirtualBalance changed from {account.VirtualBalance} to {virtualBalance}.");
|
|
account.VirtualBalance = virtualBalance;
|
|
this.SaveChanges();
|
|
}
|
|
|
|
public AccountHistorical GetAccountHistorical(int id, int lookBack)
|
|
{
|
|
DateTime now = DateTime.UtcNow;
|
|
AccountHistorical hist = new AccountHistorical() {
|
|
Account = this.Accounts.Find(id),
|
|
LookBack = lookBack
|
|
};
|
|
|
|
DateTime firstOfThisMonth = new DateTime(now.Year, now.Month, 1);
|
|
DateTime lastOfLastMonth = firstOfThisMonth.AddDays(-1);
|
|
DateTime lookBackDate = firstOfThisMonth.AddMonths(-lookBack);
|
|
|
|
List<Transaction> transactions = this.Transactions
|
|
.Include(t => t.CreditAccount)
|
|
.Include(t => t.DebitAccount)
|
|
.Where(t => (t.CreditAccount != null && t.CreditAccount.Id == id) || (t.DebitAccount != null && t.DebitAccount.Id == id))
|
|
.Where(t => t.Date >= lookBackDate.ToUniversalTime() && t.Date <= lastOfLastMonth.ToUniversalTime())
|
|
.ToList();
|
|
|
|
// Sum variables
|
|
decimal sumDebit = 0;
|
|
decimal sumCredit = 0;
|
|
decimal sumIncome = 0;
|
|
|
|
// High variables
|
|
decimal highDebit = 0;
|
|
decimal highCredit = 0;
|
|
decimal highIncome = 0;
|
|
|
|
// Low variables
|
|
decimal lowDebit = 100000000;
|
|
decimal lowCredit = 100000000;
|
|
decimal lowIncome = 100000000;
|
|
|
|
// Values variables
|
|
Dictionary<DateTime, decimal> valuesDebit = new Dictionary<DateTime, decimal>();
|
|
Dictionary<DateTime, decimal> valuesCredit = new Dictionary<DateTime, decimal>();
|
|
Dictionary<DateTime, decimal> valuesIncome = new Dictionary<DateTime, decimal>();
|
|
|
|
bool monthsHaveData = false;
|
|
int monthsWithData = 0;
|
|
|
|
for (int i = 0; i < lookBack; i++)
|
|
{
|
|
DateTime currentLookBackMonth = lastOfLastMonth.AddMonths(-i);
|
|
List<Transaction> monthTransactions = this.GetTransactionsFromMonth(currentLookBackMonth, transactions);
|
|
|
|
decimal monthlySumDebit = 0;
|
|
decimal monthlySumCredit = 0;
|
|
decimal monthlySumIncome = 0;
|
|
|
|
foreach (Transaction tran in monthTransactions)
|
|
{
|
|
monthsHaveData = true;
|
|
|
|
if (tran.CreditAccount != null && tran.CreditAccount.Id == id)
|
|
{
|
|
monthlySumCredit += tran.Amount;
|
|
|
|
if (tran.Tags != null && tran.Tags.Any(t => t.ToUpper() == "INCOME"))
|
|
monthlySumIncome += tran.Amount;
|
|
}
|
|
else
|
|
monthlySumDebit += tran.Amount;
|
|
|
|
if (tran.Amount != 0)
|
|
monthsWithData = i + 1;
|
|
}
|
|
|
|
if (monthsWithData == i + 1)
|
|
{
|
|
valuesDebit.Add(currentLookBackMonth, monthlySumDebit);
|
|
valuesCredit.Add(currentLookBackMonth, monthlySumCredit);
|
|
valuesIncome.Add(currentLookBackMonth, monthlySumIncome);
|
|
}
|
|
|
|
sumDebit += monthlySumDebit;
|
|
sumCredit += monthlySumCredit;
|
|
sumIncome += monthlySumIncome;
|
|
|
|
highDebit = i == 0 || monthlySumDebit > highDebit ? monthlySumDebit : highDebit;
|
|
highCredit = i == 0 || monthlySumCredit > highCredit ? monthlySumCredit : highCredit;
|
|
highIncome = i == 0 || monthlySumIncome > highIncome ? monthlySumIncome : highIncome;
|
|
|
|
lowDebit = i == 0 || monthlySumDebit < lowDebit ? monthlySumDebit : lowDebit;
|
|
lowCredit = i == 0 || monthlySumCredit < lowCredit ? monthlySumCredit : lowCredit;
|
|
lowIncome = i == 0 || monthlySumIncome < lowIncome ? monthlySumIncome : lowIncome;
|
|
}
|
|
|
|
if (monthsHaveData == false)
|
|
return hist;
|
|
|
|
|
|
hist.Debit = new Statistic() {
|
|
Median = HistoricalMedian(valuesDebit),
|
|
Mean = sumDebit / (decimal)monthsWithData,
|
|
High = highDebit,
|
|
Low = lowDebit,
|
|
Values = valuesDebit,
|
|
};
|
|
|
|
hist.Credit = new Statistic() {
|
|
Median = HistoricalMedian(valuesCredit),
|
|
Mean = sumCredit / (decimal)monthsWithData,
|
|
High = highCredit,
|
|
Low = lowCredit,
|
|
Values = valuesCredit,
|
|
};
|
|
|
|
hist.Income = new Statistic() {
|
|
Median = HistoricalMedian(valuesIncome),
|
|
Mean = sumIncome / (decimal)monthsWithData,
|
|
High = highIncome,
|
|
Low = lowIncome,
|
|
Values = valuesIncome,
|
|
};
|
|
|
|
return hist;
|
|
}
|
|
|
|
/*private decimal? SetZeroToNull(decimal value)
|
|
{
|
|
return value == 0 ? null : (decimal?)value;
|
|
}
|
|
|
|
private decimal? SetZeroToNull(decimal? value)
|
|
{
|
|
return value.HasValue && value.Value == 0 ? null : value;
|
|
}*/
|
|
|
|
public EnvelopeHistorical GetEnvelopeHistorical(int id, int lookBack)
|
|
{
|
|
DateTime now = DateTime.UtcNow;
|
|
EnvelopeHistorical hist = new EnvelopeHistorical() {
|
|
Envelope = this.Envelopes.Find(id),
|
|
LookBack = lookBack
|
|
};
|
|
|
|
// EnvelopeFunding this month (period)
|
|
hist.EnvelopeFunding = this.GetEnvelopeFunding(id, now);
|
|
|
|
// Gather remaining statistics
|
|
DateTime firstOfThisMonth = new DateTime(now.Year, now.Month, 1);
|
|
DateTime lastOfLastMonth = firstOfThisMonth.AddDays(-1);
|
|
DateTime lookBackDate = firstOfThisMonth.AddMonths(-lookBack);
|
|
|
|
List<Transaction> transactions = this.Transactions
|
|
.Include(t => t.CreditEnvelope)
|
|
.Include(t => t.DebitEnvelope)
|
|
.Where(t => t.IsEnvelopeFundingTransaction == false)
|
|
.Where(t => (t.CreditEnvelope != null && t.CreditEnvelope.Id == id) || (t.DebitEnvelope != null && t.DebitEnvelope.Id == id))
|
|
.Where(t => t.Date >= lookBackDate.ToUniversalTime() && t.Date <= lastOfLastMonth.ToUniversalTime())
|
|
.ToList();
|
|
|
|
// Sum variables
|
|
decimal sumDebit = 0;
|
|
decimal sumCredit = 0;
|
|
|
|
// High variables
|
|
decimal highDebit = 0;
|
|
decimal highCredit = 0;
|
|
|
|
// Low variables
|
|
decimal lowDebit = 100000000;
|
|
decimal lowCredit = 100000000;
|
|
|
|
// Values variables
|
|
Dictionary<DateTime, decimal> valuesDebit = new Dictionary<DateTime, decimal>();
|
|
Dictionary<DateTime, decimal> valuesCredit = new Dictionary<DateTime, decimal>();
|
|
|
|
bool monthsHaveData = false;
|
|
int monthsWithData = 0;
|
|
|
|
for (int i = 0; i < lookBack; i++)
|
|
{
|
|
DateTime currentLookBackMonth = lastOfLastMonth.AddMonths(-i);
|
|
List<Transaction> monthTransactions = this.GetTransactionsFromMonth(currentLookBackMonth, transactions);
|
|
|
|
decimal monthlySumDebit = 0;
|
|
decimal monthlySumCredit = 0;
|
|
foreach (Transaction tran in monthTransactions)
|
|
{
|
|
monthsHaveData = true;
|
|
|
|
if (tran.CreditEnvelope != null && tran.CreditEnvelope.Id == id)
|
|
monthlySumCredit += tran.Amount;
|
|
else
|
|
monthlySumDebit += tran.Amount;
|
|
|
|
if (tran.Amount != 0)
|
|
monthsWithData = i + 1;
|
|
}
|
|
|
|
if (monthsWithData == i + 1)
|
|
{
|
|
valuesDebit.Add(currentLookBackMonth, monthlySumDebit);
|
|
valuesCredit.Add(currentLookBackMonth, monthlySumCredit);
|
|
}
|
|
|
|
sumDebit += monthlySumDebit;
|
|
sumCredit += monthlySumCredit;
|
|
|
|
highDebit = i == 0 || monthlySumDebit > highDebit ? monthlySumDebit : highDebit;
|
|
highCredit = i == 0 || monthlySumCredit > highCredit ? monthlySumCredit : highCredit;
|
|
lowDebit = i == 0 || monthlySumDebit < lowDebit ? monthlySumDebit : lowDebit;
|
|
lowCredit = i == 0 || monthlySumCredit < lowCredit ? monthlySumCredit : lowCredit;
|
|
}
|
|
|
|
if (monthsHaveData == false)
|
|
return hist;
|
|
|
|
hist.Credit = new Statistic() {
|
|
Median = HistoricalMedian(valuesCredit),
|
|
Mean = sumCredit / (decimal)monthsWithData,
|
|
High = highCredit,
|
|
Low = lowCredit,
|
|
Values = valuesCredit,
|
|
};
|
|
|
|
hist.Debit = new Statistic() {
|
|
Median = HistoricalMedian(valuesDebit),
|
|
Mean = sumDebit / (decimal)monthsWithData,
|
|
High = highDebit,
|
|
Low = lowDebit,
|
|
Values = valuesDebit,
|
|
};
|
|
|
|
return hist;
|
|
}
|
|
|
|
private List<Transaction> GetTransactionsFromMonth(DateTime? date = null, List<Transaction> transactions = null)
|
|
{
|
|
DateTime dt = date.HasValue ? date.Value : DateTime.UtcNow;
|
|
|
|
if (transactions == null)
|
|
return this.Transactions
|
|
.Where(t => t.Date.Year == dt.Year && t.Date.Month == dt.Month)
|
|
.ToList();
|
|
|
|
return transactions
|
|
.Where(t => t.Date.Year == dt.Year && t.Date.Month == dt.Month)
|
|
.ToList();
|
|
}
|
|
|
|
private decimal GetEnvelopeFunding(int id, DateTime? month = null)
|
|
{
|
|
DateTime dt = month.HasValue ? month.Value : DateTime.UtcNow;
|
|
|
|
Transaction envelopeFundTransaction = this.Transactions
|
|
.Include(t => t.CreditEnvelope)
|
|
.Where(t => t.IsEnvelopeFundingTransaction && t.CreditEnvelope != null && t.CreditEnvelope.Id == id)
|
|
.FirstOrDefault(t => t.Date.Year == dt.Year && t.Date.Month == dt.Month);
|
|
|
|
if (envelopeFundTransaction != null)
|
|
return envelopeFundTransaction.Amount;
|
|
|
|
return 0;
|
|
}
|
|
|
|
private decimal HistoricalMedian(Dictionary<DateTime, decimal> data)
|
|
{
|
|
int count = data.Count();
|
|
|
|
if (count % 2 == 0)
|
|
{
|
|
var sortedData = data.OrderBy(s => s.Value).ToArray();
|
|
return (sortedData[count / 2 - 1].Value + sortedData[count / 2].Value) / 2;
|
|
}
|
|
else
|
|
{
|
|
return data.OrderBy(s => s.Value).ElementAt(count / 2).Value;
|
|
}
|
|
}
|
|
|
|
public void RecalculateEnvelopeBalance(int id)
|
|
{
|
|
Envelope envelope = this.Envelopes.Find(id);
|
|
decimal amount = envelope.InitialBalance;
|
|
|
|
List<Transaction> transactions = this.Transactions
|
|
.Include(t => t.DebitEnvelope)
|
|
.Include(t => t.CreditEnvelope)
|
|
.Where(t =>
|
|
(t.DebitEnvelope != null && t.DebitEnvelope.Id == id)
|
|
|| (t.CreditEnvelope != null && t.CreditEnvelope.Id == id))
|
|
.ToList();
|
|
|
|
foreach (Transaction t in transactions)
|
|
{
|
|
if (t.DebitEnvelope?.Id == id) {
|
|
amount -= t.Amount;
|
|
} else if (t.CreditEnvelope?.Id == id) {
|
|
amount += t.Amount;
|
|
}
|
|
}
|
|
|
|
envelope.Balance = amount;
|
|
this.Envelopes.Update(envelope);
|
|
this.SaveChanges();
|
|
}
|
|
|
|
public DbSet<User> Users { get; set; }
|
|
public DbSet<Account> Accounts { get; set; }
|
|
public DbSet<Envelope> Envelopes { get; set; }
|
|
public DbSet<EnvelopeFundingMethod> EnvelopeFundingMethods { get; set; }
|
|
public DbSet<CurrencyType> CurrencyTypes { get; set; }
|
|
public DbSet<Operation> Operations { get; set; }
|
|
public DbSet<Transaction> Transactions { get; set; }
|
|
public DbSet<AutoclassRule> AutoclassRules { get; set; }
|
|
public DbSet<AutoclassExpression> AutoclassExpressions { get; set; }
|
|
public DbSet<AutoclassChange> AutoclassChanges { get; set; }
|
|
} |