Exemplo n.º 1
0
        /// <summary>
        /// Calculates from the last six months' average expense, that how can the user save enough money to afford a future
        /// transaction. It lists how much money the user needs to save from each expense category to achieve the goal amount.
        /// </summary>
        public void Calculate()
        {
            decimal             cumulativeAmount = 0;
            List <UserCategory> allCategories;
            List <Transactions> transactionsLastSixMonths;
            DateTime            startDayOfCalculation;
            DateTime            lastDayOfLastMonth;

            CalculationResult.Clear();
            HasCalculation = false;

            // do all the actions that needs database
            using (var db = new DataModel())
            {
                CurrentBalance = (int)db.AccountBalance
                                 .First(ab => ab.AccountID == MainViewModel.CurrentAccount.AccountID)
                                 .Balance
                                 .GetValueOrDefault();
                // if balance included, then set the start amount of calculation to the balance of the current account
                if (CalculationFilter.BalanceIncluded)
                {
                    cumulativeAmount = (decimal)CurrentBalance;
                }

                // set the initial dates
                DateTime lastMonth = DateTime.Today.AddMonths(-1);
                startDayOfCalculation = new DateTime(lastMonth.Year, lastMonth.Month, 1).AddMonths(-5);
                lastDayOfLastMonth    = DateTime.Today.AddDays(-(DateTime.Today.Day));

                Console.WriteLine("Before calc: {0} - {1}", startDayOfCalculation, lastDayOfLastMonth);

                // get the transactions for the last six months
                transactionsLastSixMonths = db.Transactions
                                            .Include(t => t.Categories.CategoryDirections)
                                            .Where(t => t.TransactionDate >= startDayOfCalculation &&
                                                   t.TransactionDate <= lastDayOfLastMonth &&
                                                   t.AccountID == MainViewModel.CurrentAccount.AccountID)
                                            .ToList <Transactions>();

                // get all the categories which are not excluded from calculation
                allCategories = db.UserCategory
                                .Include(uc => uc.Category.CategoryDirections)
                                .Where(uc => uc.UserID == MainViewModel.CurrentUser.UserID)
                                .ToList <UserCategory>();
            }

            //if there is no previous transaction then we can't calculate
            if (transactionsLastSixMonths.Count == 0)
            {
                windowService.UserMessage("Nincs korábbi tranzakció, ami alapján kalkulálni lehetne!");
                return;
            }

            // get the create date of the first transaction
            DateTime firstTransactionDate = transactionsLastSixMonths.Select(t => t.TransactionDate).Min();

            // if the first transactions date is a later date then startDayOfCalculation
            // then set the first day to the transaction date
            if (firstTransactionDate > startDayOfCalculation)
            {
                startDayOfCalculation = new DateTime(firstTransactionDate.Year, firstTransactionDate.Month, 1);
            }

            Console.WriteLine("Start: {0}\nEnd: {1}", startDayOfCalculation, lastDayOfLastMonth);

            // get the months between the starting date and the last month
            int monthDifference = CommonFunctionService.MonthDifference(lastDayOfLastMonth, startDayOfCalculation) + 1;

            Console.WriteLine("Month difference: {0}", monthDifference);

            // get the average amount spent by each category
            var averageAmountByCategories = transactionsLastSixMonths
                                            .GroupBy(t => t.Categories.CategoryName)
                                            .Select(t => new
            {
                Category          = t.Key,
                CategoryDirection = t.First().Categories.CategoryDirections.DirectionName,
                Amount            = t.Sum(s => s.Amount) / monthDifference
            })
                                            .ToList();

            CalculationFilter.CalculationData.Clear();
            // create calculation data for each category, and initialize the limit with the average amount
            foreach (var category in allCategories)
            {
                var currentCategory = averageAmountByCategories
                                      .FirstOrDefault(c => c.Category == category.Category.CategoryName);
                decimal average = 0;
                if (currentCategory != null)
                {
                    average = currentCategory.Amount;
                }

                var newCalcData = new CalculationData
                {
                    CategoryID = category.CategoryID,
                    Average    = average,
                    Limit      = average
                };
                CalculationFilter.CalculationData.Add(newCalcData);
            }

            // average income in a month
            decimal incomes = averageAmountByCategories
                              .Where(c => c.CategoryDirection == "Bevétel")
                              .Sum(c => c.Amount);

            // average expense in a month
            decimal expenses = averageAmountByCategories
                               .Where(c => c.CategoryDirection == "Kiadás")
                               .Sum(c => c.Amount);

            // average balance in a month
            decimal averageBalance = incomes - expenses;

            // get the difference in months between today and the due date
            int monthToGoal = CommonFunctionService.MonthDifference(CalculationFilter.DueDate, DateTime.Today) - 1;

            Console.WriteLine("{0}*{1}+{2}={3}", monthToGoal, averageBalance, cumulativeAmount,
                              monthToGoal * averageBalance + cumulativeAmount);
            Console.WriteLine("{0}", CalculationFilter.Amount);

            cumulativeAmount += monthToGoal * averageBalance;
            // if the average savings plus the start balance is reaching the goal amount, then no
            // calculation is needed
            if (cumulativeAmount >= CalculationFilter.Amount)
            {
                windowService.UserMessage("A jelenlegi bevétel-kiadás arány mellett nem szükséges további spórolás a kitűzött cél megvalósításához");
                return;
            }

            // get the priority levels that are not excluded from calculation
            var categoryPriority = allCategories
                                   .Where(uc => uc.Category.CategoryDirections.DirectionName == "Kiadás" &&
                                          uc.ExcludeFromCalculation == false)
                                   .Select(uc => new
            {
                Priority     = uc.Priority,
                CategoryID   = uc.CategoryID,
                CategoryName = uc.Category.CategoryName,
                Percentage   = uc.Limit
            })
                                   .OrderBy(t => t.Priority)
                                   .ThenByDescending(t => t.Percentage)
                                   .ToList();

            var priorityLevels = categoryPriority
                                 .Select(t => t.Priority)
                                 .Distinct()
                                 .ToList <int>();

            bool goalReached = false;

            // go through each priority level
            foreach (var priority in priorityLevels)
            {
                var currentPriority = categoryPriority
                                      .Where(uc => uc.Priority == priority)
                                      .ToList();
                int    cycleCounter  = 1;
                double maxPercentage = currentPriority.Max(t => t.Percentage);
                bool   exit          = false;
                Console.WriteLine("Priority {0}:", priority);
                while (!exit)
                {
                    Console.WriteLine("{0}. round:", cycleCounter);
                    foreach (var category in currentPriority)
                    {
                        // calculate only if we didn't reach the limit
                        if (category.Percentage >= cycleCounter * 0.01)
                        {
                            var currentCalcData = CalculationFilter
                                                  .CalculationData
                                                  .First(cd => cd.CategoryID == category.CategoryID);
                            currentCalcData.Limit -= currentCalcData.Average * 0.01M;

                            // increase the saved amount by the amount we subtracted from limit for each month
                            cumulativeAmount += currentCalcData.Average * 0.01M * monthToGoal;

                            Console.WriteLine("{0}: {1}->{2}", category.CategoryName, currentCalcData.Average, currentCalcData.Limit);

                            // if the saved amount equals or greater than the planned amount, then we are done
                            if (cumulativeAmount >= CalculationFilter.Amount)
                            {
                                exit        = true;
                                goalReached = true;
                                break;
                            }
                        }
                    }
                    Console.WriteLine("Amount after {0}. round: {1}", cycleCounter, cumulativeAmount);

                    // if we reached the category with the highest percentage then go to next level
                    if (maxPercentage <= cycleCounter * 0.01)
                    {
                        exit = true;
                    }
                    cycleCounter++;
                }

                // after each level we need to check, if we reached the goal already
                if (goalReached)
                {
                    break;
                }
            }

            if (goalReached)
            {
                // create a collection for display
                foreach (var category in allCategories)
                {
                    // if category is not type of expense then skip
                    if (category.Category.CategoryDirections.DirectionName != "Kiadás")
                    {
                        continue;
                    }

                    var calcData = CalculationFilter.CalculationData.First(cd => cd.CategoryID == category.CategoryID);
                    var displayCalculationData = new CalcDataDisplay
                    {
                        CategoryName = category.Category.CategoryName,
                        Average      = calcData.Average,
                        Limit        = calcData.Limit,
                        Saved        = calcData.Average - calcData.Limit
                    };

                    CalculationResult.Add(displayCalculationData);
                }
                CreateCalculationReport(incomes, expenses);
            }
            else
            {
                windowService.UserMessage("Nem sikerült eleget spórolni a cél eléréséhez!");
            }

            HasCalculation = CalculationResult.Count > 0;
        }