refactor(app): implement year filtering for accounts and enhance dashboard PDF generation
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2">
|
<div class="d-flex justify-content-end gap-2">
|
||||||
<button class="btn btn-success" @onclick="Save"
|
<button class="btn btn-success" @onclick="Save"
|
||||||
disabled="@(!CanSave)"><i class="bi bi-arrow-left-right"></i> @(EditEntry != null ? "Speichern" : "Umbuchen")</button>
|
disabled="@(!CanSave)"><i class="bi bi-arrow-left-right"></i> @(EditEntry != null ? "Speichern" : "Hinzufügen")</button>
|
||||||
<button class="btn btn-outline-secondary" @onclick="Cancel"><i class="bi bi-x-lg"></i> Abbrechen</button>
|
<button class="btn btn-outline-secondary" @onclick="Cancel"><i class="bi bi-x-lg"></i> Abbrechen</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,8 +39,8 @@
|
|||||||
<i class="bi bi-file-earmark-pdf"></i> PDF
|
<i class="bi bi-file-earmark-pdf"></i> PDF
|
||||||
</button>
|
</button>
|
||||||
|
|
|
|
||||||
<button class="btn btn-nav @(selectedYear.HasValue ? "btn-primary" : "btn-outline-secondary")" @onclick="OpenYearFilterDialog">
|
<button class="btn btn-nav btn-primary" @onclick="OpenYearFilterDialog">
|
||||||
<i class="bi bi-funnel"></i> Filter<br>@GetFilterLabel()
|
<i class="bi bi-funnel"></i> Ansicht<br>@GetFilterLabel()
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
@inject ISettingsService SettingsService
|
@inject ISettingsService SettingsService
|
||||||
@inject IPdfStatementService PdfStatementService
|
@inject IPdfStatementService PdfStatementService
|
||||||
@inject IFileSaveService FileSaveService
|
@inject IFileSaveService FileSaveService
|
||||||
|
@inject IEntryService EntryService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
@@ -34,6 +35,10 @@
|
|||||||
<button class="btn-nav btn-dark" @onclick="HandleDashboardExportAsync">
|
<button class="btn-nav btn-dark" @onclick="HandleDashboardExportAsync">
|
||||||
<i class="bi bi-file-earmark-pdf"></i> PDF
|
<i class="bi bi-file-earmark-pdf"></i> PDF
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
<button class="btn btn-nav btn-primary" @onclick="OpenYearFilterDialog">
|
||||||
|
<i class="bi bi-funnel"></i> Ansicht<br>@GetFilterLabel()
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (accounts != null && accounts.Any())
|
@if (accounts != null && accounts.Any())
|
||||||
@@ -107,3 +112,29 @@
|
|||||||
OnCancel="CancelRestoreConfirm" />
|
OnCancel="CancelRestoreConfirm" />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (showYearFilterDialog)
|
||||||
|
{
|
||||||
|
<div class="dialog-backdrop" @onclick="CloseYearFilterDialog">
|
||||||
|
<div class="dialog-content" @onclick:stopPropagation="true">
|
||||||
|
<h5>Filter auswählen</h5>
|
||||||
|
<p class="text-muted mb-3">Wähle ein Jahr oder alle Buchungen.</p>
|
||||||
|
<div class="filter-options-grid">
|
||||||
|
<button class="btn @(selectedYear.HasValue ? "btn-outline-secondary" : "btn-primary")" @onclick="() => SelectFilterYear(null)">
|
||||||
|
Alle Buchungen
|
||||||
|
</button>
|
||||||
|
@foreach (var year in availableYears)
|
||||||
|
{
|
||||||
|
<button class="btn @(selectedYear == year ? "btn-primary" : "btn-outline-secondary")" @onclick="() => SelectFilterYear(year)">
|
||||||
|
@year
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end mt-3">
|
||||||
|
<button class="btn btn-outline-secondary" @onclick="CloseYearFilterDialog">
|
||||||
|
<i class="bi bi-x-lg"></i> Schließen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ public partial class Dashboard
|
|||||||
private bool showAddAccount;
|
private bool showAddAccount;
|
||||||
private bool showEditClubName;
|
private bool showEditClubName;
|
||||||
private bool showRestoreConfirm;
|
private bool showRestoreConfirm;
|
||||||
|
private bool showYearFilterDialog;
|
||||||
|
private int? selectedYear = DateTime.Now.Year;
|
||||||
|
private List<int> availableYears = [];
|
||||||
private string clubName = string.Empty;
|
private string clubName = string.Empty;
|
||||||
private string? operationMessage;
|
private string? operationMessage;
|
||||||
private string operationMessageClass = "alert-info";
|
private string operationMessageClass = "alert-info";
|
||||||
@@ -31,16 +34,22 @@ public partial class Dashboard
|
|||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await LoadClubName();
|
await LoadClubName();
|
||||||
await LoadAccounts();
|
await LoadAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Data Loading
|
#region Data Loading
|
||||||
|
|
||||||
|
private async Task LoadAll()
|
||||||
|
{
|
||||||
|
await LoadAccounts();
|
||||||
|
availableYears = await EntryService.GetAllEntryYearsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadAccounts()
|
private async Task LoadAccounts()
|
||||||
{
|
{
|
||||||
accounts = await AccountService.GetAllAccountsAsync();
|
accounts = await AccountService.GetAllAccountsAsync(selectedYear);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadClubName()
|
private async Task LoadClubName()
|
||||||
@@ -56,7 +65,7 @@ public partial class Dashboard
|
|||||||
{
|
{
|
||||||
await AccountService.CreateAccountAsync(name);
|
await AccountService.CreateAccountAsync(name);
|
||||||
showAddAccount = false;
|
showAddAccount = false;
|
||||||
await LoadAccounts();
|
await LoadAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleSaveClubName(string newName)
|
private async Task HandleSaveClubName(string newName)
|
||||||
@@ -104,8 +113,26 @@ public partial class Dashboard
|
|||||||
|
|
||||||
private async Task HandleDashboardExportAsync()
|
private async Task HandleDashboardExportAsync()
|
||||||
{
|
{
|
||||||
var pdf = await PdfStatementService.GenerateDashboardStatementAsync();
|
var pdf = await PdfStatementService.GenerateDashboardStatementAsync(selectedYear);
|
||||||
savedPdfPath = await FileSaveService.SaveFileAsync(pdf, $"{DisplayClubName}_Übersicht.pdf");
|
var suffix = selectedYear.HasValue ? $"_{selectedYear.Value}" : "_Gesamt";
|
||||||
|
savedPdfPath = await FileSaveService.SaveFileAsync(pdf, $"{DisplayClubName}_Übersicht{suffix}.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenYearFilterDialog()
|
||||||
|
{
|
||||||
|
showYearFilterDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CloseYearFilterDialog()
|
||||||
|
{
|
||||||
|
showYearFilterDialog = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SelectFilterYear(int? year)
|
||||||
|
{
|
||||||
|
selectedYear = year;
|
||||||
|
showYearFilterDialog = false;
|
||||||
|
await LoadAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task HandleOpenSavedPdf()
|
private async Task HandleOpenSavedPdf()
|
||||||
@@ -138,5 +165,7 @@ public partial class Dashboard
|
|||||||
return amount.ToString("N2", CultureInfo.GetCultureInfo("de-DE")) + " €";
|
return amount.ToString("N2", CultureInfo.GetCultureInfo("de-DE")) + " €";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetFilterLabel() => selectedYear?.ToString() ?? "Alle";
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace Duempelkas.App.Services;
|
|||||||
public interface IAccountService
|
public interface IAccountService
|
||||||
{
|
{
|
||||||
Task<List<AccountSummaryDto>> GetAllAccountsAsync();
|
Task<List<AccountSummaryDto>> GetAllAccountsAsync();
|
||||||
|
Task<List<AccountSummaryDto>> GetAllAccountsAsync(int? year);
|
||||||
Task<AccountSummaryDto> GetAccountAsync(int accountId);
|
Task<AccountSummaryDto> GetAccountAsync(int accountId);
|
||||||
Task<AccountSummaryDto> CreateAccountAsync(string name);
|
Task<AccountSummaryDto> CreateAccountAsync(string name);
|
||||||
Task RenameAccountAsync(int accountId, string newName);
|
Task RenameAccountAsync(int accountId, string newName);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public interface IEntryService
|
|||||||
Task<List<EntryDto>> GetEntriesAsync(int accountId, int? year);
|
Task<List<EntryDto>> GetEntriesAsync(int accountId, int? year);
|
||||||
Task<List<EntryDto>> GetEntriesAsync(int accountId, bool currentYearOnly);
|
Task<List<EntryDto>> GetEntriesAsync(int accountId, bool currentYearOnly);
|
||||||
Task<List<int>> GetEntryYearsAsync(int accountId);
|
Task<List<int>> GetEntryYearsAsync(int accountId);
|
||||||
|
Task<List<int>> GetAllEntryYearsAsync();
|
||||||
Task<EntryDto> CreateEntryAsync(int accountId, EntryType type, DateTime date, string title, decimal amount);
|
Task<EntryDto> CreateEntryAsync(int accountId, EntryType type, DateTime date, string title, decimal amount);
|
||||||
Task CreateTransferAsync(int sourceAccountId, int targetAccountId, DateTime date, string title, decimal amount);
|
Task CreateTransferAsync(int sourceAccountId, int targetAccountId, DateTime date, string title, decimal amount);
|
||||||
Task DeleteEntryAsync(int entryId);
|
Task DeleteEntryAsync(int entryId);
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ public interface IPdfStatementService
|
|||||||
Task<byte[]> GenerateStatementAsync(int accountId, int? year);
|
Task<byte[]> GenerateStatementAsync(int accountId, int? year);
|
||||||
Task<byte[]> GenerateStatementAsync(int accountId, bool currentYearOnly);
|
Task<byte[]> GenerateStatementAsync(int accountId, bool currentYearOnly);
|
||||||
Task<byte[]> GenerateDashboardStatementAsync();
|
Task<byte[]> GenerateDashboardStatementAsync();
|
||||||
|
Task<byte[]> GenerateDashboardStatementAsync(int? year);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,16 +13,60 @@ public class AccountService : IAccountService
|
|||||||
|
|
||||||
public AccountService(IDbContextFactory<FinanceDbContext> dbFactory) => _dbFactory = dbFactory;
|
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();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
|
||||||
var accounts = await db.Accounts
|
var accounts = await db.Accounts
|
||||||
.Include(a => a.Entries)
|
|
||||||
.OrderBy(a => a.Name)
|
.OrderBy(a => a.Name)
|
||||||
.ToListAsync();
|
.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)
|
public async Task<AccountSummaryDto> GetAccountAsync(int accountId)
|
||||||
@@ -34,7 +78,11 @@ public class AccountService : IAccountService
|
|||||||
.FirstOrDefaultAsync(a => a.Id == accountId)
|
.FirstOrDefaultAsync(a => a.Id == accountId)
|
||||||
?? throw new InvalidOperationException($"Account {accountId} not found.");
|
?? 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)
|
public async Task<AccountSummaryDto> CreateAccountAsync(string name)
|
||||||
@@ -115,12 +163,4 @@ public class AccountService : IAccountService
|
|||||||
await db.SaveChangesAsync();
|
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();
|
.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)
|
public async Task<EntryDto> CreateEntryAsync(int accountId, EntryType type, DateTime date, string title, decimal amount)
|
||||||
{
|
{
|
||||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
|||||||
@@ -146,7 +146,12 @@ public class PdfStatementService : IPdfStatementService
|
|||||||
return document.GeneratePdf();
|
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();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
|
||||||
@@ -169,25 +174,65 @@ public class PdfStatementService : IPdfStatementService
|
|||||||
transferByEntryId[transfer.TargetEntryId] = transfer;
|
transferByEntryId[transfer.TargetEntryId] = transfer;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entries = await db.Entries
|
var entriesQuery = db.Entries
|
||||||
.Include(e => e.Account)
|
.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)
|
.OrderBy(e => e.Date)
|
||||||
.ThenBy(e => e.CreatedUtc)
|
.ThenBy(e => e.CreatedUtc)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
Dictionary<int, decimal> balanceByAccountId;
|
||||||
|
Dictionary<int, decimal>? carryoverByAccountId = null;
|
||||||
|
|
||||||
|
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 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
|
var movementByAccountId = entries
|
||||||
.GroupBy(e => e.AccountId)
|
.GroupBy(e => e.AccountId)
|
||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
g => g.Key,
|
g => g.Key,
|
||||||
g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount));
|
g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount));
|
||||||
|
|
||||||
var balanceByAccountId = accounts
|
balanceByAccountId = accounts
|
||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
a => a.Id,
|
a => a.Id,
|
||||||
a => a.CarryoverBalance + movementByAccountId.GetValueOrDefault(a.Id));
|
a => a.CarryoverBalance + movementByAccountId.GetValueOrDefault(a.Id));
|
||||||
|
}
|
||||||
|
|
||||||
var totalClubBalance = accounts.Sum(a => a.CarryoverBalance + movementByAccountId.GetValueOrDefault(a.Id));
|
var totalClubBalance = balanceByAccountId.Values.Sum();
|
||||||
|
|
||||||
var processedTransferIds = new HashSet<int>();
|
var processedTransferIds = new HashSet<int>();
|
||||||
var rows = new List<DashboardStatementRow>();
|
var rows = new List<DashboardStatementRow>();
|
||||||
@@ -248,7 +293,10 @@ public class PdfStatementService : IPdfStatementService
|
|||||||
page.Header().Column(col =>
|
page.Header().Column(col =>
|
||||||
{
|
{
|
||||||
col.Item().Text(clubName).Bold().FontSize(18);
|
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)}")
|
col.Item().PaddingTop(2).Text($"Gesamtvermögen: {FormatCurrency(totalClubBalance)}")
|
||||||
.FontSize(10)
|
.FontSize(10)
|
||||||
.SemiBold()
|
.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)
|
foreach (var row in rows)
|
||||||
{
|
{
|
||||||
BodyCell(table, row.DisplayId);
|
BodyCell(table, row.DisplayId);
|
||||||
|
|||||||
Reference in New Issue
Block a user