private async Task AddSecondPreviousMonthFiguresAsync(DateTime timestamp, MonthlyDigestData digestData, int appOwnerId)
        {
            Sheet sheet = await this._sheetRetrievalService.GetBySubjectAsync(timestamp.Month, timestamp.Year, appOwnerId);

            digestData.Expenses.PreviousAmount = Math.Abs(CalculateExpenses(sheet));
            digestData.Income.PreviousAmount   = CalculateIncome(sheet);
            digestData.Savings.PreviousAmount  = CalculateSavings(sheet);
        }
        private async Task AddPreviousMonthAsync(DateTime timestamp, MonthlyDigestData digestData, int appOwnerId)
        {
            Sheet sheet = await this._sheetRetrievalService.GetBySubjectAsync(timestamp.Month, timestamp.Year, appOwnerId);

            digestData.MonthName = sheet.Subject.ToString("MMMM yyyy");

            digestData.Expenses.Amount = Math.Abs(CalculateExpenses(sheet));
            digestData.Income.Amount   = CalculateIncome(sheet);
            digestData.Savings.Amount  = CalculateSavings(sheet);

            AddLargestExpenses(sheet, digestData);
        }
        private async Task AddWealthInformationAsync(DateTime previousMonth, MonthlyDigestData digestData, int appOwnerId)
        {
            Sheet sheet = await this._sheetRetrievalService.GetBySubjectAsync(previousMonth.Month, previousMonth.Year, appOwnerId);

            CalculationOptions calcOffset = this._calculationService.CalculateOffset(sheet);

            digestData.Wealth.BankAccount.PreviousAmount = calcOffset.BankAccountOffset ?? 0;
            digestData.Wealth.BankAccount.Amount         = digestData.Wealth.BankAccount.PreviousAmount + CalculateFigure(sheet, se => se.Account == AccountType.BankAccount);

            digestData.Wealth.SavingsAccount.PreviousAmount = calcOffset.SavingsAccountOffset ?? 0;
            digestData.Wealth.SavingsAccount.Amount         = digestData.Wealth.SavingsAccount.PreviousAmount + CalculateFigure(sheet, se => se.Account == AccountType.SavingsAccount);
        }
        public async Task SendAsync(string to, MonthlyDigestData monthlyDigest)
        {
            var template = await this._templateProvider.GetTemplateAsync(TemplateName);

            template.AddReplacement("month-name", monthlyDigest.MonthName);
            template.AddReplacement("app-owner-name", monthlyDigest.AppOwnerName);

            AddFigures(monthlyDigest, template);
            AddWealth(monthlyDigest, template);
            AddExpenses(template, monthlyDigest);

            await this._mailService.SendAsync(to, template);
        }
        private void AddUpcomingExpenses(MonthlyDigestData digestData, int appOwnerId)
        {
            const int numberOfExpenses = 5;

            digestData.UpcomingExpenses.AddRange(this._recurringSheetEntryRepository.GetByOwner(appOwnerId)
                                                 .Where(x => x.Delta < 0)
                                                 .Where(x => x.Account == AccountType.BankAccount)
                                                 .OrderBy(x => x.SortOrder)
                                                 .Select(x => new ExpenseModel {
                Amount       = Math.Abs(x.Delta),
                CategoryName = x.Category?.Name,
                Source       = x.Source
            })
                                                 .Take(numberOfExpenses));
        }
        private static void AddLargestExpenses(Sheet sheet, MonthlyDigestData digestData)
        {
            const int numberOfExpenses = 5;

            var q = from entry in sheet.Entries
                    where entry.Account == AccountType.BankAccount
                    where entry.Delta < 0
                    let amount = Math.Abs(entry.Delta)
                                 orderby amount descending
                                 select new ExpenseModel {
                Amount       = amount,
                CategoryName = entry.Category?.Name,
                Source       = entry.Source
            };

            digestData.LargestExpenses.AddRange(q.Take(numberOfExpenses));
        }
        private async Task SendDigestInternal(AppOwner appOwner, List <AppUser> users)
        {
            MonthlyDigestData data = await this._monthlyDigestDataFactory.GetForAsync(appOwner.Id);

            data.AppOwnerName = appOwner.Name;

            foreach (AppUser appUser in users)
            {
                if (!appUser.EmailConfirmed)
                {
                    this._logger.LogWarning($"Job for app owner #{appOwner.Id}: skip digest for {appUser.UserName} #{appUser.Id} because the e-mail address is not valid or confirmed [{appUser.Email}]");
                    continue;
                }

                await this._monthlyDigestMailer.SendAsync(appUser.Email, data);
            }
        }
        private static void AddFigures(MonthlyDigestData monthlyDigest, Template template)
        {
            void AddFigure(MonthlyDigestFigure figure, string id)
            {
                template.AddReplacement($"fig-{id}", figure.Amount.ToString("C"));

                var changePercentage     = figure.ChangeQuotient.ToString("P");
                var changePercentageText =
                    figure.ChangeQuotient < 0 ? $"{changePercentage} minder" : $"{changePercentage} meer";

                template.AddReplacement($"fig-{id}-change-percentage", changePercentageText);
                template.AddReplacement($"comp-fig-{id}", figure.PreviousAmount.ToString("C"));
            }

            AddFigure(monthlyDigest.Expenses, "expense");
            AddFigure(monthlyDigest.Income, "income");
            AddFigure(monthlyDigest.Savings, "savings");
        }
        public async Task <MonthlyDigestData> GetForAsync(int appOwnerId)
        {
            DateTime previousMonth = DateTime.Today.AddMonths(-1);

            previousMonth = new DateTime(previousMonth.Year, previousMonth.Month, 1);

            DateTime secondPreviousMonth = previousMonth.AddMonths(-1);

            var digestData = new MonthlyDigestData();

            await this.AddPreviousMonthAsync(previousMonth, digestData, appOwnerId);

            await this.AddSecondPreviousMonthFiguresAsync(secondPreviousMonth, digestData, appOwnerId);

            this.AddUpcomingExpenses(digestData, appOwnerId);
            await this.AddWealthInformationAsync(previousMonth, digestData, appOwnerId);

            return(digestData);
        }
        private static void AddExpenses(Template template, MonthlyDigestData monthlyDigest)
        {
            void AddExpenses(ICollection <ExpenseModel> expenses, string sectionName, string rowName)
            {
                if (expenses.Count == 0)
                {
                    template.RemoveSection(sectionName);
                    return;
                }

                template.RepeatSection(
                    rowName,
                    expenses,
                    (e, t) => t.Replace("expense-source", e.Source)
                    .Replace("expense-amount", Math.Abs(e.Amount).ToString("C"))
                    .Replace("expense-category", e.CategoryName)
                    );
            }

            AddExpenses(monthlyDigest.UpcomingExpenses, "UPCOMING-EXPENSE-TABLE", "UPCOMING-EXPENSE-TABLE-ROW");
            AddExpenses(monthlyDigest.LargestExpenses, "EXPENSE-TABLE", "EXPENSE-TABLE-ROW");
        }
        private void AddWealth(MonthlyDigestData monthlyDigest, Template template)
        {
            void AddWealth(MonthlyDigestFigure figure, string id)
            {
                string diffString = Math.Abs(figure.Difference).ToString("C");

                diffString += figure.Difference < 0 ? " minder" : " meer";

                string color = figure.Difference < 0 ? "red" : "green";

                template.AddReplacement($"fig-wealth-{id}-diff-human", diffString);
                template.AddReplacement($"fig-wealth-{id}-color", color);

                var changePercentage     = figure.ChangeQuotient.ToString("P");
                var changePercentageText =
                    figure.ChangeQuotient < 0 ? $"{changePercentage} minder" : $"{changePercentage} meer";

                template.AddReplacement($"fig-wealth-{id}-change-percentage", changePercentageText);
            }

            AddWealth(monthlyDigest.Wealth.BankAccount, "bankaccount");
            AddWealth(monthlyDigest.Wealth.SavingsAccount, "savings");
        }