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 _logger; public DataContext( DbContextOptions options, ILogger 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 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 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 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 valuesDebit = new Dictionary(); Dictionary valuesCredit = new Dictionary(); Dictionary valuesIncome = new Dictionary(); bool monthsHaveData = false; int monthsWithData = 0; for (int i = 0; i < lookBack; i++) { DateTime currentLookBackMonth = lastOfLastMonth.AddMonths(-i); List 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 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 valuesDebit = new Dictionary(); Dictionary valuesCredit = new Dictionary(); bool monthsHaveData = false; int monthsWithData = 0; for (int i = 0; i < lookBack; i++) { DateTime currentLookBackMonth = lastOfLastMonth.AddMonths(-i); List 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 GetTransactionsFromMonth(DateTime? date = null, List 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 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 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 Users { get; set; } public DbSet Accounts { get; set; } public DbSet Envelopes { get; set; } public DbSet EnvelopeFundingMethods { get; set; } public DbSet CurrencyTypes { get; set; } public DbSet Operations { get; set; } public DbSet Transactions { get; set; } public DbSet AutoclassRules { get; set; } public DbSet AutoclassExpressions { get; set; } public DbSet AutoclassChanges { get; set; } }