Remove the settings-mutating year-hopping in GetAccountExpensesPerMonth that discarded the user's header year selection and silently forced the Spendings-over-time chart back to DateTime.Now.Year. The chart now honours settingsService.GetYear(), skips accounts with no transactions instead of returning null, and no longer depends on IAccountRepository or ISubclassRepository. Also reorder the plan: async becomes PR F, tests PR G. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
104 lines
3.9 KiB
C#
104 lines
3.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Schaad.Accounting.Datasets;
|
|
using Schaad.Accounting.Datasets.Charts;
|
|
using Schaad.Accounting.Interfaces;
|
|
|
|
namespace Schaad.Accounting.Services
|
|
{
|
|
public class ChartService : IChartService
|
|
{
|
|
private readonly ISettingsService settingsService;
|
|
private readonly IViewService viewService;
|
|
|
|
public ChartService(ISettingsService settingsService, IViewService viewService)
|
|
{
|
|
this.settingsService = settingsService;
|
|
this.viewService = viewService;
|
|
}
|
|
|
|
public IReadOnlyList<DataSerie> GetExpensesPerMonth()
|
|
{
|
|
var expenseTransactions = viewService.GetTransactionViewList()
|
|
.Where(t => t.TargetAccount.Class == ClassIds.Expenses)
|
|
.ToList();
|
|
|
|
var distinctSubClasses = expenseTransactions
|
|
.Select(t => t.TargetAccount.SubClass)
|
|
.Distinct()
|
|
.Count();
|
|
|
|
return distinctSubClasses > 1
|
|
? GetSubClassExpensesPerMonth(expenseTransactions)
|
|
: GetAccountExpensesPerMonth();
|
|
}
|
|
|
|
private List<DataSerie> GetSubClassExpensesPerMonth(List<TransactionDataset> expenseTransactions)
|
|
{
|
|
var year = settingsService.GetYear();
|
|
var maxMonth = expenseTransactions
|
|
.OrderByDescending(t => t.ValueDate)
|
|
.FirstOrDefault()?.ValueDate.Month ?? 12;
|
|
|
|
var list = new List<DataSerie>();
|
|
foreach (var grp in expenseTransactions.GroupBy(t => t.TargetAccount.SubClass))
|
|
{
|
|
var groupedByMonth = grp
|
|
.GroupBy(t => t.ValueDate.Month)
|
|
.ToDictionary(g => g.Key, g => g.Sum(t => t.Value));
|
|
EnsureEntryForEveryMonth(groupedByMonth, maxMonth);
|
|
|
|
list.Add(new DataSerie(
|
|
Id: grp.Key.ToString(),
|
|
Name: grp.First().TargetAccount.Name,
|
|
X: groupedByMonth.OrderBy(g => g.Key).Select(g => new DateOnly(year, g.Key, 1)).ToList(),
|
|
Y: groupedByMonth.OrderBy(g => g.Key).Select(g => g.Value).ToList()
|
|
));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private List<DataSerie> GetAccountExpensesPerMonth()
|
|
{
|
|
var year = settingsService.GetYear();
|
|
var allTransactions = viewService.GetTransactionViewList();
|
|
var expenseAccounts = viewService.GetAccountViewList().Where(a => a.Class == ClassIds.Expenses);
|
|
|
|
var list = new List<DataSerie>();
|
|
foreach (var account in expenseAccounts)
|
|
{
|
|
var transactions = allTransactions.Where(t => t.TargetAccountId == account.Id).ToList();
|
|
if (transactions.Count == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var maxMonth = transactions.Max(t => t.ValueDate.Month);
|
|
var groupedByMonth = transactions
|
|
.GroupBy(t => t.ValueDate.Month)
|
|
.ToDictionary(g => g.Key, g => g.Sum(t => t.Value));
|
|
EnsureEntryForEveryMonth(groupedByMonth, maxMonth);
|
|
|
|
list.Add(new DataSerie(
|
|
Id: account.Id,
|
|
Name: account.Name,
|
|
X: groupedByMonth.OrderBy(g => g.Key).Select(g => new DateOnly(year, g.Key, 1)).ToList(),
|
|
Y: groupedByMonth.OrderBy(g => g.Key).Select(g => g.Value).ToList()
|
|
));
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static void EnsureEntryForEveryMonth(Dictionary<int, decimal> values, int maxMonth = 12)
|
|
{
|
|
for (int i = 1; i <= maxMonth; i++)
|
|
{
|
|
if (values.ContainsKey(i) == false)
|
|
{
|
|
values.Add(i, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|