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