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; }
}