refactor(app): implement year filtering for accounts and enhance dashboard PDF generation
This commit is contained in:
@@ -13,16 +13,60 @@ public class AccountService : IAccountService
|
||||
|
||||
public AccountService(IDbContextFactory<FinanceDbContext> dbFactory) => _dbFactory = dbFactory;
|
||||
|
||||
public async Task<List<AccountSummaryDto>> GetAllAccountsAsync()
|
||||
public Task<List<AccountSummaryDto>> GetAllAccountsAsync()
|
||||
{
|
||||
return GetAllAccountsAsync(null);
|
||||
}
|
||||
|
||||
public async Task<List<AccountSummaryDto>> GetAllAccountsAsync(int? year)
|
||||
{
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
|
||||
var accounts = await db.Accounts
|
||||
.Include(a => a.Entries)
|
||||
.OrderBy(a => a.Name)
|
||||
.ToListAsync();
|
||||
|
||||
return accounts.Select(MapToSummary).ToList();
|
||||
var entries = db.Entries.Where(e => !e.IsDeleted);
|
||||
|
||||
if (year.HasValue)
|
||||
{
|
||||
var movementBeforeYearByAccountId = await entries
|
||||
.Where(e => e.Date.Year < year.Value)
|
||||
.GroupBy(e => e.AccountId)
|
||||
.ToDictionaryAsync(
|
||||
g => g.Key,
|
||||
g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount));
|
||||
|
||||
var movementInYearByAccountId = await entries
|
||||
.Where(e => e.Date.Year == year.Value)
|
||||
.GroupBy(e => e.AccountId)
|
||||
.ToDictionaryAsync(
|
||||
g => g.Key,
|
||||
g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount));
|
||||
|
||||
return accounts
|
||||
.Select(account =>
|
||||
{
|
||||
var carryoverForYear = account.CarryoverBalance + movementBeforeYearByAccountId.GetValueOrDefault(account.Id);
|
||||
var totalBalance = carryoverForYear + movementInYearByAccountId.GetValueOrDefault(account.Id);
|
||||
return new AccountSummaryDto(account.Id, account.Name, carryoverForYear, totalBalance, account.CreatedUtc);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var movementAllByAccountId = await entries
|
||||
.GroupBy(e => e.AccountId)
|
||||
.ToDictionaryAsync(
|
||||
g => g.Key,
|
||||
g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount));
|
||||
|
||||
return accounts
|
||||
.Select(account =>
|
||||
{
|
||||
var totalBalance = account.CarryoverBalance + movementAllByAccountId.GetValueOrDefault(account.Id);
|
||||
return new AccountSummaryDto(account.Id, account.Name, account.CarryoverBalance, totalBalance, account.CreatedUtc);
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<AccountSummaryDto> GetAccountAsync(int accountId)
|
||||
@@ -34,7 +78,11 @@ public class AccountService : IAccountService
|
||||
.FirstOrDefaultAsync(a => a.Id == accountId)
|
||||
?? throw new InvalidOperationException($"Account {accountId} not found.");
|
||||
|
||||
return MapToSummary(account);
|
||||
var totalIncome = account.Entries.Where(e => !e.IsDeleted && e.Type == EntryType.Income).Sum(e => e.Amount);
|
||||
var totalExpense = account.Entries.Where(e => !e.IsDeleted && e.Type == EntryType.Expense).Sum(e => e.Amount);
|
||||
var totalBalance = account.CarryoverBalance + totalIncome - totalExpense;
|
||||
|
||||
return new AccountSummaryDto(account.Id, account.Name, account.CarryoverBalance, totalBalance, account.CreatedUtc);
|
||||
}
|
||||
|
||||
public async Task<AccountSummaryDto> CreateAccountAsync(string name)
|
||||
@@ -115,12 +163,4 @@ public class AccountService : IAccountService
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static AccountSummaryDto MapToSummary(Account account)
|
||||
{
|
||||
var totalIncome = account.Entries.Where(e => e.Type == EntryType.Income).Sum(e => e.Amount);
|
||||
var totalExpense = account.Entries.Where(e => e.Type == EntryType.Expense).Sum(e => e.Amount);
|
||||
var totalBalance = account.CarryoverBalance + totalIncome - totalExpense;
|
||||
|
||||
return new AccountSummaryDto(account.Id, account.Name, account.CarryoverBalance, totalBalance, account.CreatedUtc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,17 @@ public class EntryService : IEntryService
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<int>> GetAllEntryYearsAsync()
|
||||
{
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
|
||||
return await db.Entries
|
||||
.Select(e => e.Date.Year)
|
||||
.Distinct()
|
||||
.OrderByDescending(y => y)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<EntryDto> CreateEntryAsync(int accountId, EntryType type, DateTime date, string title, decimal amount)
|
||||
{
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
|
||||
@@ -146,7 +146,12 @@ public class PdfStatementService : IPdfStatementService
|
||||
return document.GeneratePdf();
|
||||
}
|
||||
|
||||
public async Task<byte[]> GenerateDashboardStatementAsync()
|
||||
public Task<byte[]> GenerateDashboardStatementAsync()
|
||||
{
|
||||
return GenerateDashboardStatementAsync(null);
|
||||
}
|
||||
|
||||
public async Task<byte[]> GenerateDashboardStatementAsync(int? year)
|
||||
{
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
|
||||
@@ -169,25 +174,65 @@ public class PdfStatementService : IPdfStatementService
|
||||
transferByEntryId[transfer.TargetEntryId] = transfer;
|
||||
}
|
||||
|
||||
var entries = await db.Entries
|
||||
var entriesQuery = db.Entries
|
||||
.Include(e => e.Account)
|
||||
.Where(e => !e.IsDeleted)
|
||||
.Where(e => !e.IsDeleted);
|
||||
|
||||
if (year.HasValue)
|
||||
{
|
||||
entriesQuery = entriesQuery.Where(e => e.Date.Year == year.Value);
|
||||
}
|
||||
|
||||
var entries = await entriesQuery
|
||||
.OrderBy(e => e.Date)
|
||||
.ThenBy(e => e.CreatedUtc)
|
||||
.ToListAsync();
|
||||
|
||||
var movementByAccountId = entries
|
||||
.GroupBy(e => e.AccountId)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount));
|
||||
Dictionary<int, decimal> balanceByAccountId;
|
||||
Dictionary<int, decimal>? carryoverByAccountId = null;
|
||||
|
||||
var balanceByAccountId = accounts
|
||||
.ToDictionary(
|
||||
a => a.Id,
|
||||
a => a.CarryoverBalance + movementByAccountId.GetValueOrDefault(a.Id));
|
||||
if (year.HasValue)
|
||||
{
|
||||
var movementBeforeYearByAccountId = await db.Entries
|
||||
.Where(e => !e.IsDeleted && e.Date.Year < year.Value)
|
||||
.GroupBy(e => e.AccountId)
|
||||
.ToDictionaryAsync(
|
||||
g => g.Key,
|
||||
g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount));
|
||||
|
||||
var totalClubBalance = accounts.Sum(a => a.CarryoverBalance + movementByAccountId.GetValueOrDefault(a.Id));
|
||||
var movementInYearByAccountId = entries
|
||||
.GroupBy(e => e.AccountId)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount));
|
||||
|
||||
carryoverByAccountId = accounts
|
||||
.ToDictionary(
|
||||
a => a.Id,
|
||||
a => a.CarryoverBalance + movementBeforeYearByAccountId.GetValueOrDefault(a.Id));
|
||||
|
||||
balanceByAccountId = accounts
|
||||
.ToDictionary(
|
||||
a => a.Id,
|
||||
a => a.CarryoverBalance
|
||||
+ movementBeforeYearByAccountId.GetValueOrDefault(a.Id)
|
||||
+ movementInYearByAccountId.GetValueOrDefault(a.Id));
|
||||
}
|
||||
else
|
||||
{
|
||||
var movementByAccountId = entries
|
||||
.GroupBy(e => e.AccountId)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount));
|
||||
|
||||
balanceByAccountId = accounts
|
||||
.ToDictionary(
|
||||
a => a.Id,
|
||||
a => a.CarryoverBalance + movementByAccountId.GetValueOrDefault(a.Id));
|
||||
}
|
||||
|
||||
var totalClubBalance = balanceByAccountId.Values.Sum();
|
||||
|
||||
var processedTransferIds = new HashSet<int>();
|
||||
var rows = new List<DashboardStatementRow>();
|
||||
@@ -248,7 +293,10 @@ public class PdfStatementService : IPdfStatementService
|
||||
page.Header().Column(col =>
|
||||
{
|
||||
col.Item().Text(clubName).Bold().FontSize(18);
|
||||
col.Item().Text("Übersicht aller Konten").FontSize(13).FontColor(Colors.Grey.Darken1);
|
||||
var headerTitle = year.HasValue
|
||||
? $"Übersicht aller Konten ({year.Value})"
|
||||
: "Übersicht aller Konten";
|
||||
col.Item().Text(headerTitle).FontSize(13).FontColor(Colors.Grey.Darken1);
|
||||
col.Item().PaddingTop(2).Text($"Gesamtvermögen: {FormatCurrency(totalClubBalance)}")
|
||||
.FontSize(10)
|
||||
.SemiBold()
|
||||
@@ -285,6 +333,18 @@ public class PdfStatementService : IPdfStatementService
|
||||
}
|
||||
});
|
||||
|
||||
if (year.HasValue && carryoverByAccountId is not null)
|
||||
{
|
||||
BodyCell(table, string.Empty);
|
||||
BodyCell(table, string.Empty);
|
||||
BodyCell(table, $"Übertrag von {year.Value - 1}", alignRight: true);
|
||||
|
||||
foreach (var account in accounts)
|
||||
{
|
||||
BodyAmountCell(table, carryoverByAccountId.GetValueOrDefault(account.Id));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
BodyCell(table, row.DisplayId);
|
||||
|
||||
Reference in New Issue
Block a user